# Aham UI · 网页工作台与高密度单据

版本 7.5.0。正式扩展 [DESIGN.md](DESIGN.md) §8，数值唯一来源为 [tokens.json](tokens.json)。C 风格是 Aham 的业务工作台模式：保留视觉身份，吸收 Circle / Linear 的空间组织与操作效率。它不是另一套皮肤。

## 1. 适用范围与身份边界

| 维度 | 必须保留的 Aham 规则 | 工作台扩展 |
|---|---|---|
| 颜色 | 中性三级底色；蓝色用于主操作、品牌、焦点、当前页签和文字链接 | 导航和行选中用灰底；主按钮底色用深一档蓝（与基础层相同）；危险操作用红色文字；不引入紫色或彩色阶段卡 |
| 字体 | 字体栈照 Linear：Inter Variable，数字和代码用 Berkeley Mono（未授权时 JetBrains Mono）；缺失字体使用 token 中的本地回退 | 列表、详情、设置正文 13px；单据紧凑模式 12px；不全局缩小字号 |
| 表面 | 无静态阴影；层级来自底色、间距和必要分隔 | 单据的分区是 1px 描边的卡片，卡片里的小节是次级底色的块，只套这一层（§4.1）；摘要使用次级底色 |
| 状态 | 6px 点 + 明确文字；不能只靠颜色 | 草稿、待审核、退回、已生效均用同一语法；风险另配解释（§1.2） |
| 布局 | content 网页仍居中，macOS / Office / 邮件仍分轨 | web-workbench 全宽，窄导航 + 内容 + 可折叠摘要 |
| 控件 | 按钮 / 输入 8px 圆角；每页一个主要操作 | 工具栏、输入和表格局部密度有明确档位；筛选条件标签用胶囊（Circle 原形） |

适合 CRM、ERP、MES 的客户列表、商机详情、报价、合同、订单等业务界面。官网、阅读页、登录和向导继续采用内容型布局。禁止给所有页面批量套工作台 CSS。

### 1.1 取舍规则与工具感

Aham 与 Linear 冲突时按下面的规则取舍，例外在对应章节写明：

- 颜色、形状与图形语言以 Aham 为准：单一蓝色色相、灰底选中、6px 状态点、无静态阴影、红色只用在文字上。
- 字体用 Linear 的字体栈：Inter Variable，等宽 Berkeley Mono（DESIGN §1.3）。字号和字重仍按 Aham 的档位。
- 布局、交互、信息密度与键盘操作以 Linear / Circle 为准：40px 页眉横条、筛选与显示选项、命令菜单、列表键盘、就地修改。
- Linear 的标志性控件保留原形：筛选条件用胶囊标签（DESIGN §8.5 例外），⌘K 命令面板居中偏上。
- 按钮的底色和描边属于图形语言：次级按钮在两层一律是白底 + 1px 分隔线描边（基础层 `.btn--secondary`），不用 Circle 的灰底按钮。灰底放在次级底色的面板上几乎看不见。

工具感的五条做法：

1. 键盘优先：列表、详情和弹层都能只用键盘完成，按键登记在同一张表（§12.2）。
2. ⌘K 覆盖页面上的全部操作；高频操作另有快捷键或右键菜单，各入口调用同一份操作定义（§12）。
3. 就地修改、即时生效、可以撤销：单元格和属性栏直接改，完成后用提示条确认并给「撤销」（§12.4）。单据的表单改用编辑态、一次保存（§4.2）。
4. 控件按需出现：没有筛选条件时不显示筛选行，显示选项调整过才加圆点，面板一次只开一个。
5. 提示跟着操作走：悬停或聚焦按钮时显示名称和快捷键（§12.7），按 ? 打开完整快捷键列表。

### 1.2 风险与警示

工作台的状态点一律用次级灰，只表示记录处在哪一步，不表示好坏。真实的风险另写一行说明，或把超出阈值的数值染成语义色。

- 类名 `.wb-risk`，默认用危险色，加 `.warning` 用警示色。只在确有业务风险时用，例如毛利低于阈值、交期晚于要求、价格过期。
- 说明写成「图标 + 文字」：文字写清哪里不对、阈值是多少，例如「毛利 8.2%，低于 12% 的审批线」。不能只有颜色或只有图标。
- 表格单元格里只把数值染色，不放图标（DESIGN §1.6）。同一行或摘要栏要有文字说明，数值旁再加 `.wb-sr-only` 的隐藏文字，读屏能听到原因。
- 状态点、整行底色、胶囊都不随风险变色。一条记录同时有状态和风险时，先写状态，风险单独一行。
- 警示色文字只在白底上达到 4.5:1。放在次级底色（摘要栏、评论块、AI 区块、单据小节）上时，文字改用墨色，只有图标保留警示色。危险色在两种底色上都达到 4.5:1，由 `scripts/lint-design.mjs` 核对。
- 成功色不用来标「正常」。没有风险时什么都不显示。

## 2. 空间结构

外框由导航栏和内容卡组成。导航栏在 `tier2-panel` 底色上；内容卡是 `tier1-white`，不加边框和静态阴影。内容卡里依次是页眉横条、页面标题（单据页才有）、内容区和侧面板。

单据页顺序：页眉横条 → 标题与状态 / 操作 → 表单 → 页签与明细 → 备注与条款。摘要放在右侧，只呈现决策相关信息：金额、付款、审批、最近活动。

| 区域 | Token / 规则 |
|---|---|
| 收起导航 / 展开导航 | `workbench.railWidth` 52px / `navWidth` 240px（7.2 由 148 改为 240，容纳中文二级菜单）；只用于工作台页面，应用轨和 content 网页用 `layout` 组 |
| 导航项 | `navItemHeight` 32px；当前页用 `aria-current="page"` 标记，灰底 |
| 内容卡 | 宽于 1024px 时离窗口边缘 `shellGutter` 8px、圆角 `shellRadius` 12px；1024px 及以下贴边 |
| 页眉横条 | `headerRow` 40px，每页 1–2 条；条内控件 `headerControl` 28px，用透明外扩把点击区补到 `hitMin` 32px。见第 2.1 节 |
| 主内容 | `minmax(0,1fr)`，不用 content max-width 截断数据区 |
| 侧面板 | 两档：`panelNarrow` 240px 放属性和摘要，`panelWide` 400px 放预览和分析。一次只开一个 |
| 右侧摘要 | 窄面板；较预览加宽以容纳较大金额和 12px 文字 |
| 字段标签 | 单据表单 `formLabelWidth` 148px，左对齐，在值左边时加冒号（§4.1）；属性栏、识别结果行这类窄面板 `labelWidth` 72px；标签不可省略；长值可聚焦查看或进入编辑 |
| 单据正文 | 最宽 `documentMaxWidth` 1328px 加卡片和小节的内边距，窗口更宽时右边留白；表单一列 `formColumnMin` 480px 到 `formColumnMax` 640px |
| 表格 | `tableMinWidth` 680px；局部横向滚动，不让整页横向溢出 |
| 区域间距 | `spacing` 档位；卡片里只套一层小节块，小节里不再套块 |

