# Changelog / 更新日志 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.7] - 2026-10-03 ### Done / 完成 - P14 splits the `ContentService` god object in crearte-server (0.18.0, merge `fe810dd`, merge-base `dbf7fe5`): an 856-line file carrying five unrelated responsibility lines becomes five sub-service files plus an 82-line composition root, and the five handlers each narrow from 22 visible methods to a consumer-defined interface of 4/5/2/5/6 with zero over-declaration and zero orphaned methods. `cmd/catalog.go` stops paying the god object's tax (dependency slots 4 → 2, the `nil` bundle placeholder gone). Pure structural refactor with no behaviour change — 25 methods moved byte-for-byte, `NewContentService`'s four-parameter signature unchanged character for character, `cmd/serve.go` untouched, and not one existing `*_test.go` modified. - P14 拆分 crearte-server 的 `ContentService` god object(0.18.0,merge `fe810dd`,merge-base `dbf7fe5`):一个承载五条互不相干职责线的 856 行文件,变成五个子 service 文件加一个 82 行的组合根;五个 handler 各自从 22 个可见方法收窄到 4/5/2/5/6 个消费方定义的接口,零过声明、零孤儿方法。`cmd/catalog.go` 不再为 god object 交税(依赖槽 4 → 2、`nil` bundle 占位消失)。纯结构重构、零行为变更——25 个方法逐字节迁移、`NewContentService` 的四参数签名逐字不变、`cmd/serve.go` 未动、无任何既有 `*_test.go` 被修改。 - The review ladder ran two rounds and **both caught the controller's own errors**, which is the batch's main result rather than the split itself. The task review (PASS with notes, 7 findings from 10 self-designed mutations) found that the guard purpose the spec states — "prevent methods migrating back to the composition root" — was covered by **no assertion at all**: adding a method to the root left all three assertions PASS, `go build`/`go vet` clean, and the full 11-package suite green. It also found that the spec's own positive control had a bypass: mutation (a) turns red because it *removes* the method from the sub-service, not because anything detects the extra root method, so rewriting it as a copy would have passed green. The branch review (APPROVE with notes, 6 findings from 16 mutation/spike rounds, all run on a HEAD-exported copy proven byte-identical by 140 blob hashes) then found a hole **in the first round's own fix**: the source-scan pattern required a *named* receiver, but Go permits omitting an unused one and a shadowing body is exactly the case that often needs none — `func (*ContentService) CoverURL(k string) string` left build, vet and all seven assertions green while the shadow was effective. One token fixed it. Thirteen notes were adjudicated before merge: four fix-forward commits (`e195be0`, `e04694b`) and the rest accepted-with-reason, spec-wording corrections, or backlog. - 审查阶梯跑了两轮,**两轮都抓到了控制者自己的错误**——这才是本批的主要产出,而非拆分本身。任务级审查(PASS with notes,10 条自设 mutation 挖出 7 条 findings)发现 spec 自述的守卫目的「防方法迁回组合根」**没有任何断言覆盖**:在组合根上加方法会让三条断言全 PASS、`go build`/`go vet` 全绿、全量 11 包测试也全绿。它还发现 spec 自己的 positive control 有绕行路径:mutation (a) 之所以红,是因为它把方法从子 service **拿走**了,而不是因为任何断言侦测到组合根多出方法,故把它改写成「复制」就会误绿。全分支终审(APPROVE with notes,16 轮 mutation/spike 挖出 6 条 findings,全部跑在以 140 个 blob hash 证明与 HEAD 逐字节一致的导出副本上)接着在**第一轮的修复自身**里找到洞:源码扫描的正则要求接收者**有名**,但 Go 允许省略不用的接收者名,而遮蔽体恰好常不需要它——`func (*ContentService) CoverURL(k string) string` 会让 build、vet 与七条断言全绿,而遮蔽确实生效。一个 token 就修好了。13 条 note 在合并前逐条裁定:4 条 fix-forward(`e195be0`、`e04694b`),其余为 accepted-with-reason、spec 措辞纠正或挂账。 - Two fixes a reviewer proposed were rejected on measurement, and the branch review confirmed both rejections on the real repo: asserting `NumMethod() == 0` on the value type is unsatisfiable because embedding `*T` puts its methods in both the value and pointer method sets — the legal tree already reports 22, making it a vacuously *false* assertion, the mirror image of the vacuously *true* one P13 shipped; and comparing `Method.Func` identity cannot detect shadowing because promoted methods are forwarding wrappers that already differ on the legal tree (`9683808 != 9511104` unshadowed, `9515680 != 9511104` shadowed). Two lessons went into the spec: a vacuously false guard is as worthless as a vacuously true one and is *more* likely to pass review by looking strict; and a claim that some check is "the only way" must enumerate the forms it covers (named/unnamed receiver, value/pointer, exported/unexported) or it becomes the next reviewer's counterexample — the review overturned two such claims I had already committed. - 审查者提议的两条修法经实测被否决,全分支终审在真仓复核确认两条驳回都正确:断言值类型的 `NumMethod() == 0` 不可满足,因为嵌入 `*T` 会使其方法进入值类型与指针类型两者的方法集——合法树上已经报 22,故那是恒**假**断言,正是 P13 交付的恒**真**断言的镜像形态;而比较 `Method.Func` 同一性无法侦测遮蔽,因为提升方法本身是 forwarding wrapper,合法树上两者已经不同(未遮蔽 `9683808 != 9511104`、遮蔽 `9515680 != 9511104`)。两条教训写入 spec:恒假守卫与恒真守卫同样无价值,且**更容易因看起来严格而通过审查**;声称某个检查是「唯一手段」时必须枚举它覆盖的形态(有名/匿名接收者、值/指针、导出/未导出),否则它会成为下一个审查者的反例——本轮审查推翻了我两处已入库的这类声明。 - Spec re-pinned twice (`11fa1e2` after the task review, `33c2d8d` after the branch review) and ROADMAP updated: D-J amended from three assertions to seven, the mutation list extended to a–h with (a)'s bypass annotated, line counts pinned to the `wc -l` convention, D-D recording that `ErrSubmissionConflict` is also used at `account.go:136` outside the two lines that share it, and the risk table gaining two Route C properties — the root has no named fields so `s.objects` is an ambiguous selector rejected at compile time (a barrier earlier than any guard), and `go vet` stays silent on a root method shadowing a promoted one. The third wave table gains the P14 row and the doc index marks it executed against the merge commit; backlog gains `Approve` at 169 lines (`moderation.go:47-215`, seven phases already mapped), `memory_content.go`'s 814-line test double, handler error-mapping dedup, the `now`-field audit for `AccountService`/`CleanupService`, assertion 6 pinning field names rather than types, the lexical scan's deliberate over-strictness on block comments, and orphan private helpers. 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` (`vite.config.ts:21`), whose loading screen hardcodes light values — so dark-theme users see a white flash on every virtual game launch. - spec 两次 re-pin(任务级审查后 `11fa1e2`、全分支终审后 `33c2d8d`)并更新 ROADMAP:D-J 从三类断言增订为七类、mutation 清单扩到 a–h 并注解 (a) 的绕行路径、行数口径钉为 `wc -l`、D-D 补记 `ErrSubmissionConflict` 还被两线之外的 `account.go:136` 使用、风险表新增两条 Route C 性质——组合根零具名字段故 `s.objects` 是编译期即拒绝的 ambiguous selector(比任何守卫都更早的屏障),以及 `go vet` 对组合根方法遮蔽提升方法保持沉默。第三波表新增 P14 行、文档索引按 merge commit 标记已执行;挂账新增 169 行的 `Approve`(`moderation.go:47-215`,七阶段已测绘)、`memory_content.go` 的 814 行测试替身、handler 错误映射去重、`AccountService`/`CleanupService` 的 `now` 字段复核、断言 6 只钉字段名不钉类型、词法扫描对块注释的刻意过严、孤儿私有方法。另登记一条在为下一批取证时发现的暗色缺口:P13 的令牌化只覆盖了 `src/index.html` 入口,漏了第二个 Vite 入口 `bootstrap`(`vite.config.ts:21`),其加载屏硬编码亮色值——故暗色用户每启动一个虚拟游戏都会看到一次白闪。 ## [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 `