Files
crearte-monorepo/docs/ROADMAP.md
T
XingfenD 163b0d703a docs: record P14 as delivered (wrapper 0.3.7, ROADMAP row + backlog)
The ContentService split landed in crearte-server 0.18.0 as merge fe810dd off
merge-base dbf7fe5. The third-wave table gains the P14 row and the doc index
marks it executed against the merge commit, per the P12/P13 convention of citing
the merge rather than the accounting commit.

The row records the batch's actual result, which is not the split but the two
review rounds that each caught the controller's own error: the task review found
that the guard purpose the spec states was covered by no assertion at all and
that the spec's own positive control had a bypass; the branch review then found a
hole in the first round's fix, where the source-scan pattern required a named
receiver and so let an unnamed-receiver shadow pass all seven assertions while
the shadow was effective. It also overturned two universal claims I had already
committed to the spec, both reproduced before acting on them.

Backlog gains the 169-line Approve function with its line range relocated after
the split (the base-tree mapping is void), plus six smaller items. It also logs a
dark-mode gap found while surveying the next batch: P13's token work covered only
the src/index.html entry and missed the second Vite entry, bootstrap, whose
loading screen hardcodes light values, so dark-theme users see a white flash on
every virtual game launch.
2026-10-03 12:37:10 +08:00