使用已有断点：≤1280px 表单由三列变两列；≤1024px 内容卡贴边，摘要移到单据后方，导航展开时浮在内容上方；≤640px 单列，导航栏隐藏，由页眉的导航按钮打开。金额摘要不能因折叠而丢失。移动端优先阅读与轻编辑，大量连续录入优先桌面。嵌入 pane 时按容器可用空间折叠，不能只按整屏判断。

### 2.1 页眉横条与滚动

- 第 1 条回答「在哪里」：左侧依次放导航按钮、面包屑或页面名、视图切换；右侧放搜索、通知等全局入口。
- 第 2 条放当前数据的操作：左侧是条数或上下文，右侧依次放筛选、面板开关、显示选项。单据页通常只用第 1 条。
- 条数放第 2 条左侧，遵守 §8.5「计数不置底」。
- 主按钮每页只有一个。单据页有标题区，主按钮放在标题区右端（§6）。列表、详情、设置没有标题区，主按钮放在第 1 条横条最右端，排在搜索、通知这类全局入口之后。
- 第 2 条（条数、筛选、显示、批量条）不放主按钮。批量操作用文字按钮，破坏性操作用红字按钮（§9.5）。
- 页面没有明确的主操作时不放主按钮。详情页和设置页目前都没有。
- 视图切换用 `.wb-seg.sm`，选中为灰底深色字。单据内的内容页签仍用蓝色下划线（AGENTS.md 允许的用蓝位置）。
- 宽于 640px 时外框固定在视口内：页眉横条不动，内容区滚动，侧面板单独滚动。640px 及以下恢复整页滚动。
- 没有可聚焦内容的滚动区（如摘要栏）加 `tabindex="0"` 和可访问名，保证键盘能滚动。

### 2.2 导航开关

- 导航按钮在第 1 条横条最左侧，⌘B / Ctrl+B 同样可以切换；输入法组字时不响应。
- 宽屏时导航推开内容，展开或收起的选择记在本机。
- 1024px 及以下，导航展开时浮在内容上方，背后加遮罩，内容区设为 `inert`；Esc、点遮罩或选中导航项都会关闭，焦点回到导航按钮。这时的开关不记忆。

## 3. 密度不是统一缩放

| 内容模式 | 行高 | 使用规则 |
|---|---|---|
| 全局 comfortable | 44px | 原规范；触摸控件下限 |
| 全局 standard | 36px | 原有单行表格，保持兼容 |
| 列表页 | 36px，可切 32px | 正文 13px；行高由显示选项控制（`data-list-density`） |
| 单据 compact | 32px | 单行主信息；输入 28px；12px 正文 |
| 单据 detail | 48px | 显示名称 + 编码 / 说明两行；属于内容模式，不改全局 standard |

单据页用 `data-density="compact"` 或 `"detail"` 切换，示例按钮显示「紧凑 / 详情」。早期草稿里的 `standard` 按 `detail` 读取，新代码不再使用。列表页的档位名与单据页不共用。≤640px 或粗指针时，交互目标至少 44px，输入采用 16px；不能通过紧凑开关覆盖。选择灰底，状态无整行颜色。可滚动区域可通过 Tab 聚焦，支持原生键盘滚动。

## 4. 单据表单契约

- 默认阅读，编辑入口明确；只读值应可选中、聚焦和查看，不能伪装成禁用字段。
- 每个字段有标签；必填、格式和跨字段关系共同校验。错误贴近字段并有文字反馈，不能只有红框。
- 保存校验全部字段，包括当前隐藏页签和筛选隐藏行。失败时切回并定位首个错误，保留输入。
- 修改进入未保存状态；保存成功必须来自实际持久化结果；失败保留内容并允许重试。
- 阅读 / 编辑、未保存 / 保存中 / 已保存 / 保存失败、业务审批状态是不同维度，不混为一个状态。
- 生产单据已提交后能否编辑、版本如何变更，由业务状态和权限控制。示例为了演示，编辑后回到草稿，不能直接照搬为审批业务规则。

### 4.1 版式（7.5）

示例见 [单据的显示态和编辑态](examples/object-page.html)。下面的类都在 `workbench.css`，门槛写在 `workbench-layout.css.in`。

- 正文宽度：`.wb-document.is-capped` 把直接子元素限在 `documentMaxWidth` 1328px，另加卡片和小节的内边距。窗口更宽时右边留白，表单不跟着拉宽。列表页不用。
- 分区是卡片 `.wb-doc-card`：1px 分隔线描边、`radius.lg` 圆角，标题 `.wb-doc-card-head h2` 15px、下面一条分隔线。这是 DESIGN §0「卡片无边框」在单据正文的例外，别处的卡片照旧无边框。
- 卡片里的小节是次级底色的块 `.wb-doc-group`，标题 `.wb-doc-group-title` 前面有 3px 宽、14px 高的蓝色竖条。卡片里只套这一层，小节里不再套块。
- 表单一列 `.wb-form-col` 宽 480～640px。两个都短的小节并排放进 `.wb-doc-pair`；小节里一边是表单、一边是表格时用 `.wb-doc-side`；一个小节里的表单分两列用 `.wb-form-cols`。容器宽于 `formPairMin` 1008px 时才并排，否则上下排。
- 字段行 `.wb-form-row` 由标签 `.wb-form-label` 和值 `.wb-form-value` 组成。表单列宽于 480px 时标签在值左边，宽 `formLabelWidth` 148px、左对齐，后面是全角冒号 `.wb-colon`。窄于 480px 时标签在值上面，不加冒号。这里按表单列自己的宽度判断（容器查询），不按窗口。
- 必填标记 `.wb-form-required` 写在冒号后面，只在编辑态显示。
- 右边只剩一行的表单不单独占一块，和旁边的表格或小节并排。

### 4.2 显示态和编辑态（7.5）

照 SAP Fiori 的 Object Page。两种状态用同一套行，切换时只换值的写法，页面不上下移动。行为在 `workbench.js` 的 `initObjectPage`。

- 进入编辑态：页眉主按钮「编辑」、按 E、双击能改的值、在聚焦的值上按 Enter 或 F2。双击时焦点落在那一项；点「编辑」时落在第一个必填、空着的框，没有就落在第一个框。
- 页眉的其他操作加 `data-edit-hide`，编辑态只隐藏，位置还占着。页面标题旁边写「编辑中」。
- 编辑态的页脚 `.wb-edit-footer` 贴在正文底部。左边是消息按钮 `.wb-msg-btn`，需要时接一次保存共用的输入，例如改值的来源；右边是主按钮「保存」和「取消」。
- 「保存」和 ⌘ / Ctrl + Enter 先校验全部字段。有错误时不存，打开消息列表；没有错误时提交，服务端确认以后才回到显示态，提示条写结果。
- 提交中「保存」保留文字、设 `aria-busy`，不能重复提交。失败时留在编辑态，输入不丢，提示条写原因。
- 「取消」：没改动时直接回到显示态。有改动时先问「放弃这次的全部改动？」，按钮是「继续编辑」和红字的「放弃改动」。站内跳转、关闭标签页时同样先问。
- 回到显示态以后，焦点落在「编辑」。
- 页面打开以后别人改过同一份单据时，服务端拒收。弹窗写谁改了什么，给「仍然覆盖」「放弃我的改动」和继续编辑三条路。
- 就地修改、即时生效（§1.1、§12.4）只用于列表的格子和属性栏。单据的字段之间常有关联，一次保存才能一起校验。

