Files
crearte-monorepo/docs/plans/2026-10-03-p15b-bootstrap-dark.md
T
XingfenD 02188eb3e0 docs: re-pin both P15 specs after task-level review adjudication
Twenty-four pinned corrections across the two specs and their plans, every one
traced to a measured finding. Both task-level reviewers overturned claims I had
written into the specs, and I reproduced each against the base tree before
accepting it (git show, never the working tree, which already carries the fix).

P15-A spec gains two sections. T1.4 records the reviewer's candidate guard for
phase-independence: the spec called "do not merge phases four and six" the most
important constraint in the batch, yet its only stated mitigation was the
byte-level comparison, which is one-shot evidence — after the report is filed,
nothing reddens when someone merges them. The reviewer built CG1/CG2/CG3 and
verified them both ways: green on the legal tree, red on four distinct merge and
degeneration forms, all assertion-red rather than compile-red, while the three
shipped guards stayed green throughout. T4 records the adjudication of nine
findings, including two that undercut the guard the batch itself added: a
half-finished dir parameter that redirects which files are counted but not which
are read, so a scan pointed elsewhere can satisfy its own precondition while the
169-line function it exists to catch stays invisible; and a span scanner keyed to
line-initial func, which means a 125-line package-level closure passes all four
gates and all three guards. Also pinned: the gofmt gate in the four-gate recipe.
gofmt -l lists unformatted files and still exits zero, so the recipe everyone ran
reported gofmt_exit=0 as evidence of formatting cleanliness. Implementer and I
shared that recipe, which is why the same blind spot was computed twice and
caught by neither.

Phase intervals are corrected to the real base-tree line numbers. The prior text
told the implementer to move lines 212-213 into a helper; 213 is the slog.Info
the same spec requires to stay in the caller, so following it would have swapped
what moves with what stays. Phases six and seven also overlapped, each claiming
the transaction error mapping. The skeleton now passes a pointer where its own
note eighteen lines later demanded a pointer receiver, and the transaction
helper's signature carries submissionID, which removes the batch's dependence on
a cross-repository equality argument for the only acceptance criterion it has.

P15-B spec corrects the artifact criterion I had backwards. I wrote that deleting
a token should change the var(--color-*) count; that count measures usage, the
token had zero usage across seven utility suffixes, and it stayed at 75. Had it
moved, that would have meant something referenced the token, contradicting the
dead-token premise. Definition-side and usage-side are separate measures and the
spec now says so.

Leg five is re-pinned as critical. It was the sole enforcement point for the
non-reactive injection decision, and it only pinned literal form: two
type-legal variants that establish the dependency elsewhere in the computed body
pass all ten legs with vue-tsc clean, and the reviewer proved by effectScope
evaluation counting that both really do make the iframe src flip on a theme
change, reloading a running game. The narrowing was introduced by the
implementer's own fix, to avoid a trap comment that spells out the forbidden
literal; but comment masking already neutralizes that comment, so the narrowing
was unnecessary and the second line of defense created the gap. Same shape as
the P14 final review finding a hole in the first round's fix.

Also pinned: the rejected deviation five, with the corrected root cause (all four
freeze attempts used page.route, which does not intercept service worker
registration, worker-issued requests, or navigations synthesized by respondWith;
context.route holds the loading screen past 25 seconds); the missing
dark-system-times-light-hash grid that one mutation slipped past all ten source
legs and all five e2e legs simultaneously; the fourth tautology class the spec's
three warnings omitted, where emptying a loop's driving collection makes it
vacuously true; legs seven and eight, which respectively substring-searched a
hex that occurs three times in the file and matched only double-quoted style
attributes; the copyable code block's comment, which now avoids the three
literals that would false-redden a correct implementation, with the annotation
moved outside the block; and three new discipline entries covering untested
method scope, measurement units, and retaining one-off verification scripts.

Every correction was checked by a script asserting both directions — the pinned
text present and the superseded text gone — plus table column integrity, fence
pairing, and code-block cleanliness. Three earlier attempts at this re-pin failed
on my own errors and were fixed before commit; the working tree was never left in
a half-edited state.
2026-10-03 19:48:48 +08:00

