Appearance
功能规格说明:桌面对话流排版、表格与链接
创建日期:2026-09-10
本规格是「桌面对话流排版」的权威来源(此前这些规则只散落在走查台账
docs/desktop-density-restore.md与工作目录外的设计契约里,代码注释却以其为据)。走查过程、前后实测数据与截图仍留在台账中,本文件只规定规则与语义。
作用范围(严格区分,勿混用):
- 视觉规则(字体角色、字号行高、表格外观与对齐、链接字形与下划线时机、焦点环)一律只在桌面端生效(
[data-host="desktop"]层);IDE 宿主(VS Code / JetBrains)保持 base 观感不变。 - 结构与无障碍改进(复选框可访问名、折叠标题改为按钮并带展开态、表格包裹层)在三个宿主共用;桌面端给出可见焦点样式,IDE 端外观与行为不变。
- 本轮新增的键盘 tab 停靠点只在桌面端:焦点环样式只在桌面端层提供,IDE 端不得出现「能聚焦但看不出焦点在哪」的停靠点。
用户场景与测试
用户故事:对话流文字角色(优先级:P1)
作为桌面端用户,我希望对话流里每一类文字都落在明确定义的角色上——阅读正文、代码、辅助信息各用各的字体与字号行高——这样长回答读起来有节奏、代码片段不混排、时间数量等次要信息不喧宾夺主。
为什么是这个优先级:角色表是全部排版规则的基准,表格、链接、工具输出都挂在它上面;基准不定,后续三条故事无从验收(P1)。
独立测试:构造包含标题/段落/列表/引用/表格/行内代码/代码块/bash 命令与输出/写入预览/diff/lsp 输出/工具结果块/时间统计的助手消息,逐元素读计算样式验证字体栈(等宽判别:同一元素内 iiiii 与 WWWWW 宽度比 1.0 为等宽、≈0.25 为比例字体)与字号行高;切换浅深两主题验证同值。
验收场景:
- 假设助手消息包含 Markdown 阅读正文(段落、列表项、引用、标题、表格单元格),当渲染完成,则使用 UI 字体角色(与输入框、工具行、推理块同一字体栈),不继承编辑器等宽字体;助手错误文案随其所属正文角色一并落到 UI 字体(其等宽是继承副作用,不是代码角色绑定)。
- 假设消息中出现任何行内代码、围栏代码块,或 bash 命令与输出、写入预览、diff、lsp 输出,当渲染完成,则一律使用代码角色:等宽字体、13px 字号、20px 行高;三类「行内代码 / 代码块 / 代码块内代码」不得各走各的取值。
- 假设消息中出现时间、数量、依赖等辅助信息(工具参数摘要、写入统计行、工具结果块一族、diff 省略行),当渲染完成,则使用辅助角色:12px 字号、20px 行高;不得停留在 11px 这类低于角色表全部档位的取值。
- 假设一段文字位于表格单元格内或其它嵌套层级中,当渲染完成,则其字号行高取自角色表的绝对值,不因嵌套深度连乘(如表格 0.9em × 行内代码 0.9em)而缩到角色档位之外。
- 假设容器自身声明了行高(如用户气泡、引用),当渲染完成,则内外层一致为正文角色的整数行盒(22px),不出现 19.6 / 21 这类非整数行高。
- 假设消息中包含错误提示或工具失败提示,当查看其字形,则不使用斜体(角色表不含斜体角色,中文斜体尤其伤可读性);真正的原始堆栈仍由代码角色承载。
- 假设表格单元格文本比同列其它格更长,当渲染完成,则允许在格内按词折行、不得裁切或省略文字(不出现
text-overflow: ellipsis/ 隐藏内容来减少行数)。 - 假设消息中包含 Mermaid 图表,当走查字号,则图表 SVG 内的文字不属于上述角色表覆盖范围(不要求其跟随辅助/代码角色),避免为迁就 SVG 破坏图形布局。
用户故事:表格展示与滚动(优先级:P1)
作为桌面端用户,我希望表格有清晰的边框与圆角、表头与命令区同一底色、没有干扰阅读的斑马纹,并且列宽按内容分配、放不下时表格区域内横向滚动——而不是被压成「一字一列」的竖排。
为什么是这个优先级:表格是 AI 回答里最常见的结构化输出,「能读」是底线;字号修正后若不解决列宽压缩,可读性反而下降(P1)。
独立测试:构造 4 列、8 列、含长路径与长说明的富单元格表,在 1440 / 994 / 480 三档窗口下测列宽、单元格折行与断字、表格滚动量、页面是否被撑宽;统计「折行单元格数」「最窄内容列宽」。
验收场景:
- 假设表格自然宽度超出消息列,当渲染完成,则表格被一层横向滚动包裹层承接(
overflow-x: auto),表格在本区域内横向滚动,页面本身不产生横向滚动(画布scrollWidth == clientWidth);滚到最右时末列内容完整可见。 - 假设表格处于横向滚动状态,当键盘用户 Tab 到该区域(桌面端),则滚动区可聚焦并给出可见焦点环,可用方向键横向翻看完整内容。
- 假设表格宽度足够放下,当查看列宽,则按内容分配:短 token 单元格(分类/状态/序号/数值/日期等短词)保持自身自然宽度、不被挤成逐字竖排,长内容列吸收剩余空间;不使用三等分、固定百分比或纯
max-content之类把整表推出容器的策略。 - 假设单元格内是长且不可断的内容(URL、邮箱、路径、长英文串),当列宽不足,则才在该单元格内断行(仅在放不下时断),普通说明文字则按词自然折行;不得对所有内容统一使用「任意位置可断」——那会把最小内容宽度降到 1 字符,导致浏览器持续从长内容列抽走宽度、把短列压成竖排。
- 假设查看表格外观,则表格容器为圆角、单元格保留右/下细描边(末列与末行的重复边去掉,四角由表头首末格与末行首末格承担),表头底色与 bash 命令面使用同一语义填充色,并移除斑马纹(含其半透明叠加,该叠加曾把浅色行正文对比压到不可接受水平)。
- 假设表格单元格内出现行内代码或链接,当渲染完成,则其字形跟随各自角色(代码角色 / 链接角色),列宽与换行策略不因此改变。
- 假设同一表格中同时存在「短 token」与「长 token」两类单元格,当注入列宽分类标记,则同一单元格可同时带「长度分类」与「对齐分类」两类标记,各管各的(
white-space与text-align互不冲突)。
用户故事:表格对齐(优先级:P1)
作为桌面端用户,我希望未声明对齐的表格默认左对齐、数值比较列右对齐、编号与日期等标识列不被误判为数值列,多行说明所在行的短内容贴顶——而不是表头居中、正文左对齐这种同列两种对齐。
为什么是这个优先级:表头默认居中是浏览器 UA 样式与产品样式叠加出的错误默认值,属「一眼可见的不专业」;同时它决定表格是否可被逐列比较阅读(P1)。
独立测试:构造未声明对齐的普通表、显式声明左/中/右的对齐演示表、混合表(含单格数字、图标列、操作列、多行说明列),逐列读表头与单元格的水平对齐,统计「同列表头与正文对齐不一致的列数」(应为 0);读单元格垂直对齐。
验收场景:
- 假设Markdown 表格未声明任何列对齐,当渲染完成,则表头与单元格同为左对齐——取消浏览器 UA 给表头的居中默认值,恢复「默认左对齐」。
- 假设表格任意一列,当查看该列表头与正文的水平对齐,则二者一致(使用同一条列级判定,同一列同一个值)。
- 假设某列列头命中可比量关键词(数量/个数/条数/耗时/时长/内存/体积/金额/占比/百分比/覆盖率等)且该列整列非空单元格都能解析为数值,当判定列类型,则该列右对齐;容忍千分位、小数、正负号、比较符、货币前缀与单位后缀,
—/N/A/待定视为缺失不影响判定;任一条件不满足即保持左对齐。 - 假设某列列头是标识类(编号/序号/号/ID/版本/ver/电话/手机/传真/日期/时间/date/邮箱/端口/卡号/邮编),当判定列类型,则强制左对齐,不因整列都是数字就右对齐。
- 假设某列整列非空单元格都是图标(如 ✅/⚠️/❌),或列头明示为操作/动作/action 且整列都是无空白短词(如 查看/编辑/重试),当判定列类型,则该列居中;只有这两种情形才居中。
- 假设列类型判定,当实现,则以整列为单位:不按列序、不按表结构、不按具体表格内容,也不因为某一个单元格是数字就推断整列是数值列。
- 假设表中存在多行正文单元格撑高整行,当查看同行其它短内容,则垂直顶部对齐(不再被垂直居中到行中间)。
- 假设Markdown 显式声明了列对齐(
| :--- | :--: | ---: |),当渲染完成,则保留显式声明(左/中/右各自生效),不被全局默认规则覆盖;AI 生成的表格内容、行列与对齐声明零改写,本规格只约束渲染默认值。
用户故事:链接角色与下划线时机(优先级:P1)
作为桌面端用户,我希望正文里的描述性链接看起来就是正文、而直接展示的网址/邮箱/电话看起来是可精确辨认的地址,并且链接在鼠标悬停或键盘聚焦时给出下划线提示。
为什么是这个优先级:链接是对话流里唯一的交互文本载体,「是不是链接」「能不能点」必须一眼可辨;判据错了会把「联系我们」渲染成地址、或把地址渲染成正文(P1)。
独立测试:构造描述性链接(查看预览/参考文档/发送邮件/联系我们/拨打电话)、直显地址(http(s) URL、mailto:/tel: 串、直接显示的邮箱、直接显示的电话)、日期样文本、代码内路径链接,逐元素读字体栈与下划线;用真实键盘 Tab 路径验证 :focus-visible 命中;核对每个链接的 href 逐字节未变。
验收场景:
- 假设链接的可见文字是描述性短语(如「查看预览」「参考文档」「发送邮件」「联系我们」「拨打电话」),当渲染完成,则使用正文 UI 角色(与所在正文一致),不加地址类标记;点击后按该链接既有规则打开(本规格不改跳转语义)。
- 假设链接的可见文字本身就是地址——scheme 地址(
http(s)://///)、显式mailto:/tel:串、直接显示的邮箱、直接显示的电话号码,当渲染完成,则使用代码角色:等宽字体、13px 字号,并保留长地址换行能力(不撑破消息区域)。 - 假设要判断一条链接是不是「直显地址」,当实现判定,则判据是可见文字的含义而不是有没有
//;判定不依赖某一条消息的具体内容。 - 假设正文里出现日期样文本(如
2026-09-10),当判定是否电话,则不被误判为电话号码。 - 假设链接的可见文字与
href不同源(label 与 href 不一致),当渲染完成,则字形判定只看可见文字,href原样输出、跳转行为不变。 - 假设链接处于静态态,当查看其字形,则**无下划线(仅靠颜色区分);假设鼠标悬停或键盘聚焦(
:focus-visible),则显示下划线(下划线偏移与粗细有明确取值)并加深颜色**(深色主题下改为提亮以体现同一反馈)。 - 假设正文里由纯文本自动链接化的文件路径或代码内的链接,当查看其下划线时机,则与正文内联链接同一时机(静态无、hover / focus 有);链接的颜色与是否下划线不改变其可点击性。
用户故事:键盘可达与无障碍(优先级:P1)
作为使用键盘或读屏的用户,我希望能聚焦到每一处可滚动区域并看到焦点位置、能知道复选框勾选状态、能通过键盘展开/收起折叠块——而不是「内容能看却摸不到」。
为什么是这个优先级:可滚动区域不可聚焦、复选框无可访问名、折叠标题不是按钮,都是 WCAG 层面的硬缺陷(分别对应 2.1.1、4.1.2),且已由 axe 报出 critical/serious(P1)。
独立测试:对渲染结果跑 axe(浅深两主题)核对 label 与 scrollable-region-focusable 归零;用真实键盘 Tab 逐个走到代码块、命令输出、写入预览、表格滚动区、折叠标题,验证可达与可见焦点;对折叠标题用 Enter / Space 验证展开态切换;读 DOM 验证复选框属性。
验收场景:
- 假设消息中存在可滚动的代码块、bash 命令输出、写入预览、表格滚动区,当键盘用户 Tab(桌面端),则该区域可聚焦并显示可见焦点环;IDE 宿主不新增该类停靠点(无对应焦点样式,不得出现「看不见焦点」的停靠点)。
- 假设用户在代码块或命令输出内聚焦后,当按下方向键,则可滚动查看完整内容(内容不被静默裁切)。
- 假设消息中包含 Markdown 复选框(任务列表),当读 DOM,则每个复选框都有可访问名(已完成 / 未完成),且保留其勾选状态载体、不把控件隐藏或从无障碍树中移除。
- 假设消息中存在可折叠块(推理过程、压缩工具行等),当查看元素,则折叠标题是按钮(含展开态属性),键盘可达,Enter / Space 均可切换展开态并同步更新展开态属性;其外观与改造前一致(旧宿主同样不退化为浏览器默认按钮样式)。
- 假设新增任一元素属性依赖 HTML 净化白名单(可访问名属性、tab 停靠点属性、表格包裹层元素与类名),当实现,则必须同时放行到净化白名单——否则属性/包裹层会被静默剥掉,修复到不了 DOM 而单测与走查却看不出差异。
用户故事:桌面状态色语义 token(优先级:P2)
作为桌面端用户,我希望「进行中 / 已完成 / 失败 / 待确认 / 空闲」这类状态色在浅色与深色主题下各自取到正确的值,而不是深色沿用浅色值导致发灰难辨。
为什么是这个优先级:状态色不是本轮排版主线,但本轮把 JS 侧的状态色从硬编码色值改为引用语义 token;token 只定义在一个主题块、或缺一档,会让对应主题静默回退到错误颜色(P2)。
独立测试:在浅色与深色主题下渲染带运行中/已完成/失败/空闲状态的任务列表状态点(含行图标)与时间线节点,读计算颜色;并断言 JS 侧引用的每个状态 token 在浅色块与深色块都有定义。
验收场景:
- 假设界面处于浅色或深色主题,当渲染任务列表状态点与行图标、工具状态文字/图标、时间线节点,则颜色取自同一套语义状态 token(进行中 / 已完成 / 失败 / 待确认 / 空闲),不在这几处再写硬编码色值。
- 假设查看桌面语义 token 层,当核对状态色,则浅色档与深色档各自定义:深色档不沿用浅色档取值(深色下取下调亮度/降低饱和度的对应档位),使状态色在两主题下都可辨。
- 假设JS 侧引用某个状态 token,当该 token 只在单一主题块定义或缺失,则属于缺陷(应有守卫测试覆盖 JS 引用集合与 CSS 定义集合的一致性),避免退化为「某一主题下静默取到错误色」。
- 假设IDE 宿主不定义桌面状态 token,当在 IDE 中渲染同一状态指示,则回退到既有取值,观感不回归。
边界情况
- Mermaid 图表 SVG 内文字不在角色表覆盖范围:其字号由图形自身布局决定,不要求跟随辅助/代码角色。
- 流式输出中的未闭合表格 / 未闭合代码围栏:Markdown 尚未解析成完整结构,装饰(包裹层、对齐类、行内样式)不生效,不承诺中间态的排版。
- 极端内容:表格内嵌 Mermaid、超长无空格 token、超长电话(带分机号)、label 含内联标签(如
**http://x**)或 label 与 href 不同源,均按「判定只看可见文字、不猜列类型」的通用规则处理,未逐例验收。 - 窄窗口 + 分屏组合:994px 分屏下 8 列表的少数列可能拿到较窄剩余宽度并多折几行(短列按自然宽占满后剩余空间有限);彻底解法需给长内容列设保底宽度,与「优先自然换行、横滚只作兜底」冲突,故不引入。
- 列类型判定无命中时默认左对齐,不根据单个单元格猜整列类型;标识类列头即使整列为数字也保持左对齐。
- 缩放与文字间距:编辑器 200% 缩放、WCAG 1.4.12 文字间距未覆盖。
- IDE 宿主真机:IDE 端结论以宿主标记代理验证(视觉规则不生效、共用结构变化外观不变),未做真机走查。
- 表格单元格可同时带长度分类与对齐分类两个标记,前者管断行、后者管对齐,互不冲突。
- 对比度:链接与正文的颜色对比、行内代码与正文字号差,均受下节「已知偏离」约束。
- 状态色覆盖范围:上述状态色 token 目前只被工具状态文字/图标、任务列表状态点与行图标、时间线节点消费;侧边栏会话树自绘的「等待确认 / 已完成未读」状态点不在此集合内,仍是独立取值(见
desktop-sessions.md的状态点语义)。
已知偏离(用户明示接受)
正文内联链接静态态无下划线导致的对比度不达标:链接与正文文字的颜色对比在浅色主题约 2.9:1、深色主题约 1.99:1,均低于 WCAG 1.4.1 在「仅靠颜色区分」时要求的 3:1。因此自动化无障碍检查会报出 link-in-text-block(serious,浅深各 7 个节点);改为「静态态即显示下划线」时该告警降到 1 个节点。
该项是用户 2026-09-10 决策的结果(「常态无下划线,hover / focus 时显示」),不计为验收失败。后续若要恢复合规,可选其一:① 回到「静态态显示下划线」;② 提高链接色与正文的对比度至 ≥3:1。除此之外,本规格范围内的自动化无障碍检查(复选框可访问名、可滚动区域键盘可达)应为零违规。
相关文档
- 表格与分栏的容器几何、面板宽度:见 desktop-panels.md。
- 逐轮走查过程、前后实测数据与截图清单:见台账
docs/desktop-density-restore.md(其「对话流排版」「表格展示」「表格对齐」「批 1 · 无障碍」各节)。