### 4.3 值的写法（7.5）

- 显示态里能改的值下面有 1px 虚线（`ink.tertiary`），悬停时加深。不能改的值没有虚线；有修改权限时，页面上方的图例写「虚线 能修改」。
- 要核对的值只在文字底下加 `warning-mark` 浅黄，例如识别的置信度低。不给整行上底色。
- 必填、没写的值写「没写」或业务上的说法，红字加 `danger-bg` 浅红底。
- 空着的值写「–」，用次级文字色。
- 值后面可以放 24px 的来源图标 `.wb-source`，悬停或 Tab 聚焦时用提示写来源、出处和修改人。服务端给的时间按浏览器所在时区写。

### 4.4 校验和消息（7.5）

- 编辑态里的问题分错误和提醒两级。错误不让保存；提醒照样能保存，例如业务允许以后再补的必填项。
- 有问题的值：错误时框的下边是 2px 红线、浅红底，提醒时是 2px 黄线。说明 `.wb-value-state` 在这一项聚焦时浮在下面，不挤开下面的行。
- 页脚的消息按钮写问题的条数，有错误时红色，只有提醒时浅黄。点开是消息列表：按小节分组，错误在前，每条写字段名和说明；点一条，焦点跳到那一个框。
- 点保存时有错误：不保存，打开消息列表，焦点在第一条。焦点不直接跳到字段，用户先看到一共有几处问题。
- 改过的框失去焦点时重新校验，问题解决了说明就消失。

### 4.5 编辑控件（7.5）

- 一个值一个框时，框占满值的格子（`.wb-edit` 里的 `.wb-edit-input`）。
- 几段组成的值拆成几组 `.wb-edit-group`，每组是前面的文字、框和后面的单位，组内不折行，组和组之间可以折行。例如账期写成「60 天，」「月末 起算」两组。
- 自由文字用 `.wb-edit-area`：一行时和单行框一样高，按内容加高，不出滚动条。
- 下拉的空选项写「–」。框旁边没有文字说明时，框里写这一项的名称当占位。
- 框只有下边线。框高 `hitMin` 32px，保证点击区。上下各用 -4px 外边距收回，占的仍是一行字的 24px，进出编辑态时行高不变。多行文本框最低 32px，同样收回。数字右对齐，用等宽字体。

## 5. 可编辑明细表契约

使用原生 `table`、列头 `scope` 和带名称的输入；没有实现完整方向键导航前不能标记为 ARIA grid。只保留横线；表头与数据行同高、不加底色（与列表相同，§9.1）；数字右对齐、等宽数字；金额不能截断成省略号。名称截断需保留完整可访问文本，并提供 title / 详情查看。

数量 0–999 整数，折扣 0–100%，精度 0.1%，这是演示模型的约束，产品必须按业务定义。筛选只隐藏行，不影响总金额。空搜索结果提供恢复提示。无效编辑时保留原输入，汇总保留最近有效值并明确提示；保存和审批必须阻止。

单据正文里的小表格放在 `.wb-table-block` 里（7.5）：上面是工具栏 `.wb-table-toolbar`，和表格同宽，左边是标题，右边是「添加」；表格放在 `.wb-table-scroll` 里，最小宽度 420px，窄屏时表格自己横向滚动，页面不横向滚动。编辑态每行最后一格是删除，有比例列的表格写合计。

示例金额以整数分运算：单行金额 = round(数量 × 含税单价分 × 折扣千分值 / 1000)，总额 = 已舍入行金额之和。付款 40% / 40% / 余款，最后一期吸收尾差。产品需要单独声明税率、含税 / 未税转换、币种小数位和舍入政策；此例不模拟税额拆分。

## 6. 摘要、审批与动作

金额始终与明细同源计算，不能分别存储两套可编辑金额。付款比例与金额同时展示。审批有角色、状态与时间；真实审批须有服务器确认和审计记录。附件行须区分真实可下载文件与示意条目。

标题区放次操作“保存”和唯一主操作“提交”；演示必须标成“模拟审批”。长请求保留按钮文字和占位，`aria-busy=true`，阻止重复提交，完成通过状态区域播报，失败可重试。不要为了整齐在底部重复多个蓝色按钮。

## 7. 状态与可访问性验收

| 情况 | 产品必须具备 | 本次参考实现 |
|---|---|---|
| 默认 / 阅读 / 编辑 | 正确语义、焦点可见、可访问名 | 已实现 |
| 输入错误 | 原值保留、文字反馈、阻止提交、定位错误 | 已实现 |
| 空结果 | 原因和恢复路径 | 已实现 |
| 草稿恢复 | 版本校验、不信任本地金额、损坏数据回退 | 已实现 |
| 存储失败 | 不宣称保存成功、保留修改 | 已实现 |
| 主题 / 密度 | 亮暗语义颜色，移动目标不缩小 | 已实现；密度随显式保存恢复 |
| 加载 / 网络异常 | 尺寸匹配骨架、明确错误、重试 | 产品接入时实现；此例无网络 |
| 无权限 / 并发冲突 | 无权限原因、版本冲突与恢复选项 | 产品接入时实现 |
| 真正提交 / 审计 | 服务端校验、幂等、权限、版本、审计 | 产品接入时实现；此例仅本机模拟 |

键盘能操作全部演示控件；页签示例采用原生按钮组和 `aria-pressed`，不是 ARIA tablist，因此使用 Tab / Enter，不宣称实现方向键页签模型。错误不能只靠颜色；状态至少“点 + 文字”。普通文字对比度至少 4.5:1，焦点描边 4.63:1；禁用元素单独评估。默认外观的控件边界是 1px 分隔线色，对白底只有 1.24:1，这是「冷色的纸」的取舍。系统开启增强对比度时，边界换成 `color.contrastMore.control-border`（3.45:1），输入框加描边，焦点描边加粗到 3px。系统开启减少动态效果时去掉过渡。正文用 primary / secondary，不用 tertiary；状态文字用墨色。主按钮底色用深一档蓝，亮暗都是白字（7.21:1），与基础层一致。危险按钮用红色文字，不用红底。

### 7.1 暗色的主按钮、焦点与语义色