15 KiB
Raw Blame History

P15-B 实现计划:bootstrap 加载屏暗色 + 对比度守卫补钉 + 死令牌清理

  • spec(权威):docs/specs/2026-10-03-p15b-bootstrap-dark-design.md
  • 取证账本:crearte/.superpowers/sdd-p15/survey.md(§2/§3/§5/§7/§8 全部实测;含"叉积当违规清单"与"三元互斥分支当同元素共现"两次自我纠错)
  • 仓:crearte(仅此一个)· base f823195(master)· 分支 feat/p15b-bootstrap-dark-and-guard-pins

    ⚠️ 分支前缀用 feat/(AGENTS.md 只允许 {feat|fix|docs|chore}/)。本批含用户可感知的暗色改动 → feat/;若实现者认为纯守卫部分占主体,也不得改用 refactor/(不在允许集内)。 ⚠️ inner repo 红线:不得在 master 上提交。git commit 前先 git branch --show-current 确认。

  • 性质:用户可见改动(加载屏暗色)+ 守卫补强 + 死代码清理,三者一批(spec D-A…D-H)
  • 目标版本:crearte 0.27.0 · wrapper 0.3.8
  • 派发:一个实现者子代理做完 T1→T4(所有任务同动 crearte 单一工作树与 git index,sdd-parallel-dispatch §1 一仓一写者)
  • 并行:P15-A 在 crearte-server 仓同时进行 → 不得触碰 crearte-server/、crearte-deploy/、wrapper 的任何文件

spec 与本计划冲突时 spec 赢。spec 全文(§0–§8)必读,尤其 §1.1 的 🔴 架构陷阱(响应式注入会让运行中的游戏被重载)、§1.3 的连带面表(5 文件)、§1.4(为何不能扩 noHardcodedColor)、§2.1 被否方案 A–F、§5-T1 的 10 腿清单 + ⚠️ 恒真审视三条、§8 纪律 12 条(前端 4 条加粗)。


关键路径约定(与后端不同,勿照搬 P15-A)

  • 前端源码与 package.json 都在 crearte/src/,不在仓根。所有 npx/npm 命令须 cd crearte/src。(P15 取证期控制者两次因 cd 层级写错路径而拿到假结果:cd 仓根却写 app/...(真根 src/app/...)致 grep 全失败输出 bg-surface=0;ls ../e2e 猜错致空输出。任何"零命中"结论都要先证明扫描范围非空。)
  • 测试在宿主机跑 npx vitest run,不进容器(P13 plan 第 58 行同款先例:npx vitest run app/lib/tableOverflow.test.ts app/lib/tableScope.test.ts)。crearte/src/node_modules 已装(232M)。AGENTS.md 的"一切在容器内"针对的是 compose 栈与应用服务,不针对前端单测;e2e 需要浏览器,同样在宿主机跑。
  • vitest 环境是 node(src/vite.config.ts 的 test 段只有 exclude,无 environment 键)→ 源码级守卫可用 fileURLToPath(new URL('../..', import.meta.url)) 读文件。P13 教训:happy-dom 下该写法抛 ERR_INVALID_URL_SCHEME;本批新增守卫不得引入需要 DOM 的断言。

任务表

