The P15-B branch review returned "needs fixing before merge" on a critical gap in the guard layer. The code side is with a second fix-forward; this commit closes the document side, which is the controller's. F-FINAL-4 - the leg-8 cell carried a one-line regex that the controller wrote into the spec during re-pin. It was worse than the version in the adjudication it copied: 4 of 7 attribute forms fail rather than 2, because markdown escaping turned the alternation bar into a literal bar and `'[^']*'` was mistyped as `'[^'*]`. The controller had checked that every table row has the same number of pipes as the header, but never checked that the escaped code was still the code it came from. The cell now describes the implemented approach (extract every style attribute value in all three quoting forms, strip quotes, test each for color:) and deliberately carries no regex literal, with the reason stated: an escaped bar inside a table cell is a literal character in the regex. Read the code entity for the pattern. §6-1 - leg 9 was never re-pinned: the spec still said "length > 500" while the code says 4000 plus three structural preconditions. Corrected, with the unit spelled out (source characters 4423, not the built artifact's 7056/7588). §6-2 - "the only discriminating grid" survived re-pin in four places; the review measured two legs reddening on a real decision-order defect. Narrowed to "the only discriminating grid on the child-side direct-visit path" here and in the plan. The two occurrences in code comments belong to the fix-forward. §6-3/§6-4/§6-5 - the §1.2 count now states which tree it came from (base 66, HEAD 67, the extra one being this batch's own note text quoting the figure); the §4 boundary table lists the e2e spec it had omitted; D-G says "form aligns" rather than "aligns verbatim", since the script bodies differ by design (the main app has one more level, 16 token-level differences). F-FINAL-1 - two claims already committed are corrected rather than deleted. The §3.1(a) annotation and the §7 risk row both rested on "maskSourceComments() neutralises the trap comment, so the guard does not depend on the implementer's wording discipline". That is true for the file in question, but only because the trap comment happens to use `//`: the function's HTML-comment branch compared a 2-character slice against a 4-character literal and never ran. Merely rewording a comment reddened up to four legs while the e2e suite stayed green. §8 gains the census case for discipline 15 (an unlanded census script is why the 48-versus-47 discrepancy is still untraceable), plus disciplines 16 and 17: every evidence file a report cites must be ls-checked before delivery, and executable details in a spec must be grepped from the code entity or recomputed, never hand-written. Discipline 17 records this batch's three instances, one of them a contrast ratio cited twice by two documents and recomputed by nobody (3.0269; the actual value is 2.5519, which no grey background produces). Post-write checks: 25/25 assertions, 11 table blocks with 0 column anomalies, 16 code fences paired, 25 headings with no duplicates, copyable-block cleanliness scanned across 8 blocks.
15 KiB
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(仅此一个)· basef823195(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 在子侧直访路径上的唯一鉴别格;端到端亮色格是第二个鉴别格(RE-PIN 2026-10-04))。产物验证必须在 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:属设计决策,显而易见的一行修法已实测证伪——亮色
accentvsaccent-ink仅 1.4943,徽标与「加载失败」状态点同屏共存,改完会把两个语义压成一个视觉信号;ROADMAP9965fd7已登记该约束) - 不做第三态"恢复跟随系统"(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 惯例,不可省)
- 实现者自报 + 证据落盘(RED 逐腿标注、mutation 十条、产物验证)
- 控制者独立核实(全部自己跑,不采信自报数字)
- 任务级审查者(只读,自设 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 的绕行路径 + 控制者行数口径错(
splitvswc -l)
- P13 先例:10 条自设 mutation 挖出 5 条 findings,M1 抓到控制者自己写错两次的恒真守卫(
- 控制者逐条裁定(
sdd-pre-merge-review§3:未裁定的 note 阻塞合并,"PASS with notes" 不等于门过了) - 全分支终审者(只读;spec 全文自洽性 + 审控制者的裁定工作 + 独立复现 mutation)
- P11 先例:终审裁定过「需修复后合并」→ 不是橡皮章
- P14 先例:终审在第一轮修复自身里找到洞(匿名接收者
func (*ContentService) M()绕过源码扫描),并推翻控制者两处已入库的全称声明
- spec re-pin(裁定后一次性批量改,勿在审查者读 spec 期间改=移动靶);代码改了 spec 必须同步(P14:正则字面量写进 spec,改代码后 spec 立刻不一致,构成"需修复后合并"的同类形态)