下表是工作台在亮暗两种主题下的实际取值。工作台变量由 `scripts/build-workbench.mjs` 从 tokens 生成，对比度按 WCAG 2 公式计算。

| 用途 | 变量 | 亮色 | 暗色 | 对比度 |
|---|---|---|---|---|
| 主按钮底 | `--wb-action` | `#164EC3` | `#164EC3` | 白字 7.21:1 |
| 主按钮悬停 | `--wb-action-hover` | `#336EE8` | `#336EE8` | 白字 4.63:1 |
| 焦点描边、当前页签下划线、列表焦点行边线 | `--wb-blue` | `#336EE8` | `#5C8BED` | 暗色对内容底 5.17:1，对次级底 4.35:1，对选中灰 3.45:1 |
| 危险文字 | `--wb-danger` | `#9E3D31` | `#D08070` | 暗色对内容底和次级底都不低于 4.5:1 |
| 警示文字 | `--wb-warning` | `#8A7333` | `#C2A855` | 亮色只在白底达到 4.5:1，见 §1.2 |

- 主按钮底色亮暗相同，token 是 `color.alias.action-bg`。暗色下主按钮与内容底 `#1C1C1C` 的对比只有 2.36:1，按钮靠白字和位置辨认。
- 焦点色取当前主题的 `accent.default`，与 token `focusRing` 的说明一致。7.4 之前工作台暗色用的是 `#7BA3F0`，与基础层不同，已改为 `#5C8BED`。
- 系统开启增强对比度时，焦点描边加粗到 3px，控件边界换成 `color.dark.contrastMore.control-border`。
- 以上是按公式核算的结果，还没有在真机上目测（DESIGN 诚实声明第 2 条）。

## 8. 实现与验收入口

加载 `workbench.css`，根节点加 `.aham-workbench`；CSS 通过 `:where()` 隔离，断点覆盖来自生成文件。

类名分两层。通用结构类以 `wb-` 开头，写在 `workbench.css`，CSS 变量以 `--wb-` 开头。页面专用样式写在示例旁边的同名 CSS（如 `examples/crm-quotation.css`），不得写进 `workbench.css`。外框、页眉、状态、按钮、页签、工具栏、表格、摘要栏都属于通用层。`workbench-tokens.css` 由 `tokens.json` 和 `workbench-layout.css.in` 生成，不能手改。没有 npm 运行依赖。字体遵循系统 token，基础层和工作台都不从第三方服务器下载字体；安装 Inter / JetBrains Mono，或由产品自托管（SIL OFL），即可统一字形。

组合契约 27 个：单据页 4 个（`workbench`、`document-form`、`editable-table`、`context-panel`），列表页 4 个（`list-view`、`filter-bar`、`display-options`、`bulk-bar`，见第 9 节），通用选择器 1 个（`command-menu`，见第 10 节），详情与预览 4 个（`list-keyboard`、`record-detail`、`properties-panel`、`activity-feed`，见第 11 节），快速操作 6 个（`command-palette`、`shortcuts`、`context-menu`、`cell-picker`、`toast`、`create-dialog`，见第 12 节），设置页 1 个（`settings-page`，见第 13 节），AI 协作 3 个（`ai-output`、`ai-suggestion`、`ai-confirm`，见第 14 节），文件与版本 4 个（`file-upload`、`file-viewer`、`tree-table`、`version-compare`，见第 15 节）。参考 [CRM 报价单](examples/crm-quotation.html)、[单据的显示态和编辑态](examples/object-page.html)、[工作台外框](examples/workbench-shell.html)、[客户列表](examples/customer-list.html)、[记录详情](examples/record-detail.html) 和 [设置](examples/settings.html)。

两层组件对照（同一概念在两层长得一样；工作台只是尺寸更紧凑）：

| 概念 | 基础层 `aham-ui.css` | 工作台 `workbench.css` | 共同写法 |
|---|---|---|---|
| 主按钮 | `.btn--primary` | `.wb-btn.primary` | 底色 `#164EC3`，白字 |
| 次级按钮 | `.btn--secondary` | `.wb-btn`（`.outline` 为兼容写法） | 白底 + 1px 分隔线描边 |
| 文字按钮 | `.btn--ghost` | `.wb-btn.ghost` | 无底无边，悬停次级底色 |
| 危险按钮 | `.btn--danger` | `.wb-btn.danger` / `.ghost.danger` | 红字，不用红底 |
| 分段控件 | `.seg` | `.wb-seg` | 选中扁平灰 + 字重 500，无阴影 |
| 标签 | `.tag`（6px 圆角） | `.wb-chip`（筛选条件，胶囊） | 胶囊只用于筛选条件（DESIGN §8.5） |
| 焦点 | 2px 实线蓝色描边 | 同左 | token `focusRing` |

外框行为在 `workbench.js`（原生 ES 模块，无依赖）：`initShell(root)` 负责导航开关，`initPanels(root)` 负责侧面板开关（按钮 `[data-panel-toggle="面板 id"]`），`openPopover(trigger, panel)` 负责弹层，`initSearch(root)` 负责页眉搜索，`createCommand` / `initFilter` / `initDisplay` 见第 10 节。所有浮层登记在同一个层栈里，Esc 每次只关最上面一层。

基础组件聚合仍为兼容资产；本次恢复了 17 个预览的提取边界并去除页面脚手架污染，但不声称所有历史预览都已完成可访问性和 Token 改造。

交付前执行生成漂移检查、`node scripts/lint-design.mjs` 和 Node 测试，并浏览器检查 1440 / 1024 / 390px、亮 / 暗、输入错误、筛选、密度、保存重载、模拟审批、Tab 焦点和表格横向滚动。检查单是验收要求，不等于未经运行的通过声明。

## 9. 列表页（7.2）

列表页用于记录台账，适用于任何业务对象（客户、订单、工单、物料等）。结构自上而下：页眉第 1 条（位置、视图切换、搜索）→ 页眉第 2 条（条数、筛选、面板开关、显示）→ 筛选行（有条件时才出现）→ 列表滚动区 → 翻页区。右侧可开 400px 预览面板。

结构与交互照 Circle / Linear，颜色、圆角、状态写法照 Aham。组件状态见 [preview/workbench-filter.html](preview/workbench-filter.html)，页面组合见 [examples/customer-list.html](examples/customer-list.html)。

### 9.1 行与分组

- 原生 `table`，默认行高 `rowList` 36px，显示选项可切到 32px；触屏或 640px 及以下为 44px。
- 首列是勾选框，第二列是记录名称，名称固定显示；点击名称打开详情页。数字列右对齐、等宽数字，空值显示「—」。
- 分组时先按分组顺序排列，组内按排序字段排，再分页。分组跨页时，组头写「本页 x / 共 y」。组头可以折叠。
- 表头和分组标题在列表滚动区内吸顶。列标题可点击排序，当前排序列用 `aria-sort` 标记。
- 表头、分组标题与数据行同高，随行高档位一起变（DESIGN §8.13）。表头不加底色，靠字重 500、次级文字色和底边线区分（DESIGN §0）；吸顶时用内容区白底遮住下面滚过的行。
- 状态列用 6px 点 + 文字，不给整行上色。勾选行用灰底。