77 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# crearte 路线图 / Roadmap
跨三个仓库(`crearte` 前端 / `crearte-server` 后端 / `crearte-deploy` 编排)的子项目路线图与索引,维护在 monorepo wrapper(本仓库)。
- **版本 v0**:2026-09-29 与维护者确认的分解与顺序。
- **现状基线**:crearte 0.13.0 / crearte-server 0.11.0 / crearte-deploy 0.3.1。
## 分解原则
- 按「依赖关系 + 是否阻塞上线」排序;每个子项目的规模控制在一份 spec 能装下。
- 每个子项目独立走 **spec → 实现计划 → 实现 → 验证** 的循环。
- 隐藏复杂度出现时升级路径(拆出新子项目),不硬塞进当前子项目。
## 第一波:上线底座(运维 / 可靠性,顺序敏感)
| # | 子项目 | 内容 | 涉及仓库 | 依赖 | 状态 |
|---|--------|------|----------|------|------|
| P0 | 已知缺陷清理 | ① CORS `Access-Control-Allow-Headers` 补 `If-None-Match`(解锁 e2e:stack Step5:revoke 410 + 降级)② 清理 compose 配置漂移(`BUNDLE_KEY_STORE` 环境变量、`bundle-keys-*` 卷——密钥自 server 0.6.0 起在 Postgres)③ `crearte-deploy` CHANGELOG 补账 | server, deploy | 无 | **完成**(server `a8ea755` / deploy `27044be`,2026-09-29 合并推送) |
| P1 | prod 写侧可用 | prod 栈补对象存储(prod MinIO 或外部 S3)+ `api-prod` 配 `STORAGE_S3_*` / `GAMES_BASE_DOMAIN` / `CORS_ALLOWED_ORIGINS`;密码默认值、TLS 起步。现状:prod 无对象存储时写侧路由(上传/投稿/审核/预览)不注册,生产只读 | deploy(少量 server) | P0 | **完成**(crearte `63bb96d` / deploy `4d53d58`,2026-09-29 合并推送;TLS 与真域名留清单,见 spec §6) |
| P2 | CI + 测试基线 | 后端 / 部署仓建 CI(gofmt/vet/test + `TEST_DATABASE_URL` 集成层 + e2e:stack 全链路);与前端已有 `validate.yml` 对齐。现状:后端/部署仓无 CI,Postgres 集成测试无 `TEST_DATABASE_URL` 时静默 skip | 三仓 | P0(e2e 依赖其修复) | **完成**(server `7b97108` / crearte `711b6de` / deploy `522545d`,2026-09-30 合并推送;e2e-stack 宿主仓修订见 spec §2.2;首跑拓出 serve-runtime 子域缺陷已修(`5e0fb2e`);Actions 启用与分支保护为 owner 手动尾巴,见 spec §2.4) |
| P3 | 备份 + 可观测 | `pg_dump` 定时备份 + MinIO 数据保护 + 恢复演练 runbook;结构化 / 请求日志 + `/metrics`;限流器单实例问题记录权衡 | server, deploy | P1(拓扑定型) | **完成**(2026-10-01 历史重放合并推送:deploy `12f7c10` 0.6.0(backup.sh/restore-drill.sh/BACKUP-RUNBOOK/CI dry-run 腿/LOG_FORMAT-LOG_LEVEL 透传)+ server `d627e12` 0.16.0(slog 机械迁移 30 处含 P7/P8b2 新面、RequestLogger 中间件、手写 /metrics http+go+pgpool 三族、限流单实例权衡文档)。P3M 合流后终验全绿:容器四连+真库 `-p 1 -count=1`(skip 守卫 0,含注销链集成)+实栈 metrics 冒烟(计数族/nomatch/双跳过/slog JSON/pgpool 族/go 族全中)。原 P3 提交 `7fcfe0e`/`6ffd1ee`/`93a3c70` 在回滚窗口脱离 master,经 cherry-pick 重放字节级回归,P7/P8b2 新面补入同规则迁移。本机实栈备份演练腿未跑(prod 栈未起),留 owner 按 runbook 执行) |
## 第二波:产品价值(可与第一波部分并行)
| # | 子项目 | 内容 | 涉及仓库 | 依赖 | 状态 |
|---|--------|------|----------|------|------|
| P4 | 作者主页 | `/users/:user` 聚合页(该作者全部已上架作品,前端按命名空间过滤,**零后端改动**),按最新上架排序;顺带 `/games/:user/:slug` 面包屑与作者名互链。spec:`docs/specs/2026-09-29-author-page-design.md` | crearte(前端) | 无强依赖,可提前并行 | **完成**(crearte `f12cbf1`,2026-09-29 合并推送) |
| P5 | 收藏 / 评分 | 新数据表 + 用户态 API + 目录/详情页 UI;产品线里最大的一块 | 三仓 | 建议 P2 先落地 | **完成**(server `e3da246` / crearte `eb5fbed`,2026-09-30 合并推送;范围修订 deploy 零改动,见 spec) |
| P6 | hosted 作品投稿(方案 A 自托管内嵌) | 作者把游戏部署在别处,投稿填 https URL,站内沙箱 iframe 播放;播放端机件已全存在,打通的是入口:后端 D-B runtime 不可变 + D-C hosted 禁 bundle 两护栏,前端表单第三档+预填解锁 | 三仓 | owner 已拍 A | **完成**(owner 拍板方案 A:只存 URL、站内沙箱 iframe 播;server `3e11446` 0.13.0 / crearte `fee5f3b` 0.19.0,2026-09-30 合并推送。勘查正误:Approve 落库映射本就全(PayloadToWork import.go:39),无需动;真实缺口只在校验两处——D-B metadata_change 禁改 runtime(根治前端拒预填的降级风险)+ D-C hosted 禁带 bundle。前端:类型三档/表单第三 radio「自托管内嵌」/hostedUrl https+fallback 默认 external 镜像校验/预填解锁;播放端零改动(GameHost/useGameFrame 既有)。双路审查 PASS with notes;六腿全绿:容器四连、空库 -count=1 集成 151 PASS 零 FAIL(含 TestHostedPublishChainOnPostgres 真 HTTP 全链)、真栈 full-loop 分支后端 1 passed、vitest 492/typecheck/admin-flow 6(首轮 1 passed 系栈腿争用假警报,复跑坐实)/主套件 69。T5:deploy 模板层无主站 frame-src 下发(仅两处属运行时子域页 CSP),零改动记录在案。遗留仅剩:new_version 对 hosted 仅 server 拦(前端无硬校验,spec 决策)。D-D(审核面 hostedUrl/fallback 展示行)当日补交:控制者直改维护者小补丁,分支 feat/p6-dd-adminrow(Task 2 任务书漏列该条;审查按任务书对照未扯出——教训:审查依据应是 spec §3 全量),纯文本无锚点+降级中文标签,四腿复跑 vitest 492/typecheck/admin-flow 7/主套件 70 全 RC=0,crearte 合主 `9bd9372`(0.19.1) |
## 第三波:治理与长尾(随时可插队)
| # | 子项目 | 内容 | 状态 |
|---|--------|------|------|
| P7 | 管理后台增强 | 用户列表 / 角色管理 UI(替代 CLI `user set-role`)+ 审计日志 | **完成**(server `ca24805` 0.12.0 / crearte `900f13a` 0.18.0,2026-09-30 合并推送。audit_log 表 0010 + 组中间件统一记录(owner 定稿:admin 路由收敛为组,Full-Path 模板入史,GET 也记,失败尝试照记,未遂 401/403 不记);用户列表/PATCH 角色/审计查询三端点;唯一 admin 降级 409 护栏 CLI/API 双覆盖;前端 /admin/users + /admin/audit 两页,降级确认双要点文案;本机六腿验收全绿:容器四连+fresh-DB `-count=1` 10 ok 零 FAIL 零 skip、废 token 端到端 401、护栏矩阵(非唯一降 OK/唯一降 CLI 拒+API 409)、审计流水含 409 失败尝试、真栈 full-loop 分支后端 1 passed×2、vitest 488/typecheck/admin-flow 6/主套件 68) |
| P8 | 长尾打包 | 权限开关 UI 全量(`inlineStyle/wasm/coop/fullscreen/gamepad`)、后端 triage 小项(`games.Detail` 400 细分、admin 路由 slug 校验、approve 同名竞态测试)、账号注销、静态兜底目录、CHANGELOG 模板文案 | **第一批完成**(②⑤①,2026-10-01 合并推送:server `921443a` 0.14.0 / crearte `70ba10c` 0.20.0。容器四连+空库 `-count=1` 集成全绿(新竞态测试含,skip 守卫 0)+`-race` 竞态腿 PASS;curl 冒烟全中:detail 400 细分 user:/slug: 且双非法 user 优先、admin 四路非法 400 `invalid work id`、合法格式不存在仍 404;FE vitest 499/typecheck/admin-flow 7/主套件 70+1skip;双审 PASS(server PASS with notes—唯一 note 双非法优先级缺钉桩,已以 `ee5b960` 测试断言补上);deploy 零改动。**第二批完成(③账号注销+④静态兜底目录)**(2026-10-01 合并推送:server `12b8ffb` 0.15.0(迁移 0011 墓碑化删行、DELETE /api/auth/account 对密码注销、唯一 admin 409、user delete CLI、catalog export 出 schemaVersion=2 目录)+ crearte `7d5f338` 0.21.0(/account 危险区+deleteAccount 客户端+AdminUsersView 三码中文化钉桩)+。真库注销链集成+分支真产物 curl 冒烟 14 项全 PASS(墓碑行/邮箱复用/409/旧 token 作废/CLI delete/export schema);FE vitest 512/typecheck/主套件 71+1skip/noauth 4,双审 PASS with notes(前端 2 条次要已钉桩 `7a54152`,server 裁决项:LOG_LEVEL 大小写不一致—deferred)。余 ④的 render 消费端接入按需(feed 已交付)**,CHANGELOG 头部去模板腔两仓已随第一批完成、wrapper 头部不在 spec 范围 |
| P9 | UX 增强(第一批:浏览器反馈包) | 路由级 `document.title` 两段式(router `afterEach` 按 19 个 name 全表设静态基线 + 四个数据页 watch 数据后精化)+ 作品页 `meta description`(截 120 码点、默认回落)+ `StatePanel` 骨架对齐(cards 3→6 张、新增 `lines` 变体六页八实例)。零后端/部署改动 | **第一批完成**(2026-10-01 合并推送:crearte `26cd625` 0.22.0(`a196603`+`3f1de38`+审查补钉 `e9e4c56`)。五腿全绿:vitest **551**(基线 512+新增 39)/ vue-tsc 零错 / build OK / 主套件 72+1skip(含新增标题 e2e 腿,`landing.spec.ts` 首条兼容钉一字未动)/ noauth 4。独立审查 PASS with notes:ISSUE-1(离开作品页 description 未回落默认,spec D-C 子项)与 ISSUE-2(「七页」实为六页文案)均已在合主前补钉+补测。B 效率可访问(已于 P9-B 交付)/ C 暗色模式(已于 P13 交付)两批挂账已清偿) |
| P10 | UX 增强(第一批:日常交互包) | toast 体系(useToast 单例 store + ToastHost 挂 App.vue)+ 投稿表单脏数据离开守卫(onBeforeRouteLeave confirm + beforeunload)+ 作品标签可点(GameView/GameCard tags → /games?tag= RouterLink)+ 作品页复制链接分享(clipboard + toast)+ 最近玩过条带(recent.ts localStorage + GameHost ready 记录 + LandingView 条带)+ 页头下拉外点/Esc 关闭。零后端/部署改动 | **第一批完成**(2026-10-01 合并推送:crearte `ee4edf7` 0.23.0,merge-base `26cd625`。六任务各经实现→任务级审查→裁定,另加全分支终审(spec §3/§4/§5 全量对照)。五腿全绿:vitest **601**(基线 551+新增 50)/ vue-tsc 零错 / build OK / 主套件 **77+1skip**(基线 72+1skip)/ noauth 4。审查补钉四处:`fc0270e`(AppHeader 卸载清理 spy)、`dbf4bf0`(hosted 重开 sync-flush 再记,spec D-H re-pin)、`bb9bb35`(e2e 夹具修正——spec 测试计划自身误选 external-runtime 夹具 2048/a-dark-room,换 abs-paths/storage)、`836b2fb`(AppHeader 路由收起回归,spec §5 明列项)。终审裁定「需修复后合并」两条件均已闭合。B 效率可访问(已于 P9-B)/ C 暗色模式(已于 P13)已清偿;OG 卡片 / 播放计数等后续批挂账待启动) |
| P9-B | UX 增强(第二批:可访问与键盘效率包) | WCAG AA 色彩对比达标(`--color-success` 加深 + error toast 换 `bg-accent-ink`)+ toast 播报区重构(常驻 sr-only 播报层与视觉层解耦,收口 P10 终审转办项 T1(d)「实时区嵌交互按钮」)+ skip-link(`App.vue` 根首子,`focus:z-[80]`)+ SPA 导航后焦点管理(`focusMain()`,仅路径变化触发、`preventScroll`)+ 管理端表格 24 个 `<th>` 补 `scope="col"`(两处空表头补 sr-only「操作」)+ 五星评分可访问名(逐星「评 N 星」+ 分组 `role="group"` 动态标签 + 字形 `aria-hidden`)。另加两个源码级守卫(`contrast.test.ts` 十一配对对比度、`tableScope.test.ts` 全仓 `<th` 必带 scope)与 `e2e/a11y.spec.ts`。零后端/部署改动 | **第二批完成**(2026-10-01 合并推送:crearte `338acbf` 0.24.0,merge-base `ee4edf7`。五任务(T1-T4 派发 + T5 控制者本人)各经实现→任务级审查→裁定,四次任务级审查全 PASS,另加全分支终审(spec §1-§6 全量对照 + 跨任务集成缝隙 + mutation 抽查)。五腿全绿:vitest **626**(基线 601+新增 25)/ vue-tsc 零错 / build+build-runtime OK / 主套件 **80+1skip**(基线 77+1skip)/ noauth 4。终审独立实跑六腿与控制者数字逐条一致,裁定 APPROVE with notes(零关键/零重要)。波次内两次 spec 层修正:① T1 实现者抓出 spec 初稿 D-I 缺陷(per-toast `role=status` 与文字同时创建,部分读屏不播报首条)→ re-pin D-I′ 为常驻播报区,控制者直修 `f01baae` 并附带钉死饱和态 watch 陷阱(源必须是末尾 id 而非长度,`MAX_VISIBLE=3` 时 3→3 不触发);② 控制者简报两处数量笔误(对比度 3.16 应为 2.83、「19 个 th」是行计数、元素实为 24)均由实现者以 spec/实测为准纠正,spec §1 已同步。打磨批挂账四条(router 升级复看弱断言、撤评语义进可访问名、手动关最新 toast 的播报重念、spec 口径已修)。C 暗色模式已于 P13 交付清偿;OG 卡片 / 播放计数等后续批挂账待启动(「移动端页头」已于 P12 实测证伪删除:nav 内容宽仅 74–158px,320/360/375/412/768/1280px 零横向溢出)) |
| P11 | 服务端安全硬化 | 限流表硬上界(`maxEntries` 失败关闭:满时先过期清理、仍满则对新面孔 429 且不插入,既有键语义逐字不变;闭合缺陷 B「同窗口 map 无界增长」)+ `LOG_LEVEL`/`LOG_FORMAT` 入口层小写化与 trim(闭合 P8 deferred 缺陷 C)+ `TrustedProxies` 空时启动 `slog.Warn`(D-C 大声留痕)+ 两个 compose-SNAT 网关陷阱反面钉桩 + deploy `TRUSTED_PROXIES` **空默认**与 README 专节(真实 prod 按实际反代 IP/网段配)+ nginx `X-Real-IP` 与九处 `nosniff`/`Referrer-Policy`(`always`)。CSP 与 HSTS 有意不做(前者需独立设计批,后者待 TLS 批) | **完成**(2026-10-02 合并推送:server `9da39b6` 0.17.0(merge-base `d627e12`)/ deploy `740958a` 0.7.0(`12f7c10`)/ crearte `d25c497` 0.24.1(`338acbf`)。五任务各经实现→任务级审查→裁定全 PASS(零关键零重要,9 条次要转打磨批),另加全分支终审(qwen3.8-max)裁定「需修复后合并」——N1(WARN hint 竟建议 subnet 信任,即 D-A′ 判定的绕过配置)/N2(spec 五处 subnet 线未随 re-pin 更新)/N3-N5 均已闭合,N6(validate.yml 加 `TRUSTED_PROXIES` 空默认机器守卫)转打磨批。**本批最重要事件:端到端验收抓出设计级错误并 re-pin D-A→D-A′**——原 D-A 信任整个 compose 子网,但真实 prod 栈实测:docker 对发布端口做 SNAT,nginx 看到的源恒为网桥网关且网关也在子网内,gin 跳过可信网关后**采纳客户端预置 XFF**(`X-Forwarded-For: 8.8.8.8` → `ip=8.8.8.8`,限流可自选桶绕过);而真实浏览器(无 XFF)在 compose 下恒塔缩到网关——缺陷 A 对合法流量根本修不了(SNAT 固有)。spike_snat 六用例信任矩阵钉死后改为:空信任默认(XFF 整体忽略、无绕过、D-C 告警如实提示单桶)+ subnet 固定回退 + README 重写真实 prod 配法 + server 两个反面钉桩。修正后 prod 真实栈回归五项全中(告警在位、伪造 XFF 解析为 nginx 容器 IP 绝非 8.8.8.8、单桶 429 正常、404 上安全头完好、卷全留存)。验收全链:server gofmt/vet/build 净 + test 11 包 ok + `-race` 12 包 ok(Debian 镜像 CGO=1)+ 真库 db-test 集成 ok(skip 守卫 0);crearte vitest **626**/typecheck 零错/build OK/主 e2e **80+1skip**/noauth 4;终审独立复跑六腿与控制者数字逐条一致。教训入账:隔离 spike 证明组件语义、不证明配置在真实拓扑下正确——安全/网络类修复合并前必须走真实流量路径的端到端验证;验收测错路径(用伪造 XFF 当回归)比不测更危险。CSP / TLS+HSTS 挂账待启动(打磨批已于 P12 部分清偿:P9-B 四条中三条(路由弱断言 / 撤评语义 / 播报重念)与 P11-N5/N6 已闭合;P9-B 第四条「spec 口径」在 P9-B 波次内已修;P11 其余九条次要为纯留档、无可动手改动) |
| P12 | 窄屏溢出修复与打磨批 | 四个实测窄屏溢出缺陷 + 途中发现的一个:**缺口 A** 页头长 `display_name`(60 字为**合法上限**,非敌意构造)撑破全站每个路由(320px **+380px** / 375px **+325px**);**缺口 B** 账号页昵称 dd 逃逸(`ddRight=592`);**缺口 C** 目录排序 select 的 `shrink-0`(`/games` 320px **+3px**,短名短数据也复现);**缺口 D** 五个 admin 表格零 overflow 包裹层(`/admin/users` 320px **+76px**,**短数据也复现**);**缺口 E** 用户下拉菜单逃逸视口(`menu right=485`,由页头修复一并闭合)。另加两层机器守卫(`e2e/responsive.spec.ts` 12 测试零配置改动进 CI 主腿 + `app/lib/tableOverflow.test.ts` 源码级逐表格父元素判定)与三项打磨(P9B-1 路由弱断言 / P9B-2 撤评语义进可访问名 / P9B-4 播报重念)。零新功能、零后端行为变更 | **完成**(2026-10-02 合并推送:crearte `e59a171` 0.25.0(merge-base `d25c497`)/ server `d56c593` 0.17.1(`9da39b6`)/ deploy `529a988` 0.7.1(`740958a`)。派发遵循一仓一写者:T1/T2/T3/T5 同动 crearte 单一工作树,故交**一个**子代理而非四路争用 `.git/index.lock`;T6(server)/T7(deploy)由控制者本人写。**守卫任务提前为 TDD 第一步**而非第二波:先写守卫看它变红并逐字复现 spec 实测数字(+380/+325/+3px/五处缺失包裹层),再实现到绿——这个顺序使 RED 输出本身成为「有牙」证明,免去事后 revert 自证。任务级审查三份(前端 PASS with 8 notes、server PASS、deploy PASS with 3 notes + 1 条 spec 勘误)+ 全分支终审裁定 **APPROVE with notes(零关键零重要)**,全部 notes 合并前逐条裁定(FE-1/2/6 fix-forward `20215e7`、SD-N1 `fa39d27`、N-F1/2/4 改 spec/FINDINGS/注释,余为 accepted-with-reason)。**终审独立复现三处 mutation**(不采信控制者与审查者结果):回退 T1 → 6 腿 e2e 变红;注释掉表格包裹层 → 源码守卫变红;错误文案回显原始 env → server 两条断言同时变红。**两个挂账项由实测定夺**:①「移动端页头 / 汉堡菜单」**证伪删除**——nav 内容宽仅 74–158px,320/360/375/412/768/1280px 实测零溢出;② nav 链接逐字换行(375px = iPhone 12/13/14 同样如此)**park 待设计决策**——纯外观(无溢出无裁剪无功能损失),`whitespace-nowrap` 在 320px 引入 +11px,七种 gap 收缩组合在「320px + 长名」下全灭(+19~+75px),因 logo(77px)+nowrap nav(138–158px)+截断名(96px) 物理放不下、与页头修复争同一份像素。**诊断关键**:八种一行修法全停在残差 **+32px**(含 `overflow:hidden`)→ 改用烧蚀法定位到 P9-B 自己那个 1px `sr-only` span(`absolute` 无 `top/left`,`static` 包裹层不构成包含块故逃出滚动裁剪)→ 包裹层必须 `relative`。**归因冲突用运行时注入 2×2 消融定案**:768px `/account` 的 +40px 实由缺口 A(页头)驱动而非 dd——回退 dd @768 仍 `over=+0`、回退页头 → `over=+50`;dd 固有右缘恒 592px 故牙口在 <592px 视口。两个修复独立必要、缺一不可,但不是同一视口的同一症状;spec 已 re-pin 五处下游。验收:crearte vitest **635**/74(基线 626/73)/ vue-tsc 零错 / build+build-runtime OK / 主 e2e **92+1skip**(基线 80+1skip)/ noauth 4——控制者复跑两次 + 任务级审查者与终审者各复跑一次,四方零偏差;server gofmt/vet 净 11 包 ok;deploy 四 profile `config -q` 绿 + 守卫三态验证(正向绿 / 注入 CIDR 红 / 删注入行红)。教训入账:① 挂账项实现前必须先证伪或证实;② 所有候选修法失败在同一残差上说明**诊断错了**(换烧蚀法比再加候选有效);③ 运行时样式注入无法验证模板级修法(Vue 把插值编译成单文本节点);④ **源码级 mutation 可能读到 stale `dist/`**(`reuseExistingServer: !CI`)而产生假 GREEN,关键归因须改用运行时注入;⑤ **守卫自身要受与被守卫代码同等的审视**——审查发现注释掉包裹层会使新守卫假绿,与 deploy 守卫里那个恒不匹配的死 CIDR 断言同属假信心类,两者均合并前修掉。CSP / TLS+HSTS / OG 卡片 / 播放计数 / D4 nav 换行设计决策 挂账待启动(UX C 暗色模式已于 P13 交付清偿) |
| P13 | 暗色模式 | 两态主题(light ↔ dark)全站落地:首次访问跟随 `prefers-color-scheme`,显式点击后持久化到 `localStorage['crearte.theme.v1']` 并从此压过系统偏好(**无第三态**——有意不做)。每主题 **12 个设计令牌**;暗色在 `html[data-theme="dark"]`(特异性 (0,1,1),无 `!important`/`@apply`)下整体覆盖亮色 `@theme` 块 = **方案 B**,故只需 **3 处 usage 迁移**(方案 A 需 32+ 处)。`index.html` 内联 **pre-paint 脚本**(IIFE + 仅 `var`,在任何 CSS 与 Vue module 之前)设 `data-theme` 防闪白,判定优先级与 `useTheme.effectiveTheme()` 逐字一致;页头 32px 开关(登出态可用)同步翻转 `data-theme` + `theme-color` meta + localStorage;`color-scheme` 由 `data-theme` 驱动使原生控件跟随。五个 `--shadow-hard*` 改 `var()` 传导;新增 `--color-skeleton` 与 `--color-scrim`(**两主题同值不翻转**——暗色 `color-mix(…ink 60%)` 实测 `oklab(L=0.962)` 近白会毁遮罩)。**零生产逻辑变更** | **完成**(2026-10-03 合并推送:crearte `983e7c4` 0.26.0(merge-base `e59a171`;记账 commit `4cfabdd`+`d1aa8c0`)。**一仓一写者**:T1–T5 交一个实现者子代理(同动 crearte 单一工作树),守卫任务 TDD 先行看红。任务级审查 **PASS with notes**(自设 10 条 mutation 挖出 5 条 findings)+ 全分支终审 **APPROVE with notes**(零关键 / 2 重要 / 5 次要 / 3 建议),10 条 note 合并前逐条裁定:4 条 fix-forward(`2433ec7`/`481574c`/`6ec6170`)+ 4 条(`bb2cb42`)+ 5 条 spec 文档纠正 + 1 条 pre-existing 延后 + 2 条 accepted-with-reason。**本批最重要事件:审查阶梯抓出控制者自己写错两次的守卫**——scrim 分离断言两次反转:spec 原文「`ink vs scrim ≥3` 两主题」会让亮色腿误红(亮色实测仅 1.07,两个深色互不分离);控制者第一次改成 `max(填充侧,边框侧) ≥3` **数学恒真**(两侧互补 → 下界 = √cr(paper,ink) = **4.0621 > 3**,50653 采样暴力验证),故把亮色 scrim 改纯白(正是该令牌要防的泛白)时填充侧 1.1165 而 max() 读 18.42、守卫仍绿(任务级审查 M1 抓到);交付形态改为**按主题钉实际分离侧**(亮=填充侧 paper、暗=边框侧 ink),mutation 现能变红。**两条教训入 spec**:① `max(a,b) ≥ k` 是恒真断言高发区(互补时下界非平凡),且**修守卫必须两方向都验**(对合法值不误红 + mutation 下不误绿)——同一错误在 alpha 兜底分支重演(只对当前 Chromium 输出验,漏了 CSS Color 4 百分比/指数 alpha 被解析成 80/1);② **Tailwind v4 扫描全部源文件含 `.test.ts`/`.spec.ts`**——测试注释里的类名字面量会被当候选、烧死 utility 进产物(实现者用拼接修对一处,控制者八分钟后在自己裁定 commit 里重新引入,靠重建抓出 → spec §8-5b)。**终审另暴露一处 pre-existing 缺陷并延后不忽视**:`runtime/host/GameHost.vue:39` 降级外链徽标(`bg-accent text-paper`,11px bold)实测**亮色 `paper on accent` = 3.2590 < 4.5 FAIL**(暗色 7.1218 过),根因是 spike-0 的 grep 与 `noHardcodedColor` 守卫都只覆盖 `app/`、**漏了与 `app` 同级的 `runtime/`**;该违规自 P9-B 就在、P13 未使其变差且不属 P13 范围 → 三方面处置(守卫扫描根已含 `runtime/`、spec §3.2「accent 全仓唯一/纯非文本令牌」的假声明已纠正、违规本身登记可访问性打磨批)。守卫体系:`contrast.test.ts` **重构为按主题分块解析**(旧单 Map 解析器逐字保留为 `legacySingleMapParse` **反面钉桩**,防后人「简化」回去——旧解析器在暗色块存在后会把九个旧键静默解析成暗色值而断言标签仍写 light palette = 守卫悄悄变只守暗色)+ 新增 `noHardcodedColor.test.ts`(沿用 P12 `tableOverflow` 屏蔽范式,扫 `app/`+`runtime/`,含 2 positive + 1 negative control 防「扫 0 文件也报 0 违规」的假信心)+ `themeBootstrap.test.ts`(钉双写:同键名、两侧 stored 白名单、判定序、IIFE+var、theme-color、color-scheme meta)+ `dark.spec.ts` **10 腿**(含 pre-paint 归因腿:拦 JS 包后 `data-theme` 仍 dark,证明是内联脚本而非挂载后设置;反方向 stored 压过 system 腿;垃圾 stored 被忽略腿——后两条是 finding #2/N2 的行为解药,因文本位置守卫不鉴别语义重排)。验收:crearte vitest **688/79**(基线 635/74)/ vue-tsc 零错 / build+build-runtime OK / 主 e2e **102+1skip**(基线 92+1skip,P12 的 12 条 responsive 腿未动全绿)/ noauth 4——控制者与两位审查者各自独立复跑;产物 `main-C7966Gm-.css`:`var(--color-*)`=75、内联 `#141414`=2、暗色块在位、死 utility 双双归零、`--tw-shadow` 含 `var(--color-ink)`。挂账新增:第三态「恢复跟随系统」(spike 3 证明 header 在 @320px 最坏格无像素容纳带标签控件)、CSP 落地时内联 pre-paint 脚本需 `nonce`、死令牌 `--color-info`(`bg-info`/`text-info` 零实例)、SFC `<style>` 块内硬编码 hex(守卫不覆盖,accepted——`app/` 无 `<style>` 块)、GameHost 亮色徽标 WCAG 3.26——**修时须避开与「加载失败」状态点撞色**(亮色 `accent` vs `accent-ink` 仅 **1.4943**,徽标与状态点同屏共存于展柜标题栏,改成 `bg-accent-ink` 会把「已降级外链」与「加载失败」两个语义压成一个视觉信号),属**设计决策**而非一行改(已实测证伪显而易见修法)。CSP / TLS+HSTS / OG 卡片 / 播放计数 / D4 nav 换行 挂账待启动) |
| P14 | 拆分 ContentService god object | `internal/service/content.go` 从 **856 行**、承载五条互不相干职责线(目录读 / 反应 / 上传 / 投稿 CRUD / 审核发布)的单一 `ContentService`,拆成五个子 service 文件 + 一个组合根:`catalog.go`(110) · `reaction.go`(102) · `upload.go`(99) · `submission.go`(291) · `moderation.go`(298),`content.go` 缩到 **82** 行(只留组合根与真正被两三条线共用的 11 个声明)。五个 handler 各改为声明**消费方定义的窄接口**(可见方法 22 → **4/5/2/5/6**,审计确认**零过声明**、零孤儿);`cmd/catalog.go` 不再为 god object 交税(**依赖槽 4→2**、`nil` bundle 占位消失、不再构造从不读取的 `PostgresUserStore`);顺手删掉死字段 `ContentService.now`(构造器赋 `time.Now` 而包内零读取、无 test seam)。**纯结构重构、零行为变更** | **完成**(2026-10-03 合并推送:crearte-server `fe810dd` 0.18.0,merge-base `dbf7fe5`;记账 commit `e50d08e`;spec 两次 re-pin 在 wrapper `11fa1e2`+`33c2d8d`)。**一仓一写者**:T1–T3 交一个实现者子代理(同动 crearte-server 单一工作树),守卫任务 TDD 先行看红。任务级审查 **PASS with notes**(1 important + 6 advisory,自设 10 条 mutation)+ 全分支终审 **APPROVE with notes**(零关键 / 1 重要 / 2 次要 / 3 建议,16 轮 mutation/spike 全在 HEAD 导出副本上跑、以 140 个 blob hash 证明与 HEAD 一致),13 条 note 合并前逐条裁定:3 条 fix-forward(`e195be0`)+ 1 条 fix-forward(`e04694b`)+ 5 条 accepted-with-reason + 2 条 spec 措辞纠正 + 1 条挂账 + 1 条「审查者对我错」。**本批最重要事件:审查阶梯两轮都抓到了控制者自己的错误**——① 任务级审查发现 spec D-J 自述的守卫目的「防方法迁回组合根」**没有任何断言覆盖**(组合根新增方法后三断言 PASS + build/vet 绿 + **全量 11 包测试全绿** → god object 可静默回潮),并发现 **spec 里 mutation (a) 这个 positive control 自身有绕行路径**(它红是因为把方法从子 service 拿走,不是侦测到组合根多出方法;改成「复制」即遮蔽形态就会误绿);② 全分支终审在**第一轮修复自身**里找到洞:源码扫描正则把接收者名写成**必需**分组,而 Go 允许省略不用的接收者名——遮蔽体恰好常不需要它(`func (*ContentService) CoverURL(k string) string`),该形态下 build/vet/**七断言全绿**而遮蔽**确实生效**(经组合根调用返被接管值、直调子 service 返原值)。**一个 token 修好**:接收者名改可选分组 `(?:\w+\s+)?`。③ 终审另**推翻我两处已入库的全称声明**(「断言 5 是唯一能抓遮蔽的手段」在修前为假;「F4 两层覆盖完整」被孤儿私有方法反例推翻)——两条我都独立复现成立后才动手,均已 re-pin 进 spec 并写明范围。**控制者 spike 否决了审查者建议的两条修法**(终审在真仓复核,两条驳回都正确):断言值类型 `NumMethod()==0` **不可满足**(嵌入 `*T` 使方法进值+指针两个方法集,合法树上已是 **22** → **恒假断言**,正是 P13 恒真断言的镜像形态);比 `Method.Func` 同一性**无法侦测遮蔽**(提升方法是 forwarding wrapper,真仓实测合法 `9683808≠9511104`、遮蔽 `9515680≠9511104`,两侧都不等)。**两条教训入 spec**:① 恒假与恒真同样无价值,且**恒假更容易被当成「守卫很严」而通过审查**;② 写「唯一手段」这类全称声明时必须先**枚举形态**(有名/匿名接收者、值/指针、导出/未导出),否则它成为下一个审查者的反例靶子。守卫终态:`architecture_test.go` **253 行 / 七条 reflect 断言**(组合根形态 · 各线导出方法名集 · 无 `now` 字段 · 组合根指针方法集=五线之并 22 · **源码不得声明组合根方法** · 各线字段名集=依赖面 · 构造器不漏装),经**两轮审查驱动补强**、对 **14 条 mutation 双向验证**(合法树 7/7 绿、每条 mutation 精确红且非编译红)。**副产品发现**:`reflect.Type.NumMethod()` 只暴露导出方法 → 断言 4 看不见**未导出**的组合根方法,那部分**只有源码扫描能抓**(四轮仅变方法名大小写的诊断坐实),故断言 5 的作用比原设计更宽。**Route C 两条 spec 未记的性质已入 §7**:组合根**零具名字段** → `s.store`/`s.objects` 等在其上是 ambiguous selector(实测编译红 `ambiguous selector s.objects`),这是比守卫更早的编译期屏障,也解释了为何字段数断言是防「加具体字段」的唯一防线(加具体字段在 Go 里合法);`go vet` **不报告**遮蔽(实测 `vet_exit=0`)。验收:`wc -l` 口径 `content.go` **856→82**、六文件最大 **298**(`moderation.go`)、`service/` 内 >400 行生产文件 **1→0**、handler 里 `service.ContentService` **10→0**、`catalog.go` nil 占位与 `NewPostgresUserStore` **→0**、组合根自身方法 **25→0**、`cmd/serve.go` diff **0 行**、**既有 `*_test.go` 修改数 0**(`--diff-filter=M` 空,唯一新增 `architecture_test.go`);字节级保真 base 39 声明 → **37 逐字一致 + 恰 2 处刻意变更**(struct 五具体字段→五嵌入指针、ctor 体改组装而**4 参数签名逐字不变**)+ 0 缺失 + 零重复声明、接收者分布 4/6/2/7/6/0 匹配归属映射;四门 `-count=1` 禁缓存全绿 **11 包 ok**(api ~11s 真库集成、service ~3.7s)+ 七断言 `-v` 全 PASS——控制者与两位审查者各自独立复跑。13 文件 src diff +1239/−805(含记账 14 文件 +1283/−805)。挂账新增:`Approve` **169 行**(`moderation.go:47-215`,7 阶段边界已测绘;拆分后行号已重新定位,base 树的 606–772 作废)、`memory_content.go` 814 行测试替身规模、handler 错误映射去重(92 处 `WriteError` 但仅 2 处 `errors.Is`,重复度不足以证明收益)、`AccountService`/`CleanupService` 的 `now` 字段使用复核(本批只确证 `ContentService.now` 死)、断言 6 只钉字段**名**不钉类型(低危,有编译器/既有测试兜底)、断言 5 词法扫描对块注释内部行**过严**(刻意保留:假红比静默遮蔽便宜)、孤儿私有方法归属无守卫(产物只能是死代码,accepted)。CSP / TLS+HSTS / OG 卡片 / 播放计数 / D4 nav 换行 / GameHost 撞色 / 第三态恢复跟随系统 / `--color-info` 死令牌 / bootstrap 加载屏暗色白闪 挂账待启动) |
## 排序理由
- **P0 最先**:几十行的修复,却解锁整条 e2e 回归链路——后面每一波都靠它做验证保障。
- **P1 紧随**:不做则生产环境投稿 / 审核根本不可用,产品功能再丰富也上不了线。
- **P4 可提前**:无依赖且范围清晰,若想先看到用户可感知的产品变化,可与第一波并行。
## 文档与提交约定
- 所有 spec / 实现计划 / 跨仓库协作文档统一放在本仓库 `docs/`(`docs/specs/`、`docs/plans/`、`docs/ROADMAP.md`),并在下表登记;内层仓库只保留各自的 `docs/CHANGELOG.md` 与 `docs/README.md`。此约定取代 crearte-deploy 0.3.0 确立的「crearte-deploy 为跨仓库文档 canonical home」。
- monorepo(本仓库)可直接在 `master` 提交;内层三仓仍遵循各自 `AGENTS.md` 的分支与合并规范。
## 子项目文档索引
| 子项目 | spec | 实现计划 |
|--------|------|----------|
| P4 作者主页 | `docs/specs/2026-09-29-author-page-design.md` | `docs/plans/2026-09-29-author-page.md`(已执行,2026-09-29) |
| P1 prod 写侧可用 | `docs/specs/2026-09-29-prod-write-side-design.md` | `docs/plans/2026-09-29-prod-write-side.md`(已执行,2026-09-29) |
| P5 收藏/评分 | `docs/specs/2026-09-30-favorites-ratings-design.md` | `docs/plans/2026-09-30-favorites-ratings.md`(已执行,2026-09-30) |
| P6 hosted 投稿(方案 A) | `docs/specs/2026-09-30-hosted-submission-design.md` | `docs/plans/2026-09-30-hosted-submission.md`(已执行,2026-09-30) |
| 创作者中心 `/creator`(维护者直接需求) | `docs/specs/2026-09-30-creator-center-design.md` | `docs/plans/2026-09-30-creator-center.md`(已执行,2026-09-30) |
| P2 CI+测试基线 | `docs/specs/2026-09-30-ci-test-baseline-design.md` | `docs/plans/2026-09-30-ci-test-baseline.md`(已执行,2026-09-30) |
| P3 备份+可观测 | `docs/specs/2026-09-30-backup-observability-design.md` | `docs/plans/2026-09-30-backup-observability.md`(已执行——回滚窗口脱离 master,2026-10-01 历史重放回归:deploy 0.6.0 / server 0.16.0) |
| P7 管理后台增强 | `docs/specs/2026-09-30-admin-console-design.md` | `docs/plans/2026-09-30-admin-console.md`(已执行,2026-09-30) |
| P8 长尾打包(第一批:②⑤①) | `docs/specs/2026-10-01-p8-long-tail-design.md` | `docs/plans/2026-10-01-p8-long-tail.md`(第一批已执行,2026-10-01) |
| P8 长尾打包(第二批:③④) | `docs/specs/2026-10-01-p8b2-deletion-catalog-design.md` | `docs/plans/2026-10-01-p8b2-account-catalog.md`(已执行,2026-10-01:server 0.15.0 / crearte 0.21.0) |
| P9 UX 第一批(浏览器反馈包) | `docs/specs/2026-10-01-p9-ux-browser-feedback-design.md` | `docs/plans/2026-10-01-p9-ux-browser-feedback.md`(第一批已执行,2026-10-01:crearte 0.22.0) |
| P10 UX 第一批(日常交互包) | `docs/specs/2026-10-01-p10-ux-daily-interaction-design.md` | `docs/plans/2026-10-01-p10-ux-daily-interaction.md`(第一批已执行,2026-10-01:crearte 0.23.0) |
| P9-B UX 第二批(可访问与键盘效率包) | `docs/specs/2026-10-01-p9b-a11y-keyboard-design.md` | `docs/plans/2026-10-01-p9b-a11y-keyboard.md`(第二批已执行,2026-10-01:crearte 0.24.0) |
| P11 服务端安全硬化 | `docs/specs/2026-10-02-p11-server-hardening-design.md` | `docs/plans/2026-10-02-p11-server-hardening.md`(已执行,2026-10-02:server 0.17.0 / deploy 0.7.0 / crearte 0.24.1) |
| P12 窄屏溢出修复与打磨批 | `docs/specs/2026-10-02-p12-narrow-viewport-overflow-design.md` | `docs/plans/2026-10-02-p12-narrow-viewport-overflow.md`(已执行,2026-10-02:crearte 0.25.0 / server 0.17.1 / deploy 0.7.1) |
| P13 暗色模式 | `docs/specs/2026-10-03-p13-dark-mode-design.md` | `docs/plans/2026-10-03-p13-dark-mode.md`(已执行,2026-10-03:crearte 0.26.0) |
| P14 拆分 ContentService god object | `docs/specs/2026-10-03-p14-content-service-split-design.md` | `docs/plans/2026-10-03-p14-content-service-split.md`(已执行,2026-10-03:crearte-server `fe810dd` 0.18.0) |