docs: register P13 dark mode (crearte 0.26.0) + server micro-batch (0.17.2)

Brings the P13 batch into this wrapper's version control: spec and plan enter
the repo, ROADMAP gains the P13 row and its document-index entry, and the four
stale 'C dark mode' backlog mentions carried since P9/P10/P9-B/P12 are marked
cleared in place — following the P12 precedent of annotating the historical row
rather than rewriting it, since those entries were true when written.

Wrapper CHANGELOG 0.3.6 records the crearte-only dark-mode delivery
(983e7c4, merge-base e59a171), the parallel server micro-batch delivered during
the survey phase while the implementer owned crearte exclusively (dbf7fe5,
0.17.2: a real uploads.go error-fallthrough defect with zero prior coverage,
plus a decision-record comment on the bundle-key route after grep overturned the
initial suspicion of a hole), and three lessons:

- max(a,b) >= k is a vacuous-assertion hot zone: when the two sides are
  complementary the max has a non-trivial lower bound (here sqrt(16.50) =
  4.0621), so any threshold below it can never fail. The controller's own first
  correction to the scrim guard shipped exactly that, and the same
  one-directional verification recurred in the alpha fallback. Fixing a guard
  now requires proving both no false-red on legal values and no false-green
  under mutation.
- Tailwind v4 scans every source file including test files, so a class-name
  literal in a test comment burns a dead utility into the artifact.
- A universal claim needs a universal grep: 'accent is the only background use'
  was false because both the spike-0 grep and the new guard covered app/ while
  runtime/ sits beside it. That blind spot cost a false spec fact and hid a real
  pre-existing WCAG violation (paper on accent = 3.2590 in light, since P9-B).