### 9.2 页眉第 2 条的按钮

从右往左依次是「显示」「面板开关」「筛选」，按钮高 28px。

| 按钮 | 样式 | 状态变化 |
|---|---|---|
| 筛选 | 次级按钮：白底 + 1px 分隔线色描边，图标 + 文字 | 有条件后缩为只剩图标，可访问名写明条件数 |
| 面板开关 | 只有图标的按钮，带可访问名 | 面板打开时灰底（`aria-pressed="true"`） |
| 显示 | 次级按钮，图标 + 文字 | 设置与默认不同时右上角加 6px 墨色圆点，并附隐藏文字「已调整」 |

### 9.3 筛选

- 点「筛选」打开命令菜单（§10）：顶部搜索框，下面是字段列表，每个字段有图标，高亮行右侧显示箭头。选中字段后，同一个弹层切换为该字段的取值编辑，不嵌套弹层。
- 在字段列表里输入选项值，会直接列出「字段 › 值」，并显示条数，勾选即生效（快速筛选）。中文单字即有意义，输入 1 个字就开始匹配。
- 条件生效后，页眉下方出现筛选行：行首是只有图标的「添加筛选」按钮，然后是条件标签，行尾是「清除」。没有条件时整行不出现。
- 条件标签高 28px，中性胶囊（Circle 原形，DESIGN §8.5 写明例外），四段之间用 1px 分隔线：字段（图标 + 名称，字重 500）/ 条件（次级文字色，可点击修改）/ 值（可点击修改）/ 移除（28px 方形按钮）。
- 值的显示：选项只选 1 个时显示该值，选多个时显示「N 个字段名」，悬停显示全部；数字和日期显示值和单位；未填完显示「…」。
- 各类型的条件：文本（包含、不包含），选项（是、不是、是其中之一、不是其中任何一个），数字（不少于、不多于、介于），日期（晚于、早于、介于）。选项的条件随值的个数自动切换单复数，正反向不变。
- 选项在选值菜单里勾选即生效；已选的排在上方，与未选的用分隔线隔开，弹层打开期间顺序不变。文本、数字、日期边输入边生效；输入无效时保留上一次有效条件，并用文字说明错误。介于的上下限填反时自动调换。
- 每个字段最多一条条件，再次选同一字段就是编辑它。
- 条件、视图、搜索词、页码写进网址，可以分享。网址里无法识别、重复或取值非法的条件丢弃，并提示丢弃了几条。

### 9.4 显示选项

- 弹层宽 `popoverForm` 320px，自上而下：分组、排序（字段 + 升降序按钮）、行高（标准 / 紧凑），分隔线，「显示属性」标签组，底部「恢复默认」。
- 显示属性用中性胶囊开关，打开为灰底墨字，关闭为描边次级文字；记录名称固定显示，不在其中。
- 修改即时生效，保存在本机，不写进网址；读取时逐项校验，未知列丢弃，新增列按默认补齐。
- 每页条数放在翻页区，不放进显示选项。

### 9.5 勾选与批量操作

- 勾选后页眉第 2 条换成批量条：已选条数、「清除选择」、批量操作。高度不变，不遮挡列表。
- 本页全选且结果多于本页时，提供「选择全部 N 条」。筛选、搜索或视图变化时清空勾选。
- 批量改属性（如分配负责人）用命令菜单选值。删除等破坏性操作用红色文字按钮，点击后弹确认框（DESIGN §8.6）：取消在左、删除在右，删除不设为默认，焦点落在取消。
- 批量操作需要服务端权限校验、审计记录，以及部分失败时逐条反馈。示例只改页面内数据。

### 9.6 空结果与翻页

- 没有结果时，说明当前条件，并给出适用的恢复入口：清除筛选、清除搜索、查看全部。
- 条数放页眉第 2 条左侧（DESIGN §8.5 计数不置底）。翻页区只放当前范围、每页条数和上一页 / 下一页。
- 筛选、搜索或视图变化时回到第 1 页。

### 9.7 暂未覆盖

列顺序调整、列宽拖拽、固定列、保存查询方案尚未定义。行内编辑、右键菜单、命令面板见第 12 节。

## 10. 命令菜单与工作台浮层（7.2）

命令菜单是工作台的通用选择器，筛选、批量改属性、以后的行内属性编辑和命令面板都用它。形式照 Circle 所用的 cmdk：

- 结构：顶部搜索框（高 `commandInputHeight` 36px，左侧搜索图标，底部 1px 分隔线）→ 列表（最高 300px，超出滚动）→ 无结果提示。选项不超过 4 个时可以不放搜索框。
- 选项行高 32px，13px 文字，左侧可带 16px 图标；同一列表里只要有一行带图标，没有图标的行留出同宽空位对齐。
- 高亮行灰底，随方向键和鼠标移动。多选行左侧有 14px 勾选框：未选时只在高亮行显示，已选为墨色底白勾。选中项不用蓝色。
- 条数用上标、次级文字色、等宽数字，超过 99 显示「99+」。当前项字重 500。
- 键盘：焦点始终在搜索框；↑↓ 循环移动高亮，Enter 选择，Esc 关闭并把焦点还给触发按钮，多步菜单在搜索框为空时按退格返回上一步。
- 读屏：搜索框 `role="combobox"` + `aria-activedescendant`，列表 `role="listbox"`，多选行带 `aria-checked`。

工作台浮层（命令菜单、显示选项、属性选择）在基础 popover 契约上改两处：圆角用 `radius.md` 8px，外加 1px 分隔线色描边。原因是白底浮层压在白底密集列表上时，只靠 6% 透明度的阴影分辨不出边界。其余沿用基础契约：一次只开一个，点外部关闭，不嵌套。

参考实现：`workbench.js` 的 `createCommand`（命令菜单）、`initFilter`（筛选按钮、筛选行、各类取值编辑）、`initDisplay`（显示选项）。产品只需提供字段定义和数据，筛选语义以 `FILTER_OPERATORS` 与 `matchesFilter` 为准，服务端查询按同样的语义实现。

## 11. 列表键盘、预览与记录详情（7.2）

列表 → 预览 → 详情是一条连续的路径，结构与交互照 Circle / Linear。状态见 [preview/workbench-detail.html](preview/workbench-detail.html)，组合见 [examples/customer-list.html](examples/customer-list.html) 与 [examples/record-detail.html](examples/record-detail.html)。

### 11.1 列表键盘