任务 交付物 要求 完成判据
T1 app/lib/bootstrapTheme.test.ts(新增) RED 先行(spec §5-T1)。10 腿:①pre-paint 脚本在 <style> 与 <body> 之前 ②判定序 hash→prefers→light(indexOf 比较,先断言三者都 > -1)③白名单校验 hash theme(只接受 light/dark)④IIFE 且只用 var ⑤**GameHost.vue 注入的是 effectiveTheme() 而非 useTheme().theme.value** ⑥bootstrap 暗色块六个 hex 与 main.css 暗色调色板逐值一致(字符串相等,不得写成 max(a,b) ≥ k)⑦bootstrap 亮色六个 hex 未被改动 ⑧bootstrap/index.html 内不存在 inline style 里的 color: ⑨前提检查(读到 ≥3 个文件且 bootstrap html 长度 >500)⑩AA_PAIRS 含 ['ink','surface'] 且 REQUIRED_KEYS 不含 info、长度 toBe(11) RED 输出逐字存证 .superpowers/sdd-p15b/impl-evidence/t1-red.txt,并逐腿标注红/绿:腿 1/2/5/6/8/10 须红,腿 7/9 应当已绿(它们是"不得回退"钉桩,不是新功能断言——spec §5-T1 明写这是有意的)
T2 app/lib/contrast.test.ts(改)+ app/styles/main.css(改) 按 spec §3.2/§3.3:AA_PAIRS 插入 ['ink','surface','卡片/表格/表单底(bg-surface 62 处,58 靠 body 继承 text-ink)亮18.42/暗14.85'](不得改其它 10 对);删 main.css:12(亮)与:53(暗) 两行 --color-info;REQUIRED_KEYS 删 'info',;四处 "12 令牌" 文案改 "11 令牌"(:47,74,119,121)。⚠️ LEGACY_KEYS(:197)不动(九键不含 info,已实测) npx vitest run 80 文件 / 688+N 全绿(N = T1 腿数 ≥10);--color-info 全仓命中 0;AA_PAIRS 11 对;REQUIRED_KEYS 11 项;LEGACY_KEYS 9 项不动
T3 bootstrap/index.html(改)+ runtime/host/adapters.ts(改)+ runtime/host/GameHost.vue(改) 按 spec §3.1(a)–(e):head 内 <style> 之前插 pre-paint 脚本(逐字对齐 spec 给的代码块,含 CSP 注释与 catch 兜底);<style> 加 html[data-theme="dark"] 覆盖块(亮色值逐字不变,color-scheme: dark 随块声明一次);删两处 inline color(:25 的 #a3a3a3、:27 的 #f87171,各保留 font-size);resolveRuntimeTargets 的 opts 加可选键 theme?: 'light' | 'dark',仅当存在时才写 fragment.theme;GameHost.vue 注入 effectiveTheme() 并写明陷阱注释 npx vitest run 全绿;npm run typecheck(vue-tsc --noEmit)零错;adapters.test.ts 4 处既有调用零修改仍绿(验证 theme 是可选键);既有 *.test.ts 修改数 = 1(仅 contrast.test.ts,须在报告显式声明)
T4 e2e / 产物验证 + mutation 自查 ⚠️ RE-PIN 2026-10-03(审查裁定后):偏离 5 已驳回——端到端腿可确定性化,用 context.route(不是 page.route) + inert sw.js(page.route 拦不到 SW 注册请求 / SW 发起的请求 / 被 SW respondWith 合成的导航,实测命中 0);断言须含 snap.url === 父侧 iframe src(因果链闭合);另加行为腿(游戏页切主题 → iframe src 不变,D-B 的唯一行为级防线)与反方向格(子侧 [系统暗色] × [hash=light] → 期望 light,H1/H3 的唯一鉴别格)。产物验证必须在 e2e 之后重跑生产 npm run build 之上做,并用 grep -c -F 'localhost:4173' = 0 证明量的是生产构建(控制者当日量了 e2e 残留 → 7069 vs 7056 的假矛盾)。计数须同时给 raw 与屏蔽注释后两个数、字节用 wc -c(详见 spec §5-T4.3/§5-T4.4 与 adjudication.md §三) 端到端腿 + 行为腿 + 反方向格三类都跑绿;mutation 复验须跑审查者自设的 G1/G2/H1/H3/F1/F2/H4 七条(spec 的 (a)–(j) 对这七条全部无牙)+ 全部对照组 + 合法树

验收(控制者独立复跑,不采信自报)

测试与构建

cd crearte/src
npx vitest run                      # 基线 79 文件 / 688 测试 → 目标 80 / 688+N
npm run typecheck                   # vue-tsc --noEmit,零错
npm run build                       # 含 vue-tsc + vite build + build-runtime.mjs
npm run e2e                         # 基线 102+1skip(dark.spec.ts 10 腿)

基线(spec §0,已实测 2026-10-03 13:07):vitest 79 文件 / 688 测试全绿,Duration 44.83s——与 P13 交付值逐字相同(P14 未触及前端)。e2e 102+1skip 是 P13 交付值。

⚠️ npm run build 含 vue-tsc --noEmit,会类型检查 e2e/*.spec.ts;spike 期间若只想快速验产物可用 npx vite build(P13 纪律 5 的同款做法),但交付验收必须跑完整 npm run build。

结构指标(spec §5 表,交付时须报实测数字)

指标 基线 目标
vitest 文件数 / 测试数 79 / 688 80 / 688+N(N ≥ 10)
既有 *.test.ts 修改文件数 — 1(仅 contrast.test.ts,仅因 "12→11 令牌" 文案;须在报告显式声明)
AA_PAIRS 对数 10 11
REQUIRED_KEYS 项数 12 11
LEGACY_KEYS 项数 9 9(不动)
--color-info 全仓命中 2(两处定义) 0
bootstrap 内 localStorage / prefers-color-scheme / data-theme 命中 0 / 0 / 0 0 / ≥1 / ≥1(localStorage 仍须为 0:源隔离,spec D-A)
bootstrap 内 inline style 的 color: 数 2 0
typecheck / build 0 / 0 0 / 0
主 e2e 102+1skip ≥102+1skip(新增腿另计)

mutation 抽查(spec §5-T1 的 (a)–(j),控制者独立复现,不复用实现者结果)

十条,每条测"预期红 + 其余绿 + 恢复证明":

# mutation 预期
(a) bootstrap 的 pre-paint <script> 移到 <style> 之后 腿 1 红
(b) 交换 hash theme 与 prefers-color-scheme 的判定先后 腿 2 红
(c) hash theme 校验放宽成 hashTheme ? hashTheme : … 腿 3 红
(d) GameHost.vue 改用 useTheme().theme.value 腿 5 红(这条是 spec D-B 的牙:响应式注入会让主题切换重载运行中的游戏)
(e) 改 bootstrap 暗色的一个 hex 腿 6 红
(f) 加回 style="color:#a3a3a3" 腿 8 红
(g) 从 AA_PAIRS 删掉补钉的 ['ink','surface'] 腿 10 红
(h) 把 info 加回 REQUIRED_KEYS 腿 10 红
(i) 守卫的文件路径改成不存在的文件 腿 9 红(防"读 0 文件报 0 违规"的假信心)
(j) 删掉 main.css 的暗色块(模拟 P13 成果回退) 腿 6 或 contrast.test.ts 的暗色腿红

⚠️ restore() 只回滚 mutation 触及的文件,绝不用 git checkout -- .(P14 控制者第 24 次自伤:全量回滚抹掉了未提交的守卫编辑,导致后四轮全在测没有修复的树、汇总报"绿=3")。每轮恢复后核 git diff HEAD --exit-code -- <file> 空 + 守卫文件腿数不变。

⚠️ 写守卫时注释里不要出现完整的 bg-[#…] / text-[#…] 字面量(spec §8-8:Tailwind v4 扫描全部源文件含 .test.ts,注释里的类名字面量会被当候选、烧死 utility 进产物。P13 实现者用拼接修对一处,控制者八分钟后在自己裁定 commit 里重新引入,靠重建抓出)。

产物验证(P13 教训:源码级守卫绿 ≠ 产物正确)

  • 必须验真实 dist/,不得用运行时注入或 APPLY.toString()+new Function 序列化注入替代(P13 控制者勘查期两次栽在这两种无效手法上)
  • 选择器/字符串 grep 用 grep -F,不手写反斜杠转义(P13:.backdrop\:bg-scrim 手写转义对 minified 产物假阴性)
  • 带引号的 grep 模式对 minified 产物也假阴性(grep 'bg-[#]' 匹配不到压缩形态)

边界(不做,spec §4)

  • 不动 themeBootstrap.test.ts(主应用 pre-paint 守卫 7 腿)——若某腿因本批变红,停下来报告
  • 不扩 noHardcodedColor.test.ts 的扫描根(spec §1.4 + 被否方案 D:candidates() 只收 .vue、maskNonTemplate() 会空白掉 <style> 块 → 扩根扫不到任何东西,只会制造"已覆盖"的假信心)
  • 不动 useTheme.ts(effectiveTheme() 已够用;改它会波及主应用 10 腿 e2e)
  • 不动 src/index.html(主应用入口,P13 交付)
  • 不改 P13 的 CHANGELOG 0.26.0 条目与 P13 spec 原文(spec D-C:改写会让"当时交付了什么"失真;改为新条目说明 + P13 spec 加 ⚠️ RE-PIN 标注块,那是控制者的记账动作)
  • 不修 GameHost 亮色徽标 WCAG 3.26(spec §4 挂账 + 被否方案 F:属设计决策,显而易见的一行修法已实测证伪——亮色 accent vs accent-ink 仅 1.4943,徽标与「加载失败」状态点同屏共存,改完会把两个语义压成一个视觉信号;ROADMAP 9965fd7 已登记该约束)
  • 不做第三态"恢复跟随系统"(P13 spike 3 已证伪:header 在 @320px 最坏格无像素容纳带标签控件)
  • 不新增任何令牌(只删 --color-info)
  • 不碰 crearte-server、crearte-deploy、wrapper 的 docs/(实现者红线;记账由控制者做)

记账(控制者做,实现者禁动 docs/)

  • crearte/docs/CHANGELOG.md 新增 ## [0.27.0] - 2026-10-03(插 ## [0.26.0] 前)
  • wrapper docs/CHANGELOG.md 新增 ## [0.3.8] - 2026-10-03(插 ## [0.3.7] 前),小节 ### Done / 完成;与 P15-A 合并为一条还是分两条按合并时序定
  • docs/ROADMAP.md:第三波表新增 P15-B 行 + 文档索引表新增一行(哈希引 merge commit);挂账里删掉「死令牌 --color-info」(已处置)、新增「bootstrap 内联脚本的 CSP nonce」(与主应用同批)
  • P13 spec 加 ⚠️ RE-PIN 标注块(spec D-C:12→11 令牌),不改写原文
  • 格式:同条目英文行紧跟中文行、无空行;不同条目空一行;小节标题双语
  • 纪律:显式 git add <file>,禁 git add -A;wrapper 的 IDENTITY.md/SOUL.md/USER.md 三个未跟踪文件绝不 stage
  • 合并:CHANGELOG 改动提交到特性分支 → git checkout master → git merge --no-ff → 记 merge 哈希 → 推送(禁管道)→ git ls-remote origin master 非空对账(-z 检查,防空变量假 MATCH)→ 删分支

审查阶梯(P11–P14 惯例,不可省)

  1. 实现者自报 + 证据落盘(RED 逐腿标注、mutation 十条、产物验证)
  2. 控制者独立核实(全部自己跑,不采信自报数字)
  3. 任务级审查者(只读,自设 mutation 不照抄 spec 的 (a)–(j),审守卫恒真/恒假、spec 自身问题、特别是腿 5 这条架构陷阱断言是否真能防住响应式注入)
    • P13 先例:10 条自设 mutation 挖出 5 条 findings,M1 抓到控制者自己写错两次的恒真守卫(max(a,b) ≥ 3 互补下界 4.0621)
    • P14 先例:抓到 spec D-J 的守卫目的无断言覆盖 + spec 里 positive control 的绕行路径 + 控制者行数口径错(split vs wc -l)
  4. 控制者逐条裁定(sdd-pre-merge-review §3:未裁定的 note 阻塞合并,"PASS with notes" 不等于门过了)
  5. 全分支终审者(只读;spec 全文自洽性 + 审控制者的裁定工作 + 独立复现 mutation)
    • P11 先例:终审裁定过「需修复后合并」→ 不是橡皮章
    • P14 先例:终审在第一轮修复自身里找到洞(匿名接收者 func (*ContentService) M() 绕过源码扫描),并推翻控制者两处已入库的全称声明
  6. spec re-pin(裁定后一次性批量改,勿在审查者读 spec 期间改=移动靶);代码改了 spec 必须同步(P14:正则字面量写进 spec,改代码后 spec 立刻不一致,构成"需修复后合并"的同类形态)