ROADMAP's P13 row cites merge commit 983e7c4 per the P12 convention, with the
bookkeeping commits named separately so the reference cannot be mistaken for
them.
This commit is contained in:
2026-10-03 07:30:12 +08:00
parent b9e59aee83
commit 14d029e61b
4 changed files with 644 additions and 4 deletions
+13
View File
@@ -6,6 +6,19 @@ All notable changes to this repository should be documented in this file.
The format loosely follows Keep a Changelog and can be adapted to the team's habits.
本文档参考了 Keep a Changelog 的思路,也可以根据团队习惯调整。
## [0.3.6] - 2026-10-03
### Done / 完成
- **P13 dark mode delivered** (crearte-only batch): crearte `983e7c4` (0.26.0, merge-base `e59a171`); zero server/deploy changes. A two-state theme (light ↔ dark) site-wide — first visit follows `prefers-color-scheme`, an explicit toggle persists to `localStorage['crearte.theme.v1']` and overrides the system preference thereafter (no third "follow-system" state, deliberately deferred: spike 3 measured that the header has no pixel room at 320px worst-case for a labelled control). Each theme carries 12 design tokens; the dark palette overrides the light `@theme` block wholesale under `html[data-theme="dark"]` (specificity (0,1,1), no `!important`, no `@apply`) — **approach B**, which needed only 3 usage migrations versus the 32+ that approach A (flip every token in place) would have demanded, and which leaves `::selection`, markdown `strong`, and `.wordmark-label` untouched. A pre-paint inline `<script>` in `index.html` (IIFE, `var` only, before any CSS or the Vue module) sets `data-theme` so dark-preference users never see a white flash, with stored→system→light precedence byte-for-byte identical to `useTheme.effectiveTheme()`. Dispatch followed one-writer-per-repo: T1–T5 all touch crearte's single worktree, so they went to **one** implementer subagent; the guard task was TDD-first (write the guard, watch it go RED reproducing the spec's numbers verbatim, then implement to GREEN). **Review ladder**: task-level review **PASS with notes** (5 findings from 10 self-designed mutations), whole-branch final review **APPROVE with notes** (zero critical, 2 important, 5 minor, 3 advisory). All ten notes adjudicated before merge: four fixed forward in `2433ec7`/`481574c`/`6ec6170`, four more in `bb2cb42`, five were spec-document corrections, one pre-existing defect deferred to the a11y polish batch, two accepted with reason. Gates: crearte vitest **688/79** (baseline 635/74), vue-tsc clean, build + build:runtime clean, main e2e **102 passed + 1 skipped** (baseline 92+1skip; P12's 12 `responsive.spec.ts` legs untouched and green), noauth 4 — re-run independently by the controller and both reviewers. Artifact `main-C7966Gm-.css`: `var(--color-*)` = 75, inline `#141414` = 2, dark block present, dead utilities zero.
- **P13 暗色模式交付**(仅 crearte 的批次):crearte `983e7c4`(0.26.0,merge-base `e59a171`);server/deploy 零改动。两态主题(light ↔ dark)全站落地——首次访问跟随 `prefers-color-scheme`,显式点击开关后持久化到 `localStorage['crearte.theme.v1']` 并从此压过系统偏好(**无第三态**「恢复跟随系统」,有意延后:spike 3 实测 header 在 @320px 最坏格无像素容纳带标签控件)。每主题 12 个设计令牌;暗色调色板在 `html[data-theme="dark"]`(特异性 (0,1,1),无 `!important`/`@apply`)下整体覆盖亮色 `@theme` 块 = **方案 B**,故只需 3 处 usage 迁移(方案 A 逐令牌就地翻转需 32+ 处),且 `::selection`、markdown `strong`、`.wordmark-label` 零改动。`index.html` 一段 pre-paint 内联 `<script>`(IIFE、仅 `var`、在任何 CSS 与 Vue module 之前)设 `data-theme`,故暗色偏好用户不闪白,判定优先级 stored→system→light 与 `useTheme.effectiveTheme()` 逐字一致。派发遵循一仓一写者:T1–T5 同动 crearte 单一工作树,故交**一个**实现者子代理;守卫任务 TDD 先行(先写守卫、看它变红并逐字复现 spec 数字、再实现到绿)。**审查阶梯**:任务级审查 **PASS with notes**(10 条自设 mutation 挖出 5 条 findings)、全分支终审 **APPROVE with notes**(零关键 / 2 重要 / 5 次要 / 3 建议)。10 条 note 合并前逐条裁定:4 条在 `2433ec7`/`481574c`/`6ec6170` fix-forward、另 4 条在 `bb2cb42`、5 条是 spec 文档纠正、1 条 pre-existing 缺陷延后到可访问性打磨批、2 条 accepted-with-reason。验收:crearte vitest **688/79**(基线 635/74)、vue-tsc 零错、build + build:runtime 零错、主 e2e **102 passed + 1 skipped**(基线 92+1skip;P12 的 12 条 `responsive.spec.ts` 腿未动全绿)、noauth 4——控制者与两位审查者各自独立复跑。产物 `main-C7966Gm-.css`:`var(--color-*)` = 75、内联 `#141414` = 2、暗色块在位、死 utility 归零。
- **Parallel server micro-batch** delivered during the P13 survey phase (the implementer owned crearte exclusively, so crearte-server was free to the controller): crearte-server `dbf7fe5` (0.17.2). A read-only audit found a real defect in `uploads.go` — when `ParseMultipartForm` failed with anything other than `MaxBytesError`, the handler fell through to the kind switch and returned a misleading 400 "kind must be bundle or cover", and **no test covered it** (all five existing upload tests send valid multipart). Test-first RED, fix, then mutation-verified (revert → red, restore → green, `porcelain=0`). The second audit item — the bundle-key route being public — was initially suspected as a hole but **overturned by grep**: six existing pins already assert it, so only a decision-record comment was added (zero behaviour change). Gates: gofmt/vet clean, `go test ./...` 11 packages ok, `ls-remote` MATCH, branch deleted.
- **并行的 server 微批**在 P13 勘查期交付(实现者独占 crearte,故 crearte-server 对控制者自由):crearte-server `dbf7fe5`(0.17.2)。只读审计发现 `uploads.go` 一个真缺陷——`ParseMultipartForm` 以非 `MaxBytesError` 失败时,handler 落到 kind switch 并返回误导性的 400「kind must be bundle or cover」,且**零测试覆盖**(五个既有上传测试全发合法 multipart)。测试先行 RED、修复、再 mutation 验证(回退→红、恢复→绿、`porcelain=0`)。第二项审计发现——bundle-key 路由公开——一度被疑为漏洞,但**被 grep 推翻**:六处既有钉桩早已断言它,故只加了决策记录注释(零行为变更)。验收:gofmt/vet 净、`go test ./...` 11 包 ok、`ls-remote` MATCH、分支已删。
- Lessons recorded. **`max(a,b) ≥ k` is a vacuous-assertion hot zone, and a guard fix must be verified in both directions.** The P13 scrim-separation guard went through two reversals: the original spec text (`ink vs scrim ≥ 3` in both themes) would false-red the light theme (light `ink vs scrim` measures 1.07 — two darks that don't separate), and the controller's first correction, `max(fill, border) ≥ 3`, is **mathematically vacuous** — because the two sides are complementary, `max(cr(paper,c), cr(ink,c)) ≥ √cr(paper,ink) = 4.0621 > 3` for *any* scrim colour (brute-forced over 50653 samples), so whitening the light scrim — exactly the wash-out the token exists to prevent — left the fill side at 1.1165 while `max()` read 18.42 and the guard stayed green. Task review caught it. The first correction had verified only the false-red direction and shipped a never-red assertion; the same mistake recurred in the same batch when an alpha fallback was checked only against current Chromium output, not the CSS Color 4 forms it claims to support (percentage and exponential alpha parsed as 80 and 1). **Corollary, now in the spec: fix a guard by proving both no false-red on legal values and no false-green under mutation.** (2) **Tailwind v4 scans every source file including `.test.ts`/`.spec.ts`** — a class-name literal in a test comment is treated as a candidate and burns a dead utility into the artifact. The implementer hit this and fixed one instance by string concatenation; the controller reintroduced it eight minutes later in its own adjudication commit (hash `C7966Gm-`→`CV7wAyTH`, `var(--color-*)` 75→76), caught only by rebuilding. Now spec §8-5b. (3) **A universal claim needs a universal grep.** The spec's "accent is the only place using it as a background / becomes a pure non-text token after migration" was false: `runtime/host/GameHost.vue` has three live `bg-accent` references, and both the spike-0 grep and the `noHardcodedColor` guard covered only `app/` while `runtime/` sits *beside* it. That blind spot produced two layers of damage — a false spec fact and a real uncovered WCAG violation (`paper on accent` = 3.2590 < 4.5 in light, pre-existing since P9-B and unchanged by P13). The guard's scan root now includes `runtime/`, the spec claim is corrected, and the violation is logged to the a11y polish batch rather than silently dropped.
- 教训入账。**`max(a,b) ≥ k` 是恒真断言高发区,而修守卫必须两个方向都验。** P13 的 scrim 分离守卫经历两次反转:spec 原文(`ink vs scrim ≥ 3` 两主题)会让亮色腿误红(亮色 `ink vs scrim` 实测 1.07——两个深色互不分离),而控制者的第一次纠正 `max(填充侧, 边框侧) ≥ 3` **数学恒真**——因两侧互补,对*任意* scrim 色都有 `max(cr(paper,c), cr(ink,c)) ≥ √cr(paper,ink) = 4.0621 > 3`(50653 采样暴力验证),故把亮色 scrim 改纯白(正是该令牌要防的泛白)时填充侧得 1.1165、而 `max()` 读作 18.42、守卫仍绿。任务级审查抓到。第一次纠正**只验了误红方向**就交付了一个永不断红的断言;同一错误在同批重演——alpha 兜底分支只对着当前 Chromium 输出验,没验它声称支持的 CSS Color 4 形态(百分比与指数 alpha 被解析成 80 和 1)。**推论,已写进 spec:修守卫必须同时证明对合法值不误红、且在 mutation 下不误绿。**(2)**Tailwind v4 扫描全部源文件含 `.test.ts`/`.spec.ts`**——测试注释里的类名字面量会被当候选、把死 utility 烧进产物。实现者撞到并用字符串拼接修了一处;控制者八分钟后在自己的裁定 commit 里重新引入(哈希 `C7966Gm-`→`CV7wAyTH`、`var(--color-*)` 75→76),只靠重建才抓出。现为 spec §8-5b。(3)**全称声明需要全称 grep 支撑。** spec 那句「accent 是唯一把它当背景用的地方 / 迁移后成纯非文本令牌」为假:`runtime/host/GameHost.vue` 有 3 处 `bg-accent` 活引用,而 spike-0 的 grep 与 `noHardcodedColor` 守卫都只覆盖 `app/`,`runtime/` 却与 `app` **同级**。该盲区造成两层损害——一条假的 spec 事实 + 一条真实且无守卫覆盖的 WCAG 违规(亮色 `paper on accent` = 3.2590 < 4.5,自 P9-B 就存在、P13 未使其变差)。守卫扫描根现已含 `runtime/`、spec 声明已纠正、违规本身登记进可访问性打磨批而非静默丢弃。
## [0.3.5] - 2026-10-02
### Done / 完成