- 列表里只有一行在 Tab 顺序中（当前行），Tab 进入列表就落在当前行，再按 Tab 离开列表。
- 当前行获得焦点时：↑↓ 移动，Home / End 到首尾，空格打开或关闭预览，Enter 打开详情，x 勾选或取消勾选。
- Shift + ↑↓ 从当前行扩展勾选范围；Shift + 点击勾选框，勾选或取消两次点击之间的所有行（照 Linear）。
- 焦点在行内的勾选框、链接或输入框上时，这些键交还给控件本身。输入法组字时不响应。
- 键盘焦点行：浅灰底加 1px 蓝色上下边线，首格加 2px 蓝色左边线（蓝色只用于焦点）。正在预览的行保持浅灰底。
- 列表重绘后焦点回到同一条记录。表格不声明为 ARIA grid，因为单元格不能逐格移动。

### 11.2 预览

- 空格打开 400px 宽面板显示当前行，焦点留在列表里；预览打开时移动当前行，面板内容跟着切换。
- 再按空格、按 Esc 或点面板开关关闭，焦点回到当前行。1024px 及以下，预览浮在列表右侧。
- 预览只放快速判断需要的信息：名称、状态、关键属性。需要编辑或查看完整内容时进入详情。

### 11.3 记录详情页

- 页眉只用 1 条：左侧导航按钮与面包屑（最后一段是记录编码），右侧是「当前位置 / 总数」和上一条、下一条按钮（向上、向下箭头）。位置和上下条来自进入详情前的列表顺序；没有列表上下文时按全部记录排序。面包屑里的列表链接保留原来的筛选和视图。
- 内容区分两栏：左侧可滚动的主内容，居中，最宽 `detailWidth` 720px，四周留 32px；右侧是 240px 属性栏，单独滚动。1024px 及以下属性栏移到主内容后方。
- 主内容自上而下：标题（`detailTitle` 24px，字重 500）→ 摘要行（状态点 + 文字、编码、负责人）→ 正文（行高 1.75）→ 关联记录（36px 行，上下细线）→ 分隔线 → 动态。
- 单据类记录（报价、订单、合同）仍用第 4–6 节的单据页，它们有明细表和金额摘要；本节用于没有明细表的记录。

### 11.4 属性栏

- 分组排列，组名用次级文字色、字重 500，组间距 24px。
- 每行是「标签 + 值」：标签 72px、次级文字色，不可省略（Circle 的事项属性栏省略了标签，Aham 保留）。
- 可编辑的值是按钮：悬停灰底，点击打开命令菜单（§10），当前值字重 500，选中即保存并关闭。只读的值是普通文本，等宽数字。空值显示「设置 + 字段名」，次级文字色。
- 修改属性后在动态里追加一条「谁把什么从 A 改为 B」。真实产品要以服务端保存结果为准，失败时恢复原值并提示。

### 11.5 动态

- 按时间正序排列，最新的在底部，紧挨评论框。
- 事件是一行次级文字：20px 圆形图标底（次级底色）+ 操作人（墨色、字重 500）+ 描述 + 「· 时间」。
- 评论放在次级底色的块里（圆角 8px，内边距 12px，不加边框）：20px 头像 + 姓名 + 时间，下面是正文。
- 评论框在同样的底色块里：多行输入，⌘ / Ctrl + Enter 或「发送」提交，单独按 Enter 换行。内容为空时发送按钮不可用，输入法组字时不提交；提交期间 `aria-busy`，失败时保留内容。
- 动态区用 `role="log"`，新增内容会被读屏播报。

参考实现：`workbench.js` 的 `initListKeys`（范围勾选用 `onRange`）、`initPanels(...).show(id, { layer, returnTo })`、`initPropertyPickers`、`initComposer`。

## 12. 命令面板、快捷键、右键菜单与快速操作（7.2）

页面上的全部操作都能在 ⌘K 命令面板里找到；高频操作另有快捷键、右键菜单或就地控件。各入口调用同一份操作定义。形式照 Circle / Linear，颜色取 Aham。状态见 [preview/workbench-actions.html](preview/workbench-actions.html)。

### 12.1 命令面板

- ⌘K / Ctrl+K 打开或关闭。模态，宽 `overlayWidth` 640px，距视口顶部 `paletteTop` 22vh；搜索框高 48px，选项行高 40px，列表最高 384px，超出滚动。
- 有当前记录时，顶部显示上下文标签（编码 + 名称），命令作用于它；在空搜索框按退格或点标签上的 ✕ 移除上下文。
- 命令按组排列，组名用次级文字色：当前记录 → AI → 操作 → 界面 → 跳转 → 帮助。有快捷键的命令在右侧显示按键提示。
- 带「…」的命令进入子列表（如「修改阶段…」），子列表在面板内原地替换；Esc 或在空搜索框按退格返回上一级，根列表按 Esc 关闭。
- 执行命令后面板关闭，焦点回到打开前的位置，结果用提示条确认。

### 12.2 快捷键

所有快捷键登记在同一张表（`registerShortcut`），帮助列表（按 ? 打开）只显示已登记的按键，不显示未实现的提示。

| 按键 | 作用 | 范围 |
|---|---|---|
| ⌘ / Ctrl + K | 命令面板 | 全局 |
| ⌘ / Ctrl + B | 展开 / 收起导航 | 全局 |
| ? | 键盘快捷键 | 全局 |
| Esc | 关闭最上层的浮层 | 全局 |
| C | 新建当前页的对象 | 列表 |
| / | 搜索 | 列表 |
| ↑ ↓ Home End | 移动当前行 | 列表 |
| 空格 | 预览当前行 | 列表 |
| Enter | 打开当前行 | 列表 |
| X | 勾选当前行 | 列表 |
| Shift + ↑ ↓ | 扩展勾选范围 | 列表 |
| S | 修改状态类主属性（阶段、状态） | 列表当前行、详情 |
| A | 修改负责人 | 列表当前行、详情 |
| Shift + F10 / 菜单键 | 当前行的操作菜单 | 列表当前行 |
| J / K | 下一条 / 上一条 | 详情 |
| E | 编辑当前单据（进入编辑态） | 单据 |
| ⌘ / Ctrl + Enter | 提交评论、新建，或保存编辑态 | 输入区、单据编辑态 |
| → / ← | 展开 / 折叠当前层，或移到子行 / 父行 | 树形明细当前行 |
| + / − / 0 | 放大 / 缩小 / 适合页面 | 文件预览查看区 |
| PageUp / PageDown | 上一页 / 下一页 | 文件预览查看区 |

- 单字母快捷键只在焦点不在输入框、且输入法没有组字时生效；带 ⌘ / Ctrl 的快捷键在输入框里也生效。
- 单字母快捷键可以在设置里整体关闭（WCAG 2.1.4），用 `setSingleKeyShortcuts(false)`，选择记在本机。关闭后，带 ⌘ / Ctrl 的快捷键，以及当前行获得焦点时的方向键、空格、Enter、X 仍然可用。文件预览的 + / − / 0 只在查看区获得焦点时生效，属于控件内部按键，同样不受这个开关影响。
- 表中按键是保留键，产品不得改作他用。新增快捷键先登记，再出现在界面提示里。
- 界面上的按键提示用 20px 高的 `kbd` 标签，⌘ / ⇧ / ⌥ 在 macOS 显示为符号，其他系统显示为 Ctrl / Shift / Alt。

