diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5aa5343..45e874b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,16 @@ 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.5] - 2026-10-02 + +### Done / 完成 + +- P12 narrow-viewport batch delivered across all three product repos: crearte `e59a171` (0.25.0, merge-base `d25c497`), crearte-server `d56c593` (0.17.1, `9da39b6`), crearte-deploy `529a988` (0.7.1, `740958a`). Zero new features, zero backend behaviour changes. Dispatch followed the one-writer-per-repo rule (`sdd-parallel-dispatch` §1): T1/T2/T3/T5 all touch crearte's single worktree and git index, so they went to **one** subagent rather than four racing the same `.git/index.lock`; the controller wrote T6 (server) and T7 (deploy) itself. The guard task was **pulled forward to be the first step** rather than a second wave — write the guards, watch them go RED reproducing the spec's measured numbers verbatim (`+380px`, `+325px`, `+3px`, five missing wrappers), then implement until GREEN. That ordering removed the throwaway verification scripts the survey phase needed and made the RED output itself the proof the guards have teeth, instead of requiring an after-the-fact revert to demonstrate it. **Four measured defects fixed** (header long-name overflow hitting every route at a *legal* 60-char name; account-page nickname escape; catalog sort-select `shrink-0`; five admin tables with no scroll wrapper — reproducing with short data) **plus one found on the way** (the user dropdown escaping the viewport, closed by the header fix). Two machine guards added: `e2e/responsive.spec.ts` (12 tests, joins the CI main leg with zero config change) and `app/lib/tableOverflow.test.ts` (source-level, per-table parent check). Three P9-B polish items landed (P9B-1/2/4). Three task-level reviews (frontend PASS with 8 notes, server PASS, deploy PASS with 3 notes plus one spec erratum) and a whole-branch final review verdict **APPROVE with notes — zero critical, zero important**; every note adjudicated before merge, three fixed forward (`20215e7`, `fa39d27`). **The final reviewer independently reproduced three mutations rather than accepting the controller's or the reviewer's claims**: reverting the header fix turns 6 e2e legs red; commenting out a table wrapper turns the source guard red; echoing the raw env value turns both server assertions red. **Two parked items decided by measurement, not taste**: the ROADMAP's "mobile header / hamburger menu" backlog entry is **falsified and deleted** (nav content is 74–158px wide with zero horizontal overflow at 320/360/375/412/768/1280px), and nav's per-character link wrapping is **parked pending a design decision** (cosmetic only — no overflow, no clipping, no lost function; `whitespace-nowrap` costs +11px at 320px and all seven gap-reduction variants fail at 320px + long name because logo 77px + nowrap nav 138–158px + truncated name 96px physically do not fit). Acceptance: crearte vitest **635**/74 files (baseline 626/73) / vue-tsc clean / build + build-runtime OK / main e2e **92+1skip** (baseline 80+1skip) / noauth 4 — re-run independently by the controller twice and by both reviewers, four-way zero deviation; server gofmt/vet clean, 11 packages ok; deploy four-profile `config -q` green with the new guard verified in three states. +- P12 窄屏批交付,横跨三个产品仓:crearte `e59a171`(0.25.0,merge-base `d25c497`)、crearte-server `d56c593`(0.17.1,`9da39b6`)、crearte-deploy `529a988`(0.7.1,`740958a`)。零新功能、零后端行为变更。派发遵循「一仓一写者」(`sdd-parallel-dispatch` §1):T1/T2/T3/T5 都动 crearte 的单一工作树与 git index,故交给**一个**子代理而非四路争用同一个 `.git/index.lock`;T6(server)与 T7(deploy)由控制者本人写。守卫任务被**提前为第一步**而非第二波——先写守卫、看它变红并逐字复现 spec 的实测数字(`+380px`、`+325px`、`+3px`、五处缺失包裹层),再实现到绿。这个顺序消除了勘查期所需的 throwaway 验证脚本,并使 RED 输出本身成为「守卫有牙」的证明,而不必事后再回退一次来自证。**四个实测缺陷已修**(页头长名溢出波及全站每个路由,而 60 字名是**合法上限**;账号页昵称逃逸;目录排序 select 的 `shrink-0`;五个 admin 表格无滚动包裹层——短数据也复现)**外加途中发现的一个**(用户下拉菜单逃逸视口,由页头修复一并闭合)。新增两道机器守卫:`e2e/responsive.spec.ts`(12 测试,零配置改动进 CI 主腿)与 `app/lib/tableOverflow.test.ts`(源码级,逐表格父元素判定)。三项 P9-B 打磨落地(P9B-1/2/4)。三份任务级审查(前端 PASS with 8 notes、server PASS、deploy PASS with 3 notes 加一条 spec 勘误)与一次全分支终审,裁定 **APPROVE with notes——零关键零重要**;全部 notes 在合并前裁定完毕,三处 fix-forward(`20215e7`、`fa39d27`)。**终审独立复现了三处 mutation,而非采信控制者或审查者的声称**:回退页头修复 → 6 腿 e2e 变红;注释掉表格包裹层 → 源码守卫变红;错误文案回显原始 env → server 两条断言同时变红。**两个挂账项由实测定夺、非口味**:ROADMAP 的「移动端页头 / 汉堡菜单」挂账**已证伪并删除**(nav 内容宽 74–158px,320/360/375/412/768/1280px 实测零横向溢出),nav 链接逐字换行**park 待设计决策**(纯外观——无溢出、无裁剪、无功能损失;`whitespace-nowrap` 在 320px 代价 +11px,且七种 gap 收缩变体在 320px + 长名下全部失败,因 logo 77px + nowrap nav 138–158px + 截断名 96px 物理上放不下)。验收: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` 全绿且新守卫经三态验证。 + +- Lessons recorded. **A parked backlog item must be falsified or confirmed before being implemented** — "mobile header" had been carried in the ROADMAP since P9-B, and measurement showed the nav fits every viewport; implementing it would have shipped a hamburger menu nobody needed. **When every candidate fix fails at the same residue, the diagnosis is wrong, not the fix** — eight one-line remedies all stopped at exactly +32px (including `overflow: hidden`), which is what forced the ablation method (element-by-element `display: none` plus re-measure) that immediately found the culprit: P9-B's own 1px `sr-only` span, `position: absolute` with no `top`/`left`, escaping a `static` scroll wrapper because such a wrapper is not a containing block. Border-box scanning could not see it (the offender scan reported `trueOffenders=0` while the document still overflowed, because its "has a scrollable ancestor" filter classified the escapee as legitimate). **Runtime style injection cannot validate a template-level fix**: Vue compiles `{{ user.display_name }} ▾` into one text node, so three variants that tried to keep the caret outside the truncating span all measured `caret=HIDDEN` — the template had to split it explicitly, and only then could the flex shape be measured. **Source-level mutation testing can read a stale `dist/`** under `reuseExistingServer: !CI`, producing a false GREEN; the attribution that mattered (which of two fixes owns the 768px symptom) was settled by runtime-injection ablation instead, which needs no rebuild. **Guards get the same scrutiny as the code they guard**: review found the new source-level parser would stay green if someone commented out a wrapper — the same false-confidence class as the dead CIDR assertion caught in the deploy guard, both fixed before merge. **The controller itself made two verification errors this batch, both caught by reverse-testing its own work**: an assertion written as `"[0-9]` could never match compose's bare-scalar rendering (a dead assertion = false confidence), and a comment claimed a SIGPIPE/pipefail hazard that measurement did not reproduce (removed rather than left as unfounded lore). +- 教训入账。**挂账项在实现前必须先证伪或证实**——「移动端页头」自 P9-B 起就挂在 ROADMAP 上,而实测表明 nav 在所有视口都放得下;实现它等于交付一个没人需要的汉堡菜单。**当每个候选修法都失败在同一残差上,错的是诊断而非修法**——八种一行修法全停在恰好 +32px(含 `overflow: hidden`),这才逼出烧蚀法(逐元素 `display: none` 加重测),并立即找到元凶:P9-B 自己那个 1px 的 `sr-only` span,`position: absolute` 而无 `top`/`left`,因 `static` 滚动包裹层不构成包含块而逃出裁剪。边框盒扫描看不见它(offender 扫描在文档仍溢出时报 `trueOffenders=0`,因为它的「有可滚动祖先」过滤把这个逃逸者归为合法)。**运行时样式注入无法验证模板级修法**:Vue 把 `{{ user.display_name }} ▾` 编译成单个文本节点,故三个试图把 caret 留在截断 span 之外的变体全部实测 `caret=HIDDEN`——必须在模板里显式拆开,之后才谈得上测量 flex 形态。**源码级 mutation 测试可能读到 stale `dist/`**(`reuseExistingServer: !CI` 下)而产生假 GREEN;真正要紧的那次归因(两个修复中谁拥有 768px 症状)改用运行时注入消融定案,它不需要重建。**守卫自身要接受与被守卫代码同等的审视**:审查发现新的源码级解析器在有人注释掉包裹层时会保持绿——与 deploy 守卫里抓到的死 CIDR 断言属同一类假信心,两者都在合并前修掉。**控制者本人本批也犯了两个验证错误,均由反向测试自己的工作抓出**:一条断言写成 `"[0-9]` 而永不匹配 compose 的裸标量渲染(死断言 = 假信心);一句注释声称存在 SIGPIPE/pipefail 危害,而实测未复现(已删除,而非留作无据传闻)。 + ## [0.3.4] - 2026-10-02 ### Done / 完成 diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index b29fb97..827b82c 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -36,8 +36,9 @@ | 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 效率可访问 / C 暗色模式两批挂账待启动) | | 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 效率可访问 / C 暗色模式 / 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 个 `` 补 `scope="col"`(两处空表头补 sr-only「操作」)+ 五星评分可访问名(逐星「评 N 星」+ 分组 `role="group"` 动态标签 + 字形 `aria-hidden`)。另加两个源码级守卫(`contrast.test.ts` 十一配对对比度、`tableScope.test.ts` 全仓 `` 补 `scope="col"`(两处空表头补 sr-only「操作」)+ 五星评分可访问名(逐星「评 N 星」+ 分组 `role="group"` 动态标签 + 字形 `aria-hidden`)。另加两个源码级守卫(`contrast.test.ts` 十一配对对比度、`tableScope.test.ts` 全仓 `0 即页面横向溢出),在真实构建产物上由 Playwright 实测(`build:e2e` + `serve-runtime.mjs --port 4173`)。 + +### 缺口 A(D1a):合法长用户名把页头撑出视口,且波及**全站每个路由** + +`AppHeader.vue:102` 的 `` 直接插值 `{{ user.display_name }} ▾`,无任何宽度约束。60 字符 **ASCII** 名在 `word-break: normal` 下不可断行,summary 索取 484px 内容宽: + +| 视口 | 实测 | +|---|---| +| 320px | `doc=700/320` → **+380px**;`sumW=484 sumRight=700` | +| 375px | `doc=700/375` → **+325px** | +| 768px | `/` ok;`/account` **+40px** — spike 17 反证更正:此 +40 由**本缺口(页头)**驱动,非缺口 B(回退 dd 修复 @768 仍 `over=+0`;回退页头 @768 → 红)。原表把它记在缺口 B 名下是归因错误 | + +**可达性是硬事实,不是敌意构造**:后端 `internal/handler/auth.go:25` `maxDisplayNameLen = 60`、`:76` 错误文案「display name must be 1-60 characters」;前端 `app/auth/validation.ts:4` `DISPLAY_NAME_MAX = 60`、`RegisterView.vue:90` `maxlength="60"`。即**任何注册用户填满合法上限即触发**,且因页头全站常驻,`/`、`/games`、`/docs`、`/creator`、`/login`、`/account`、`/admin/*` 无一幸免。 + +24 字 **CJK** 名不触发(CJK 可断行,实测 `doc=320/320 ok`)——缺陷取决于**字符可断性**而非长度本身,故修复必须针对「不可断行串」而非「截断到 N 字」。 + +### 缺口 B(D1b):账号页昵称 `dd` 逃逸 + +`AccountView.vue:190` 的 `
` 位于 `flex flex-wrap items-baseline gap-2` 内,但 60 字 ASCII 不可断行且 flex item 默认 `min-width:auto` 拒绝收缩:320px 实测 `ddW=542 ddRight=592 ddEscapes=true`(视口 320),`ddWhiteSpace=normal ddOverflowWrap=normal`。 + +**牙口位置(spike 17 2×2 消融实测)**:dd 固有宽 542px、左缘 50px → 右缘恒为 592px,故它在**任何 <592px 的视口**都溢出:回退本修复 @320px → `over=+272`(`scrollWidth=592`,即 `ddRight=592`)、@375px → `over=+217`。但 @768px dd 右缘 672 < 768,**本修复在 768 非必要**——该视口 `/account` 的 +40px 是缺口 A(页头)驱动(见上表更正)。即:dd 修复的牙在 320/375,页头修复的牙在全视口;两者独立必要、缺一不可,但**不是同一视口的同一症状**。 + +### 缺口 C(D2):目录页排序 select 的 `shrink-0` + +`ResultMeta.vue:22` 的 `
` 包裹排序 `