原型改动后的版本随本页发布。第一批的盘点结论见下方存档,未变。
结果:8 个状态、全部控件,键盘不可达数 = 0(修改前 13 个;probes/a11y-before.json 与
probes/a11y-after.json 可逐项对照)。
| 页面 | 页面自身可聚焦控件(修改前 → 修改后) |
|---|---|
| Scan | 0 → 5 |
| Home | 1 → 7 |
| Log | 3 → 3 |
| Plan | 3 → 3 |
| Me | 13 → 15 |
做法:凡是可点的一律改成原生 <button type="button">,不用 role=button + tabindex 凑数。
新增 .zh-tap 类只做外观复位(去掉浏览器按钮样式),视觉零变化,并统一 :focus-visible 轮廓。
role="tablist" 里的四个 <button role="tab" aria-selected>,选中态同样是 button,不会像原来那样一选中就退化成纯 div。
scan-analyse,原生 button。 role=tab + aria-selected。
aria-pressed 表达状态。顺带修掉一个被这次改动暴露的缺陷:选中份量后,DOM 分支切换会把被聚焦的节点整个换掉,
焦点掉回 <body>——键盘用户每选一次就被扔回文档顶部。已加 restoreFocus(),
在重绘后把焦点放回代表同一选项的新节点。实测:Enter 选中 0.5 cups 后
aria-pressed=true 且焦点仍在 pick-f-yogurt-half。
另一个被暴露的内容缺陷:主按钮文案原本硬编码「Analyse with photo」。
模式过去键盘切不了、鼠标切了也没人注意,所以没被发现——现在选 Barcode 时它会说
「Look up this barcode」,选 Describe 时说「Analyse the description」。
### 真实键盘主链实测(不是属性审计)
用浏览器真实按键走完,每步都是 focus() + 真实 Enter:
| 步骤 | 结果 |
|---|---|
| 底部 Scan → Enter | 进入采集页 |
| 模式 Describe → Enter | aria-selected 切到 Describe |
| 模式 Photo → Enter | 切回 Photo |
| Analyse → Enter | 进入复核页 |
| 解决读不出的行 → Enter → 选 Black coffee → Enter | 已解决 |
| 四个份量 → Enter | 逐项确认,焦点保持在被按的那颗 |
| 确认这一餐 → Enter | 出现「Saved to today」 |
| 底部 Log → Enter → 编辑该餐 → Enter | 进入编辑,横幅正确显示「already counted」 |
| owner 的要求 | 这一版怎么做的 |
|---|---|
| 入口离用户更近、更显眼,拍完立即提示 | 入口从item 列表下方的一行小字移到复核页顶部(ESTIMATE 条正下方),改成有边框的提示卡:一句话说明 + 两个按钮。不打断主链——下面的逐项列表照常继续 |
| 支持在线商户截图 | 卡上直接给两个并列入口:「Photograph a receipt」和「Pick a screenshot」。截图不再是纸质流程里的一个子步骤 |
| 不要多一次专门确认点击 | 移除了「用途确认」式的措辞。遮挡页的按钮改为「Read this receipt」(描述它做什么),并写明「Masked areas are not sent」。用途与边界写在提示卡上:「How the image is used is covered in the terms and in Me › Data controls」 |
| 可跳过、可移除,不能画成默认强制 | 卡上明写「Optional — you can confirm this meal without one, and remove it again at any point」;原有的「Not now」「Drop the receipt」路径保留 |
| 有无收据都进同一套核对页 | 本来就是同一套——receipt.joint 只是在同一张复核页上多加一条说明横幅,不是另一个页面。已核实,未新建「收据专用核对页」 |
| 不暗示已具备标准菜单数据库 | 现有文案没有此类主张;task #182 的融合能力未在原型中出现 |
1. 原始 JSON 现在是真文件:probes/audit-320.json … audit-480.json、
a11y-before.json、a11y-after.json、focusable-per-page.json,都是本次运行导出的,
不再回落到报告 HTML。
2. harness 可真跑:站上现在带 widths.html(含审计脚本要求的 #f iframe)以及
原型源文件 zhishi-prototype.dc.html、support.js、assets/。
打开 widths.html,贴 probes/width-audit.js,调 await window.__audit(320) 即可复跑。
顺带修了一个会让复跑结果失真的问题:harness 原来用 location.reload(),
会命中缓存旧原型;现在每次都用带时间戳的 src 强制取新。
3. 首页「13 个控件」已改成按页面统计的准确表述,见下方存档的更正段。
probes/assert-all.py)。滚到底无控件被底栏遮挡。
@打包的 用部署站 harness 实按发现:Scan 的模式 tab 被 Enter 选中后焦点掉回 <body>。
我第一版只给 Fridge 份量加了 restoreFocus,Scan 的 tab 漏了;而且我的主链测试每步都先
focus() 再按 Enter,恰好把这个缺陷掩盖了——逐步能走完,连续导航会丢位置。
已修:
setMode 四个模式、showIntake / showBurn 两个 insight tab 都接上 restoreFocus。(「add at least one mask first」),不再是像二次用途确认的措辞。
新增证据 probe:probes/focus-retention.js —— 断言的是「真实激活后 activeElement
仍是同一个控件」,不是统计 tabindex。9 个会发生分支重绘的控件全部保持焦点。
这个 probe 不是空过的:我把 restoreFocus 临时停用再跑一遍,9 个里 7 个立刻失败
(全部掉到 BODY),确认它真的能抓到这类缺陷。
一处要如实说明:那 7 个是 Scan 四个模式 + Fridge 三个份量。**Home 的两个 insight tab
本来就不会丢焦点**(它们绑的是 aria-selected,节点不发生分支替换)。我按你的要求一并接了
restoreFocus,但这两个属于加固,不是修复既有缺陷——不想把它算成战果。
连续键盘导航实测(不再用 focus() 辅助):Tab 依次经过
photo → describe → barcode → label → analyse;在 barcode 上按 Enter 后焦点仍在 barcode,
再按 Tab 正常前进到 label。
顺带一个观察,不属于本批范围:从底部导航继续 Tab 会进入画布外的审图开关
(sim-unreadable-receipt 等),因为它们在 DOM 里排在手机壳之后且可聚焦。旅程文档第 5 节
本来就要求把这类工程内容移出用户流程;要不要在原型里也把它们移出 tab 顺序,请你定。
每组左 390px、右 320px。窄屏是布局回流,不是整体缩放;点击区恒 48px。