### 12.3 右键菜单

- 在行上右键，或当前行按 Shift + F10 / 菜单键打开；菜单宽 `menuWidth` 256px，出现在鼠标位置或当前行左侧。
- 分组顺序：属性（阶段、负责人…）→ 打开类（打开、预览、勾选）→ 复制类 → 破坏性操作（删除，红色文字，放最后）。
- 有下级选项的项右侧显示箭头，选中后在同一个菜单里换成下级列表，首项是「返回」；← 或退格返回。Aham 不允许级联弹出子菜单。
- 读屏：菜单 `role="menu"`，项 `role="menuitem"`，有快捷键的项带 `aria-keyshortcuts`。

### 12.4 表格内直接改属性

- 状态类和负责人单元格是按钮：悬停灰底，点击在单元格下方打开命令菜单，高亮停在当前值，选中即保存。
- 同样的修改也能从右键菜单、命令面板和快捷键 S / A 进入，四处共用同一个修改函数。
- 修改后用提示条确认并提供「撤销」。真实产品以服务端结果为准，失败时恢复原值并提示原因。

### 12.5 提示条

- 位于右下角（640px 及以下在底部通栏），宽 `toastWidth` 360px，同时最多 3 条。每条 4 秒后消失，鼠标悬停或键盘聚焦时暂停计时。
- 只用于确认已经完成的操作，可带一个操作按钮（撤销、打开）。需要用户修正的错误放在字段旁或行内提示，不用提示条。
- `role="status"`，读屏会播报；不抢焦点。

### 12.6 新建弹窗

- C 或页眉右端的「新建」主按钮打开。模态，宽 640px，距视口顶部 `createTop` 30vh（Circle 的新建弹窗位置）。
- 自上而下：范围标签与标题（如「客户 › 新建记录」）→ 无边框标题输入（20px，字重 500）→ 说明 → 属性标签按钮（点击打开命令菜单）→ 底栏「继续新建」开关、取消、创建（主按钮）。
- ⌘ / Ctrl + Enter 创建。标题为空时在标题下方用文字提示并阻止创建。
- 打开「继续新建」时，创建成功后清空标题和说明、保留属性，焦点回到标题，便于连续录入。
- 有未保存内容时关闭（取消、Esc），在底栏原地换成「继续编辑 / 放弃 / 创建」，焦点落在「继续编辑」；不叠第二个弹窗（DESIGN §8.6「一次只一个模态」）。
- 复杂单据（有明细表、金额）不用快速新建，进入整页单据（第 4 节）。

### 12.7 按钮提示与快捷键提示

- 页眉和工具栏按钮悬停 500ms 或键盘聚焦时，在按钮下方显示提示：名称 + 已登记的快捷键（照 Linear）。
- 样式用 Aham 的深底小提示：墨色底、白字、圆角 4px、浮层阴影；深色主题反色。
- 写法：`data-tooltip="名称"`，有快捷键时加 `data-keys="mod+b"`，按键必须在 §12.2 的表里。纯图标按钮仍要有 `aria-label`，提示不代替可访问名。
- 一次只显示一个；移开、失焦、按 Esc、滚动或点击时消失。粗指针（触屏）不显示。

参考实现：`workbench.js` 的 `initPalette`、`registerShortcut` / `listShortcuts` / `formatKeys` / `setSingleKeyShortcuts`、`initTooltips`、`initContextMenu`、`openPicker`、`toast`、`openCreateDialog`。

## 13. 设置页（7.2）

结构照 Circle 的设置套件（外框 / 分区 / 行），分组方式改为 Aham 的横线分隔。组合见 [examples/settings.html](examples/settings.html)。

- 页面由页眉横条（面包屑「设置 / 当前页」）、左侧 240px 设置导航、右侧设置内容组成。Circle 把设置导航放进全局侧栏；Aham 的导航栏只放业务模块，设置导航放在内容区左列。1024px 及以下，设置导航移到顶部并横向滚动。
- 设置导航按组排列（个人 / 工作区 / 集成），组名次级文字色、字重 500；当前页灰底墨字（`aria-current="page"`）。
- 设置内容居中，最宽 `detailWidth` 720px：页标题 24px 字重 500，下方一行说明；分区之间 32px。
- 分区标题 14px 字重 500，下方一行说明；需要时右侧放分区级操作。
- 分区里的设置行用上下横线分隔，不加卡片边框：左侧是标题（13px 字重 500）和说明（次级文字色），右侧是控件（开关、选择、分段按钮或按钮）。行最小高 64px。
- 单项设置修改后立即生效，用提示条确认；保存失败时恢复原值并提示原因。需要整体校验或会影响他人的配置（审批流程、权限、字段）用整页表单和明确的保存按钮（第 4 节）。
- 恢复默认、删除工作区等破坏性操作放在最后一个分区，按钮用红色文字，点击后弹确认框（DESIGN §8.6）。
- 开关 `.wb-switch`：28 × 16px，打开时墨色底；键盘聚焦显示蓝色焦点环。
- 「常规」分区放「单键快捷键」开关，默认打开；关闭后调用 `setSingleKeyShortcuts(false)`（§12.2）。
- 「AI 助手」分区放「使用 AI 助手」开关，默认关闭，写明会发送什么（§14.6）。

## 14. AI 协作（7.2）

AI 在工作台里负责起草、总结和建议，由人决定是否采纳。性格上延续 Aham 的「对话式」，数据上遵守 DESIGN §1.11 的隐私原则；形式照 Linear 的做法：就地出现、可以撤销、键盘可达。状态见 [preview/workbench-ai.html](preview/workbench-ai.html)，组合见 [examples/record-detail.html](examples/record-detail.html)。

### 14.1 入口

- ⌘K 命令面板里放「AI」一组，排在「当前记录」之后：生成摘要、起草跟进记录、建议下一步。有当前记录时作用于它。
- 就地入口：详情页标题下方的「生成摘要」，评论框底部的「AI 起草」，属性栏里的字段建议。
- 不加悬浮聊天气泡，不占页面角落，也不给 AI 单独的按钮颜色。

### 14.2 标记与依据

- AI 生成的内容带标记：`ai` 图标 + 状态文字，次级文字色。
- 生成区块用次级底色，不加边框和阴影，与评论块同一写法；不用紫色、渐变和发光。
- 结果下方列出依据：用到了哪些记录和动态，最多 3 条。说不出依据时写「未引用具体记录」。
- 采纳后的内容保留来源说明，例如评论头部显示「AI 起草，林悦 确认后发送」。

### 14.3 生成中、完成、停止、失败

