diff --git a/docs/plans/2026-10-01-p9b-a11y-keyboard.md b/docs/plans/2026-10-01-p9b-a11y-keyboard.md new file mode 100644 index 0000000..f1adc54 --- /dev/null +++ b/docs/plans/2026-10-01-p9b-a11y-keyboard.md @@ -0,0 +1,115 @@ +# P9-B UX 第二批(可访问与键盘效率)· 实现计划 + +spec:`docs/specs/2026-10-01-p9b-a11y-keyboard-design.md`(决策 D-A…D-K 以其为准;本计划与 spec 冲突时 spec 赢)。 +仓库:**仅 crearte**(`crearte-monorepo/crearte`),分支 `feat/p9b-a11y-keyboard`。server/deploy 零改动。 +npm 根:`crearte/src`(package.json 在此)。命令一律 `cd crearte/src` 后跑。 + +## 全局约束(逐字,进每个任务简报) + +1. 零新依赖:不得改 `package.json` dependencies/devDependencies;`version` 字段恒 `0.1.0` 不动。 +2. 禁触文件:`e2e/landing.spec.ts`、`e2e/noauth.spec.ts`、`e2e/author-page.noauth.spec.ts`、`e2e/merge-repo.spec.ts`、`e2e/ux.spec.ts`、`e2e/submit-flow.spec.ts`、`e2e/admin-flow.spec.ts`、`e2e/helpers.ts`、`playwright.config.ts`、`playwright.noauth.config.ts`、`vitest.config.ts`、`vite.config.ts`、`vite.config.test.ts`、`scripts/`、`runtime/`(本批完全不动,含 `GameHost.vue`)。 +3. 样式语言:新粗野主义既有 tokens(`border-2 border-ink`、`shadow-hard*`、`bg-paper/surface/ink/highlight/accent/accent-ink/success`、`btn-ink/btn-surface/lift`、`font-mono text-[0.6875rem]`、`sr-only`)。注释中文、标识符英文(仓内惯例)。`app/lib/no-gradient.test.ts` 守卫(禁渐变/禁圆角工具类/禁非零 border-radius)必须继续绿——本批不得引入 `rounded*`、`gradient`、非零 radius。 +4. TDD:先写/改 vitest 跑红,再实现跑绿;每个任务在自己报告里贴 RED/GREEN 的命令与输出摘要。 +5. 每任务独立 commit(前缀 `feat(a11y):` 或 `fix(a11y):` + 任务号);**CHANGELOG 不由任务写**——控制者在波次末统一补 0.24.0。 +6. 验收命令(每任务收尾自跑并贴输出摘要):`npm test -- <本任务改动面的测试文件>` + `npm run typecheck`。全量 `npm test` / `npm run build` / `npm run e2e` / `npm run e2e:noauth` 由控制者在波次末跑。 +7. 分支纪律:commit 前 `git branch --show-current` 必须是 `feat/p9b-a11y-keyboard`;不 merge、不 push、不碰 wrapper 仓(ROADMAP/spec/plan 记账归控制者)。 +8. 基线不回退:vitest **601** 全绿、主 e2e **77+1skip**、noauth **4**。既有测试断言只在 spec/本计划明确要求时才可改(本批有两处:`ToastHost.test.ts` 的 `bg-accent`→`bg-accent-ink` 与容器 role 断言反转)。 +9. 精确使用 spec 给定值:skip-link 文案 `跳到主内容`、`href="#main"`、`main` 的 `id="main" tabindex="-1"`;操作列表头 sr-only 文本 `操作`;星标 `aria-label` 模板 `评 ${i} 星`、分组 `aria-label` 模板(未评 `评分`,已评 `评分:${score} 星`);`--color-success: #1f7a4d`;error toast 类 `bg-accent-ink text-paper`;toast 文本节点 `

`。 +10. 本批**不做**移动端汉堡菜单(spec D-K:实测 360px/320px 视口下 `documentElement.scrollWidth` 恰等于视口宽,无溢出);**不做** ``→`` 行头重构(spec D-F);**不做**暗色模式(C 包另行立项)。 + +--- + +### Task 1: 色彩对比达标 + toast 实时区重构(D-A / D-B / D-C / D-I) + +改动文件:`app/styles/main.css`、`app/components/ToastHost.vue`、新 `app/lib/contrast.test.ts`、扩 `app/components/ToastHost.test.ts`。 + +**TDD 顺序**: + +1. 先写 `app/lib/contrast.test.ts`(RED:`success` 当前 `#2fa46a`,`paper on success` 只有 3.16,断言 ≥4.5 必红)。逐字实现 spec §3.1 的 `channel`/`luminance`/`contrast` 三个函数(放测试文件内即可,不必新建 lib 模块——它是守卫而非生产代码)。令牌解析:从 `app/styles/main.css` 读文本,正则 `/--color-([\w-]+):\s*(#[0-9a-fA-F]{6})/g` 建 map;先断言 map 至少含 `paper/surface/ink/ink-soft/ink-faint/accent/accent-ink/highlight/success` 九个键(解析失败要红得明确,不要静默跳过配对断言)。 +2. 断言 ≥4.5 的配对(`fg on bg`,逐字用 spec §3.1 清单):`paper on success`、`paper on accent-ink`、`ink on highlight`、`ink on accent`、`ink-soft on paper`、`ink-faint on paper`、`ink-soft on surface`、`ink-faint on surface`、`paper on ink`、`accent-ink on paper`。断言 ≥3 的:`accent on paper`(焦点环,WCAG 1.4.11 非文本)。断言时给出实测值以便失败可读(例如 `expect(contrast(paper, success), 'paper on success').toBeGreaterThanOrEqual(4.5)`)。 +3. 改 `--color-success: #1f7a4d` → 跑 GREEN(预期 `paper on success` 4.76)。 +4. 改 `ToastHost.test.ts`:① 容器断言反转为**不含** `role`/`aria-live`(`expect(host.attributes('role')).toBeUndefined()`、`aria-live` 同);② 新断言每条 toast 内 `p[role=status][aria-live=polite]` 存在且 `text()` 等于消息;③ 新断言关闭按钮是实时区**兄弟**:`button.element.parentElement === p.element.parentElement` 且 `p.element.contains(button.element) === false`;④ kind 配色断言里 `error` 的 `bg-accent` 改 `bg-accent-ink`(`success`/`info` 两行不动);⑤ 既有「点关闭 → store 少一条」用例保留。先跑 RED。 +5. 按 spec §3.5 逐字重写 `ToastHost.vue` 模板 + 按 spec §3.1 改 `KIND_CLASS` 的 `error` 行 → GREEN。`