#f iframe)await window.__audit(320) / await window.__a11y()本次运行的原始结果:a11y-before.json · a11y-after.json · focusable-per-page.json · audit-320 · 360 · 390 · 480 · assert-all.py
task #2 · 依据 docs/06-user-journey.md(PR #290 合并版,17 条旅程)与本频道已关单的原型
(Claude Design zhishi-prototype.dc.html,etag 1788752587954826)。
这一批只做盘点,不做视觉细化。 按 @打包的 的要求,优先普通用户主链、390/320 断点、
状态恢复、键盘与可达性缺口;future 功能只标位置,不画细节。
两列分开,不要混作一件事:
| J | 旅程 | 页面族 | repo 已实现 | 原型已覆盖 | 差 |
|---|---|---|---|---|---|
| J01 | 登录、恢复会话、首次目标 | A | 已接入 | 可点通:登录/加载/错误/重试、登出、目标编辑器(草稿+取消/保存) | 首次进入的空白 Home 只有模拟开关,没有真正的「首次」编排 |
| J02 | 看懂今天与当日回顾 | B | 现有主流程 | 可点通:能量总览、intake/burn 双页、两个明细弹层、Daily Report | 三种区间状态齐;缺失/过期/刷新失败只有画布外开关,不是页面内路径 |
| J03 | 采集餐食与低置信度回退 | C | 已接入(四路径依赖受控目录) | 可点通:choose→analysing→review→saved,四模式、低置信度分支、草稿恢复、图片失效 | 四个模式切换是死的(见 §3 键盘);手动估算入口有,但没有独立页 |
| J04 | 保存前复核与一餐一次确认 | C | 已接入 | 可点通:逐项包含/排除、份量四档、精确克数、油/酱、目录替换/新增、一餐一次确认、结果未知/离线/冲突全套恢复 | 覆盖最完整的一族;无缺口 |
| J05 | 照片 + 收据联合核对 | D | 已合并发布,线上专项未完成,原生延期 | 可点通:入口、预览、遮挡编辑(至少一处)、联合核对四类证据行、失败保留草稿 | 原型比 repo 状态更「完整」,要标 future/受限,不能让评审当成原生可用 |
| J06 | 从 Log 找旧餐并修正 | E | 现有主流程 | 可点通:日期分组、较早记录、详情、整餐编辑、不可编辑说明、版本冲突二选一 | 无大缺口 |
| J07 | 待确认订单与个人分餐 | F | 简单 Full/Half/Shared 现有;五模式暂停 | 完全没有 —— 组件 state 里没有任何 order 字段 | 整族缺失。repo 有简单版,原型一格都没有 |
| J08 | 商户菜单、订单 QR | G | on hold,只有探索稿 | 完全没有 | 按 on hold 处理,本批只占位 |
| J09 | Fridge 添加/吃了/移除 | H | 现有主流程 | 可点通:库存、份量选择、预览、确认、移除、数量校验、三种失败恢复 | 无大缺口 |
| J10 | 目标、下一餐意向、一周回顾 | I | 现有主流程 | 可点通:目标编辑器、下一餐建议、保存/移除意向;周概览是静态图 | 周回顾没有交互 |
| J11 | 手动运动与健康连接 | J | 现有主流程 | 可点通:运动表单/估算/保存/编辑历史;健康八种状态(默认未连接) | 无大缺口 |
| J12 | Closet 与 Outfit | K | 提案,未批准实施 | 完全没有 | 占位 |
| J13 | Vanity 与 Makeup | K | 提案,未批准实施 | 完全没有 | 占位 |
| J14 | 自定额度 | K | 设计讨论,无实现 | 完全没有 | 占位 |
| J15 | 导出、删除、退出 | L | 现有主流程 | 可点通:导出准备/文件/下载/分享/失败、两步删除、登出 | 无大缺口 |
| J16 | x 天计划 | M | task #178 Web v1 已交付 | 可点通:创建向导(天数/起始日/时区+校验)、逐日完成/跳过/替换/撤销/补记、暂停、恢复日期预览、到期只读复盘 | ⚠️ 两套东西:repo 的 v1 是产品实现,原型这套是我们这几轮自建的。需要一次对齐核对,本批未做 |
| J17 | Pro | N | task #179 todo,先不实现 | 完全没有 | 占位 |
合计: 17 条旅程里,原型可点通 11 条、完全没有 6 条(J07、J08、J12、J13、J14、J17)。
14 个页面族里,F、G、K、N 四族一格都没有。
| 优先级 | 页面族 | 要做什么 | 为什么 |
|---|---|---|---|
| P1 | C 记录一餐 | 四个采集模式改成真正可切换;「Analyse with photo」补键盘可达 | 主链上唯一的主按钮,现在键盘按不到(见 §3) |
| P1 | B 今日 | 三个指标块、carousel 两个 tab 补键盘可达 | 同上 |
| P1 | H 库存 | 份量按钮的选中态补键盘可达 | 选中后 role/tabindex 丢了,键盘用户选完就出不去 |
| P2 | A 进入产品 | 把「首次进入」做成真实编排(空 Home + 引导到第一次记录),不再依赖画布外开关 | J01 终点是「空白 Home」,现在没有这条路径 |
| P2 | B 今日 | 缺失/过期/刷新失败做进页面,不只在画布外 | 文档第 5 节 B 族要求随稿交付这三种状态 |
| P2 | I 计划 | 周概览从静态图改成可点 | J10 终点之一 |
| P3 | D 收据 | 全族加 future/受限 标注 | repo 线上专项未完成、原生延期;原型现在看起来「已上线」 |
| P3 | M x 天计划 | 与 repo v1 做一次逐条对齐 | 两套实现,参数/语义可能已经不一致 |
| — | F/G/K/N | 只占位,标 future/disabled | 按你的要求本批不做视觉细化 |
明确不动: 已通过的功能行为、品牌环(含 CONNECTED / DETECTED FROM SCAN)、48px 点击区、
390pt 视觉基线。不为视觉整洁删功能。
脚本 probes/a11y-audit.js,逐状态遍历壳内所有可点元素,检查三件事:能否用键盘到达、
有没有可访问名称、有没有焦点样式。
更正(2026-09-08,msg 4edf8301): 原先按状态数「13 个控件到不了」说轻了。
反过来数每个页面键盘能到几个才是真实情况:
| 页面 | 壳内可聚焦 | 其中是底部导航 | 页面自己的控件 |
|---|---|---|---|
| Scan | 5 | 5 | 0 |
| Home | 6 | 5 | 1(只有 open-nutrients) |
| Log | 8 | 5 | 3 |
| Plan | 8 | 5 | 3 |
| Me | 18 | 5 | 13 |
Scan 整页键盘可达控件为 0,主链从这里就断。底部五个 tab 是上一轮补 48px 点击区时顺带修好的,
页面内控件当时没一起处理。另外 insight-tab-intake 有 role="tab" 却没有 tabindex。
明细(cursor: pointer 的裸 div,既不是 button 也没有 tabindex):
| 状态 | 控件 | 影响 |
|---|---|---|
| Home | summary-intake、summary-today-burn、summary-nutrition | 三个指标块打不开明细弹层 |
| Home | insight-tab-intake、insight-tab-burn | 摄入/消耗切不了 |
| Home | intake-latest-photo-entry | 进不去最近那餐 |
| Scan | Photo / Describe / Barcode / Nutrition label | 四种采集模式全部切不了 |
| Scan | Analyse with photo | 主链上的主按钮,键盘按不到 |
| Me / 运动 | 份量的选中态(如 None) | 未选中的有 role=button tabindex=0,一旦选中就变成纯 div |
好消息: 所有控件都有可访问名称(0 个缺失);.zh-plan-control 一族有 :focus-visible 样式。
结论: 这不是「补几个 aria」的问题——Scan 主按钮键盘按不到,等于键盘用户走不完主链。
建议统一:凡是可点的,要么用 <button>,要么 role="button" tabindex="0" + Enter/Space,
选中态也不例外。
上一轮已经把窄屏做成布局回流(不缩放,点击区恒 48px),本批复核结论不变:
滚到底没有控件被底栏遮挡(脚本与结果见 probes/)。
本批新发现的断点问题:无。 320 的问题在上一轮已经解决,这里只做回归确认。
失败恢复层是上一轮的重点,72 条断言全过,本批只确认没有回归:
每个待处理失败绑定自己的对象/内容/版本/会话;成功写入只消费同一个操作;
「结果未知」按尝试 ID 查询而不是比内容;版本冲突给出具体的最新版本对照。
缺口在别处:J02 的「缺失/过期/刷新失败」和 J01 的「首次进入」目前只有画布外开关,
不是页面内可达的状态 —— 见 §2 的 P2。
说清楚边界,避免被当成已完成:
1788752587954826;上面全部是盘点结果。