- 生成中：正文逐段出现，末尾不加闪烁光标；标题行右侧是「停止」按钮，Esc 也能停止。区块 `aria-busy="true"`。
- 完成：由 `role="status"` 播报一次。底部只有一个主按钮（采纳类，文字按用途写，如「存为评论」「放入评论框」），其余是文字按钮：编辑、重新生成、丢弃。
- 已停止：保留已生成的部分，可以采纳或重新生成。
- 失败：写明原因和「重试」，已有输入不受影响。服务不可用时，页面上的手动操作照常可用。
- 起草类结果不直接发送，先放进输入框，由人修改后再发送。

### 14.4 字段建议

- 建议放在对应字段下方：「建议：商务谈判」+ 一句依据 + 「采纳」「忽略」两个文字按钮。
- 一页一次只显示一条；建议不自动填入字段。
- 采纳走和手动修改相同的路径（§12.4）：立即生效，提示条确认并提供撤销。忽略后本页不再提示同一个值。

### 14.5 修改数据前确认

- AI 提出要改记录（改阶段、分配负责人、新建任务）时，先打开确认弹窗，逐项列出：对象或字段、原值 → 新值，以及共几项。
- 用户确认后才写入；写入后用提示条确认并提供撤销。取消在左、确认在右，确认是主按钮。
- AI 不能直接删除记录。

### 14.6 数据与隐私

- 首次使用前说明会把哪些内容发给哪项服务；由用户开启，默认关闭，开启后可在设置里关闭。工作区管理员可以整体关闭。
- 只发送当前任务需要的内容（当前记录的名称、属性、最近动态），不发送整个列表。
- 生成结果在采纳前只存在于当前页面，不自动写入记录。

参考实现：`workbench.js` 的 `createAIOutput`、`showSuggestion`、`openAIConfirm`、`aiEnabled` / `setAIEnabled`；契约 `ai-output`、`ai-suggestion`、`ai-confirm`。示例文字由 `examples/ai-sample.mjs` 在本地模拟，没有连接任何模型服务。

## 15. 文件、树形明细与版本对比（7.4）

报价、订单、工程变更这类单据常要附图纸、列多层物料、比较前后版本。本节补上四种组合，颜色、字体和状态写法仍按 Aham。状态见 [preview/workbench-files.html](preview/workbench-files.html)。

### 15.1 文件上传

- 拖放区 `.wb-drop` 写明接受的扩展名和单个文件的大小上限。拖入时换成次级底色、描边加深，不用蓝色。
- 拖放区里放「选择文件」按钮，需要整个文件夹时再放「选择文件夹」。键盘和读屏用户经由这两个按钮完成上传。
- 每个文件单独一行（`.wb-file`）：图标、文件名、大小或相对路径、状态、行内操作。行高 36px，只有横线。
- 先在本机检查类型、大小和空文件。未通过的文件写明原因，可以移除，不影响同批其他文件。
- 上传中显示百分比和 4px 墨色进度条，可以取消。失败写明原因，「重试」只重传这一个文件。
- 文件夹按相对路径列出其中的文件。压缩包按一个文件上传，由服务端解压后再按文件列出。
- 「已上传」和识别进度是两个维度，识别结果写在同一行，不混成一个状态（§4）。
- 整批结果用 `role="status"` 播报一次。单个文件的失败写在该行，不用提示条。

### 15.2 文件预览

- Aham 不渲染 PDF 或 CAD。产品把渲染好的页面（图片、canvas 或 SVG）放进 `.wb-viewer-canvas`，翻页时换内容并调用 `load()`。
- 工具栏 `.wb-viewer-bar` 高 40px：左侧是上一页、页码输入、总页数、下一页，右侧是缩小、百分比、放大、「适合页面」「100%」。
- 打开时适合页面，缩放档位 25%–400%。放大后超出查看区时可以拖动平移，也可以用方向键滚动。
- 查看区获得焦点后，+ / − 缩放，0 适合页面，PageUp / PageDown 翻页；⌘ / Ctrl + 滚轮也能缩放。
- 与识别字段对照时用 `.wb-viewer-split`：查看区在左，字段栏在右，宽 `panelWide` 400px。1024px 及以下改为上下排列。
- 字段栏每行是按钮：标签、识别值、来源页。点击或聚焦一行，查看区翻到来源页，用 2px 墨色框标出位置。
- 识别不确定的字段用 `.wb-risk.warning` 写明原因（§1.2），由人对照原图确认，不自动改值。
- 页面保持原稿底色，暗色主题下不反色。需要全屏时用 DESIGN §8.9 的 `.preview--full`。

### 15.3 树形明细

- 用原生 `table`，行上写 `data-id` 和 `data-level`。名称列在最前，每深一层缩进 16px。
- 父行有 20px 的展开按钮（`.wb-tree-toggle`，带 `aria-expanded`），叶子行留同宽空位对齐。表格上方放「全部展开」「全部折叠」。
- 父行的金额是子行之和，用 `treeTotals` 与明细同源计算，不另存一份可编辑的合计。金额用整数分。
- 父行金额字重 500，叶子行 400。折叠后父行仍显示小计，表尾合计等于顶层行之和。
- 键盘沿用列表的行级操作（§11.1）：只有当前行在 Tab 顺序里。↑↓ 移动，→ 展开或移到子行，← 折叠或移到父行，Enter 展开或折叠。
- 表格不声明为 ARIA treegrid，因为单元格不能逐格移动。层级写在展开按钮的可访问名里，叶子行用隐藏文字写明。
- 层级超过 5 层时提供「只看这一层」进入下一级视图，不无限缩进（DESIGN §2.4）。
- 用量连乘、损耗率等业务规则由产品定义。价格过期这类风险按 §1.2 写：单元格只染数值，表格上方写明原因。

### 15.4 版本对比

- 版本选择写成「从 旧版本 到 新版本」，旁边放交换按钮。选项显示版本号、日期和状态，默认比较当前版本与上一版。
- 摘要写成「V2 → V3：新增 1 · 删除 2 · 改动 5」，用 `role="status"` 播报。旁边用分段按钮筛选变化类型。
- 按分类分节，例如表头、明细、条款。每节一张表，列为变化、对象、字段、旧值、新值。没有变化的分类不显示。
- 变化类型写成文字加等宽符号：+ 新增、− 删除、~ 改动。不用红绿色，也不给整行上色。
- 旧值用次级文字色加删除线，并配隐藏文字「原值」；新值字重 500。数值右对齐、等宽。
- 新增行的旧值、删除行的新值写「—」。一个对象改了几个字段，就逐字段各占一行。
- 差异用 `diffRecords` 按主键和字段计算。金额比较整数分，不比较格式化后的文字。
- 两个版本相同时写「选了同一个版本」；筛选后没有结果时，给出切回「全部」的入口。

参考实现：`workbench.js` 的 `initDropZone`、`checkFile`、`formatBytes`、`initViewer`、`initTreeTable`、`treeTotals`、`diffRecords`；契约 `file-upload`、`file-viewer`、`tree-table`、`version-compare`。上传、识别和渲染都要由产品接入，预览页里的进度和识别结果是模拟的。
