@deepseek-ai/dsh-client-ui-primitives 0.1.2-alpha.5 → 0.1.3-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-primitives/README.md
5
- README.md: 78cb6caec665687feb1c18c65e1afe47c6e01ea1
6
- README.zh.md: 1450de83f88e4e33be4616ebd1f70588dd19e8d2
5
+ README.md: 8e579d9467f1ab8c87bfb11d45354f68229fd138
6
+ README.zh.md: 83dce1bdbdab34cde00e7cfb56c648a1d721421f
package/README.md CHANGED
@@ -27,13 +27,49 @@ English | [中文](README.zh.md)
27
27
 
28
28
  Compose feature UI from these atoms whenever the web client needs a standard control or an agent-output renderer. They render through React only and take `--dsw-*` design tokens from the theme, so they fit any plugin without importing the theme or the slot system.
29
29
 
30
+ <a id="component-catalog"></a>
31
+ ### Component catalog
32
+
33
+ Check this table before writing a control in a feature package. A plugin cannot import another plugin's component, so this package is the only place a control can be shared: reuse what fits, and lift a deliberate visual difference into a prop rather than starting a second copy.
34
+
35
+ | Export | What it is |
36
+ |---|---|
37
+ | `Button` | Clickable action; `variant` selects `primary`, `ghost`, `outline`, or `toolbar`. |
38
+ | `Switch` | Two-state toggle, 36×20. `label` is required, so the control cannot ship unnamed. |
39
+ | `Input` | Single-line text entry for search boxes and inline forms. |
40
+ | `Menu` | Dropdown of items, separators, and group labels, with nested submenus. |
41
+ | `Pill` | Selectable capsule button for view switchers and filters; takes `active` and `onClick`. |
42
+ | `Tag` | Read-only capsule badge; `tone` selects one of eight palettes. |
43
+ | `StateDot` | Status mark: `done`, `warning`, `ongoing`, `error`, or `idle`. `aria-hidden`, so the render site owns the name. |
44
+ | `ConnectionIndicator` | Inline connection-recovery control across outage, retry, and recovered states. |
45
+ | `DisclosureRow` | 24px compact disclosure that lays title and content side by side. |
46
+ | `Modal` | Centered dialog over a page mask. |
47
+ | `RiskConfirmation` | Sensitive action gated behind an explicit checkbox. |
48
+ | `OnboardingSurface` | First-run stage that holds the application root inert. |
49
+ | `Tooltip` | Hover text on a cloned anchor, placed right, bottom, or top. |
50
+ | `HoverCard` | Hover preview the pointer can rest on and select from; optional copy button. |
51
+ | `Toast` | Transient top-center banner held for the owner's `holdMs`. |
52
+ | `JsonTree`, `JsonBlock` | Read-only JSON inspection. |
53
+ | `MarkdownText`, `CodeBlock` | Untrusted GFM with TeX math, and highlighted code. |
54
+ | `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, `WebBlock` | The agent-output card matching each tool-result intent. |
55
+ | `icons/*`, `FishLogo`, `BrandWordmark`, `ReferenceIcon`, `LinkIcon`, `DocumentFileIcon` | Glyphs and brand marks, all riding `currentColor`. |
56
+
57
+ Three pairs are easy to confuse:
58
+
59
+ - **`Tag` against `Pill`.** Reach for `Tag` for a read-only badge at the 11px capsule size, and for `Pill` when the capsule is selectable (`active` and `onClick`, as view switchers and filters use) or when it must sit on a 24px text line — `TerminalBlock` renders its exit status as a static `Pill` for exactly that reason. Size decides as much as interactivity here; the two are not interchangeable.
60
+ - **`DisclosureRow` against a card.** The row lays its title and content side by side at a fixed 24px. A card that stacks a name over a description is a different layout, and belongs in the feature package — `ui-settings-plugins`' `PluginCard` is the precedent and records why.
61
+ - **`FoldToggle` against the exported surface.** It is package-internal and not exported; the output cards use it for their head-tail fold.
62
+
63
+ Writing your own component in your own package is fine when the need is genuinely specific. What is not fine is copying a control that already exists here — and once a second package needs the same control, it belongs in this package ([decision](../../../.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.md)).
64
+
30
65
  ### Controls and icons
31
66
 
32
- `Button`, `Pill`, `Input`, `Menu`, `Modal`, `Tooltip`, `DisclosureRow`, `StateDot`, `HoverCard`, `Toast`, `ConnectionIndicator`, `RiskConfirmation`, and the `OnboardingSurface` first-run takeover cover the common interaction shapes. The `ic_ds_*` icon set and `FishLogo`/`BrandWordmark` marks fill brand and inline-icon slots. `ConnectionIndicator` renders a warning-colored disconnected action, a connecting label whose one-to-three dots advance every 500ms independently of retry timing, or a success-colored recovered status. Every state reserves the widest supplied label and uses fixed icon and text columns, so copy changes do not move or resize the control. Its owner supplies visibility, the recovery hold, localized labels, and the immediate-reconnect callback; the primitive uses no native title tooltip. `useAnchoredPosition` and `useAnchoredMaxHeight` keep floating panels and bottom-anchored overlays clamped to the viewport and following their anchor. `HoverCard` keeps its portaled preview reachable across the anchor gap and can expose a copy button through the `copyText` prop. `Toast` holds for the window its owner names through `holdMs`, because how long a banner has to stay depends on how much there is to read; the same value drives its unmount timer and the stylesheet's fade delay, so the two cannot disagree.
67
+ The catalog above lists what each export is for; this section covers the behavior that props alone do not show. The `ic_ds_*` icon set and `FishLogo`/`BrandWordmark` marks fill brand and inline-icon slots. `LinkIcon` draws the leading category glyph for clickable artifact links — globe, folder, code, image, document, or plain paper, all riding `currentColor` — and `classifyLinkPath` derives a file path's category from its extension. `ConnectionIndicator` renders a warning-colored disconnected action, a connecting label whose one-to-three dots advance every 500ms independently of retry timing, or a success-colored recovered status. Every state reserves the widest supplied label and uses fixed icon and text columns, so copy changes do not move or resize the control. Its owner supplies visibility, the recovery hold, localized labels, and the immediate-reconnect callback; the primitive uses no native title tooltip. `useAnchoredPosition` and `useAnchoredMaxHeight` keep floating panels and bottom-anchored overlays clamped to the viewport and following their anchor. `HoverCard` keeps its portaled preview reachable across the anchor gap and can expose a copy button through the `copyText` prop. `Toast` holds for the window its owner names through `holdMs`, because how long a banner has to stay depends on how much there is to read; the same value drives its unmount timer and the stylesheet's fade delay, so the two cannot disagree. `rankByName` is the `/` menu's shared candidate ranker for the command and skill sources: the query must be a case-insensitive ordered subsequence of the name; prefix hits rank first, then alignment score, then source order.
33
68
 
34
69
  ### Rendering agent output
35
70
 
36
- `MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax ([incremental renderer](../../../.agents/notes/implemented/architecture/2026-08-06-web-markdown-incremental-ast-renderer.md), [streaming fence highlighting](../../../.agents/notes/implemented/feature/2026-08-20-web-streaming-fence-highlight.md)). `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, and `WebBlock` render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. `JsonTree` and `JsonBlock` inspect JSON values read-only, while `MessageText` remains the literal-text primitive for user-authored content.
71
+ `MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax. `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, and `WebBlock` render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. `JsonTree` and `JsonBlock` inspect JSON values read-only, while `projectUserText` projects sent user text into inline plain runs and reference chips for the message bubble and queue rows.
72
+
37
73
 
38
74
  ### Localizing copy
39
75
 
@@ -63,7 +99,7 @@ The package is one separation: presentational React atoms with zero Cordis and z
63
99
 
64
100
  ### Streaming markdown
65
101
 
66
- While a reply streams, `MarkdownText` parses incrementally: all but the trailing two blocks freeze as cached React elements and only the source tail re-parses per chunk, so per-chunk work tracks the tail instead of the whole reply. A final unclosed top-level fence keeps its parsed code node and sends only the last completed line plus the current partial line through the same GFM grammar; a closing fence or ambiguous parse returns to the ordinary tail path. Highlighting likewise resumes from saved Shiki grammar state and publishes only newly completed lines plus the mutable tail. `CodeBlock` seals completed lines into fixed-size React groups, reuses earlier groups, and retains the whole highlighted tree across settlement when code and language are unchanged. The settled full parse still resolves references that crossed the freeze boundary ([incremental renderer](../../../.agents/notes/implemented/architecture/2026-08-06-web-markdown-incremental-ast-renderer.md), [streaming fence highlighting](../../../.agents/notes/implemented/feature/2026-08-20-web-streaming-fence-highlight.md)).
102
+ While a reply streams, `MarkdownText` parses incrementally: all but the trailing two blocks freeze as cached React elements and only the source tail re-parses per chunk, so per-chunk work tracks the tail instead of the whole reply. A final unclosed top-level fence keeps its parsed code node and sends only the last completed line plus the current partial line through the same GFM grammar; a closing fence or ambiguous parse returns to the ordinary tail path. Highlighting likewise resumes from saved Shiki grammar state and publishes only newly completed lines plus the mutable tail. `CodeBlock` seals completed lines into fixed-size React groups, reuses earlier groups, and retains the whole highlighted tree across settlement when code and language are unchanged. The settled full parse still resolves references that crossed the freeze boundary.
67
103
 
68
104
  ### Geometry and overflow
69
105
 
@@ -106,7 +142,7 @@ These limits define how the atoms behave at the edges; they are current package
106
142
  - **A long highlighted fence retains its complete token DOM** — streaming avoids re-parsing, re-tokenizing, and reconciling the completed prefix, but it does not discard old colors or virtualize token spans. Final DOM cardinality therefore still follows the fence's token count; nested/container fences and a pathological single long line remain on the general tail path.
107
143
  - **Glyph-level icons are redrawn approximations** — the fish logo and the sparkle mark come from font glyphs whose vector geometry is not exportable from the local design data; hand-authored recreations stand in until an exact export path exists.
108
144
  - **`Pill` and `Input` have no design source** — both atoms are self-defined; the sidebar search field and view-tab strip that resemble them are consumer-owned compositions, not these atoms.
109
- - **No `Active` `StateDot` variant** — the supported states are done, warning, ongoing, and error.
145
+ - **No `Active` `StateDot` variant** — the supported states are done, warning, ongoing, error, and idle.
110
146
  - **User-facing copy is required at the render site** — the atoms are zero-Cordis and cannot reach `ctx.locale`; each feature must supply complete localized labels through the primitive's typed props ([decision](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md)).
111
147
  - **`TerminalBlock` is not a terminal emulator** — it renders settled or still-running command output, not an interactive session: SGR colors, carriage return, backspace, erase-in-line, tab stops, and character width are honored; absolute cursor positioning, screen clearing, and alternate-screen sequences are stripped.
112
148
 
package/README.zh.md CHANGED
@@ -27,13 +27,49 @@ kind: "package-library"
27
27
 
28
28
  只要 Web 客户端需要标准控件或 agent 输出渲染器,就用这些原子组件拼装功能 UI。它们只经 React 渲染,并从主题取得 `--dsw-*` 设计 token,因此无需导入主题或 slot 系统即可适配任意插件。
29
29
 
30
+ <a id="component-catalog"></a>
31
+ ### 组件目录
32
+
33
+ 在功能包里写控件之前,先查这张表。插件无法 import 另一个插件的组件,因此本包是控件唯一可以共享的地方:合适的就复用,有意的视觉差异提升成 prop,而不是另起一份拷贝。
34
+
35
+ | 导出 | 是什么 |
36
+ |---|---|
37
+ | `Button` | 可点击操作;`variant` 选择 `primary`、`ghost`、`outline` 或 `toolbar`。 |
38
+ | `Switch` | 36×20 的双态开关。`label` 必填,控件不可能在没有名称的情况下发布。 |
39
+ | `Input` | 单行文本输入,用于搜索框与行内表单。 |
40
+ | `Menu` | 由条目、分隔线与分组标题构成的下拉菜单,支持嵌套子菜单。 |
41
+ | `Pill` | 可选中的胶囊按钮,用于视图切换与筛选器;接受 `active` 与 `onClick`。 |
42
+ | `Tag` | 只读胶囊徽章;`tone` 选择八种配色之一。 |
43
+ | `StateDot` | 状态标记:`done`、`warning`、`ongoing`、`error` 或 `idle`。它是 `aria-hidden` 的,名称由渲染点提供。 |
44
+ | `ConnectionIndicator` | 行内连接恢复控件,覆盖断线、重试与已恢复三种状态。 |
45
+ | `DisclosureRow` | 24px 紧凑折叠行,标题与内容左右排列。 |
46
+ | `Modal` | 页面遮罩之上的居中对话框。 |
47
+ | `RiskConfirmation` | 以显式复选框把关的敏感操作确认。 |
48
+ | `OnboardingSurface` | 首次运行的引导舞台,期间保持应用根节点 inert。 |
49
+ | `Tooltip` | 克隆锚点上的悬停文本,可置于右、下、上三个方向。 |
50
+ | `HoverCard` | 指针可停留、可选中的悬停预览;可选带复制按钮。 |
51
+ | `Toast` | 顶部居中的瞬时横幅,保持时长由所有者的 `holdMs` 决定。 |
52
+ | `JsonTree`、`JsonBlock` | 只读 JSON 查看。 |
53
+ | `MarkdownText`、`CodeBlock` | 不可信 GFM 与 TeX 数学,以及高亮代码。 |
54
+ | `TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock`、`WebBlock` | 与各类工具结果意图对应的 agent 输出卡片。 |
55
+ | `icons/*`、`FishLogo`、`BrandWordmark`、`ReferenceIcon`、`LinkIcon`、`DocumentFileIcon` | 字形与品牌标识,全部随 `currentColor`。 |
56
+
57
+ 有三组容易混淆:
58
+
59
+ - **`Tag` 与 `Pill`。** 11px 胶囊尺寸的只读徽章用 `Tag`;胶囊可选中(`active` 与 `onClick`,视图切换与筛选器就是这样用的),或者必须落在 24px 文本行上时用 `Pill`——`TerminalBlock` 把退出状态渲染成静态 `Pill` 正是后一种情况。这里尺寸和是否可交互同样是判据,两者不可互换。
60
+ - **`DisclosureRow` 与卡片。** 该行以固定 24px 把标题与内容左右排列。把名称叠在描述之上的卡片是另一种布局,属于功能包——`ui-settings-plugins` 的 `PluginCard` 是先例,并记录了原因。
61
+ - **`FoldToggle` 与对外导出面。** 它是包内组件,未导出;输出卡片用它做头尾折叠。
62
+
63
+ 需求确实特殊时,在自己的包里写自己的组件没有问题。不可以的是复制这里已有的控件——而当第二个包需要同一个控件时,它就该住进本包([决定](../../../.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.zh.md))。
64
+
30
65
  ### 控件与图标
31
66
 
32
- `Button`、`Pill`、`Input`、`Menu`、`Modal`、`Tooltip`、`DisclosureRow`、`StateDot`、`HoverCard`、`Toast`、`ConnectionIndicator`、`RiskConfirmation` 与首次运行接管层 `OnboardingSurface` 覆盖常见的交互形态。`ic_ds_*` 图标集与 `FishLogo`/`BrandWordmark` 标记填充品牌与行内图标 slot。`ConnectionIndicator` 可渲染警告色的断联操作、以独立于 retry 时序的 500ms 节奏推进一至三个点的连接中状态,或成功色的恢复状态。所有状态都为最长的输入 label 预留空间,并使用固定的图标列和文字列,因此文案变化不会移动控件或改变其宽度。它的 owner 提供可见性、恢复驻留时间、本地化 label 与立即重连回调;该原语不使用原生 title tooltip。`useAnchoredPosition` 与 `useAnchoredMaxHeight` 让浮动面板与底部锚定浮层始终钳制在视口内并跟随锚点。`HoverCard` 通过指针离开宽限期让采用 portal 的预览在跨过锚点间隙时仍可触及,并可通过 `copyText` prop 提供复制按钮。 `Toast` 的停留时长由使用方通过 `holdMs` 指定,因为横幅该留多久取决于有多少内容要读;同一个值同时驱动它的卸载定时器与样式表的淡出延迟,两者不可能再错位。
67
+ 上面的目录说明每个导出的用途;本节讲 props 本身看不出来的行为。`ic_ds_*` 图标集与 `FishLogo`/`BrandWordmark` 标记填充品牌与行内图标 slot。`LinkIcon` 为可点击产物链接绘制前置分类图形——地球、文件夹、代码、图片、文档或纸张,全部随 `currentColor`——`classifyLinkPath` 按扩展名推导文件路径的类别。`ConnectionIndicator` 可渲染警告色的断联操作、以独立于 retry 时序的 500ms 节奏推进一至三个点的连接中状态,或成功色的恢复状态。所有状态都为最长的输入 label 预留空间,并使用固定的图标列和文字列,因此文案变化不会移动控件或改变其宽度。它的 owner 提供可见性、恢复驻留时间、本地化 label 与立即重连回调;该原语不使用原生 title tooltip。`useAnchoredPosition` 与 `useAnchoredMaxHeight` 让浮动面板与底部锚定浮层始终钳制在视口内并跟随锚点。`HoverCard` 通过指针离开宽限期让采用 portal 的预览在跨过锚点间隙时仍可触及,并可通过 `copyText` prop 提供复制按钮。 `Toast` 的停留时长由使用方通过 `holdMs` 指定,因为横幅该留多久取决于有多少内容要读;同一个值同时驱动它的卸载定时器与样式表的淡出延迟,两者不可能再错位。 `rankByName` 是 `/` 菜单命令源与 skill 源共享的候选排序器:查询必须是名字的不区分大小写的有序子序列;前缀命中排最前,其次按对齐分数,再按来源顺序。
33
68
 
34
69
  ### 渲染 agent 输出
35
70
 
36
- `MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM([增量渲染器](../../../.agents/notes/implemented/architecture/2026-08-06-web-markdown-incremental-ast-renderer.zh.md)、[流式 fence 高亮](../../../.agents/notes/implemented/feature/2026-08-20-web-streaming-fence-highlight.zh.md))。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`MessageText` 仍是用户创作内容的字面文本原语。
71
+ `MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`projectUserText` 把已发送的用户文本投影为行内普通文本段与引用 chip,供消息气泡和排队行使用。
72
+
37
73
 
38
74
  ### 本地化文案
39
75
 
@@ -63,7 +99,7 @@ kind: "package-library"
63
99
 
64
100
  ### 流式 markdown
65
101
 
66
- 回复流式输出期间,`MarkdownText` 增量解析:除末尾两个块外全部冻结为缓存的 React 元素,每个分片只重新解析其后的源文本尾部,因此每分片的工作量跟随尾部而非整个回复。末尾的顶层未闭合 fence 会保留已解析的 code node,只把最后一个已完成行与当前未完成行交给同一套 GFM grammar;闭合 fence 或有歧义的解析会回到普通尾部路径。高亮同样从保存的 Shiki grammar state 续接,并只发布新完成行与可变尾部。`CodeBlock` 把已完成行封入固定大小的 React 分组、复用更早的分组,并在代码与语言未变化时跨定稿保留整棵高亮树。定稿时的全量解析仍会解析跨过冻结边界的引用([增量渲染器](../../../.agents/notes/implemented/architecture/2026-08-06-web-markdown-incremental-ast-renderer.zh.md)、[流式 fence 高亮](../../../.agents/notes/implemented/feature/2026-08-20-web-streaming-fence-highlight.zh.md))。
102
+ 回复流式输出期间,`MarkdownText` 增量解析:除末尾两个块外全部冻结为缓存的 React 元素,每个分片只重新解析其后的源文本尾部,因此每分片的工作量跟随尾部而非整个回复。末尾的顶层未闭合 fence 会保留已解析的 code node,只把最后一个已完成行与当前未完成行交给同一套 GFM grammar;闭合 fence 或有歧义的解析会回到普通尾部路径。高亮同样从保存的 Shiki grammar state 续接,并只发布新完成行与可变尾部。`CodeBlock` 把已完成行封入固定大小的 React 分组、复用更早的分组,并在代码与语言未变化时跨定稿保留整棵高亮树。定稿时的全量解析仍会解析跨过冻结边界的引用。
67
103
 
68
104
  ### 几何与溢出
69
105
 
@@ -106,7 +142,7 @@ kind: "package-library"
106
142
  - **长高亮 fence 会保留完整 token DOM**:流式路径避免重新解析、重新 tokenize 和 reconcile 已完成前缀,但不会丢弃旧颜色或虚拟化 token span。因此最终 DOM 数量仍随 fence 的 token 数增长;嵌套/容器内 fence 与病态的单个超长行仍走通用尾部路径。
107
143
  - **字形级图标是重新绘制的近似版本**:鱼形标志与闪光标记来自字体字形,而本地设计数据无法导出其矢量几何;在获得精确导出路径前,使用手工重建版本代替。
108
144
  - **`Pill` 与 `Input` 没有设计来源**:两个原子组件均自行定义;与其相似的侧边栏搜索字段和视图标签条由消费方组合,不是这些原子组件。
109
- - **`StateDot` 没有 `Active` 变体**:支持的状态为 done、warning、ongoing 和 error
145
+ - **`StateDot` 没有 `Active` 变体**:支持的状态为 done、warning、ongoing、erroridle
110
146
  - **面向用户的文案必须由渲染点提供**:这些原子组件是 zero-Cordis 的,拿不到 `ctx.locale`;各功能必须通过 primitive 的带类型 prop 提供完整本地化 label(见[决策](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.zh.md))。
111
147
  - **`TerminalBlock` 不是终端模拟器**:它渲染已结束或仍在运行的命令输出,而不是交互式会话:SGR 颜色、回车、退格、行内擦除、制表位与字符宽度会被遵循;绝对光标定位、清屏与备用屏幕序列会被剥离。
112
148
 
@@ -190,6 +190,11 @@
190
190
  background: transparent;
191
191
  }
192
192
 
193
+ /* Fill-mode selection: the row holds the hover fill instead of a check. */
194
+ .selectedFill {
195
+ background: var(--dsw-alias-interactive-bg-hover);
196
+ }
197
+
193
198
  /* Destructive row: error text/icon, danger hover fill. */
194
199
  .danger {
195
200
  color: var(--dsw-alias-state-error-primary);
@@ -44,6 +44,12 @@
44
44
  color: var(--dsw-alias-state-error-primary);
45
45
  }
46
46
 
47
+ /* Idle is the absence of activity, not a fourth outcome: it stays on the
48
+ * tertiary label color so it recedes beside the three outcome colors. */
49
+ .dot[data-state='idle'] {
50
+ color: var(--dsw-alias-label-tertiary);
51
+ }
52
+
47
53
  /* Pixel chase: each outer cell holds a discrete brightness step (flat keyframe
48
54
  * holds, no tweening — the retro feel), peaking when the chase hits it and
49
55
  * decaying over the next three cells. Phase offsets come from per-rect
@@ -0,0 +1,50 @@
1
+ /* The on/off appearance keys off aria-checked rather than a parallel class, so
2
+ * the visual state cannot disagree with the state assistive technology reads.
3
+ *
4
+ * The track is a capsule: its radius is half its height, so the corners consume
5
+ * the whole side and the global superellipse would square the ends off against
6
+ * the round thumb inside it. `corner-shape: round` opts the track out, the way
7
+ * every full-round radius does. The corner-shape spec does not catch this one —
8
+ * it recognizes 50%, 100%, and radii at or above 99px, not a radius that is
9
+ * full-round only relative to its own box. */
10
+ .switch {
11
+ box-sizing: border-box;
12
+ position: relative;
13
+ flex: 0 0 auto;
14
+ width: 36px;
15
+ height: 20px;
16
+ padding: 2px;
17
+ border: 0;
18
+ border-radius: 10px;
19
+ corner-shape: round;
20
+ background: var(--dsw-alias-border-l3);
21
+ cursor: pointer;
22
+ }
23
+
24
+ .switch[aria-checked='true'] {
25
+ background: var(--dsw-alias-brand-primary);
26
+ }
27
+
28
+ .switch:disabled {
29
+ cursor: default;
30
+ opacity: 0.5;
31
+ }
32
+
33
+ .switch:focus-visible {
34
+ outline: 2px solid var(--dsw-alias-brand-primary);
35
+ outline-offset: 2px;
36
+ }
37
+
38
+ .thumb {
39
+ display: block;
40
+ width: 16px;
41
+ height: 16px;
42
+ border-radius: 50%;
43
+ corner-shape: round;
44
+ background: var(--dsw-alias-label-primary-foreground);
45
+ transition: transform 120ms ease;
46
+ }
47
+
48
+ .switch[aria-checked='true'] .thumb {
49
+ transform: translateX(16px);
50
+ }
@@ -0,0 +1,57 @@
1
+ /* Capsule geometry is fixed: a tag reads as one size everywhere, and only its
2
+ * palette varies. Tone colors ride background/border/color so a render site can
3
+ * still position the tag with its own class without touching the palette. */
4
+ .tag {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ border-radius: 999px;
8
+ corner-shape: round;
9
+ padding: 1px 8px;
10
+ font-size: 11px;
11
+ line-height: 17px;
12
+ font-weight: 500;
13
+ white-space: nowrap;
14
+ }
15
+
16
+ .tag[data-tone='outline'] {
17
+ border: 0.5px solid var(--dsw-alias-border-l4);
18
+ color: var(--dsw-alias-label-tertiary);
19
+ }
20
+
21
+ .tag[data-tone='solid'] {
22
+ background: var(--dsw-alias-label-primary);
23
+ color: var(--dsw-alias-bg-layer-3);
24
+ }
25
+
26
+ .tag[data-tone='neutral'] {
27
+ background: var(--dsw-alias-bg-module-platform);
28
+ color: var(--dsw-alias-label-secondary);
29
+ }
30
+
31
+ .tag[data-tone='quiet'] {
32
+ color: var(--dsw-alias-label-tertiary);
33
+ }
34
+
35
+ /* Status tones tint their own color for the fill, so a palette change moves
36
+ * fill and text together and neither needs a second token. The tint is 10%,
37
+ * except `warning`, which keeps the 12% the plugin inventory's conditional
38
+ * tag shipped with — matching it is what makes this a pure consolidation. */
39
+ .tag[data-tone='success'] {
40
+ background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);
41
+ color: var(--dsw-alias-state-success-primary);
42
+ }
43
+
44
+ .tag[data-tone='info'] {
45
+ background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent);
46
+ color: var(--dsw-alias-state-business-primary);
47
+ }
48
+
49
+ .tag[data-tone='warning'] {
50
+ background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);
51
+ color: var(--dsw-alias-state-warn-primary);
52
+ }
53
+
54
+ .tag[data-tone='danger'] {
55
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);
56
+ color: var(--dsw-alias-state-error-primary);
57
+ }
@@ -52,14 +52,27 @@
52
52
  }
53
53
 
54
54
  .sourceLink {
55
- color: var(--dsw-alias-state-business-primary);
55
+ color: var(--dsw-alias-link);
56
56
  font-size: 14px;
57
+ font-weight: 500;
57
58
  line-height: 20px;
58
59
  word-break: break-word;
60
+ text-decoration: none;
59
61
  }
60
62
 
61
- .sourceLink:hover {
62
- text-decoration: underline;
63
+ /* Leading URL glyph inside source and fetch links; rides the link color.
64
+ 1.1em follows each link's own font size. */
65
+ .linkIcon {
66
+ width: 1.1em;
67
+ height: 1.1em;
68
+ vertical-align: -0.25em;
69
+ margin-right: 5px;
70
+ }
71
+
72
+ .sourceLink:hover,
73
+ .sourceLink:focus-visible {
74
+ text-decoration: underline dotted;
75
+ text-underline-offset: 3px;
63
76
  }
64
77
 
65
78
  .snippet {
@@ -95,15 +108,19 @@
95
108
  }
96
109
 
97
110
  .fetchUrl {
98
- color: var(--dsw-alias-state-business-primary);
111
+ color: var(--dsw-alias-link);
99
112
  font-family: var(--ds-font-family-code);
100
113
  font-size: 13px;
114
+ font-weight: 500;
101
115
  line-height: 19px;
102
116
  word-break: break-all;
117
+ text-decoration: none;
103
118
  }
104
119
 
105
- .fetchUrl:hover {
106
- text-decoration: underline;
120
+ .fetchUrl:hover,
121
+ .fetchUrl:focus-visible {
122
+ text-decoration: underline dotted;
123
+ text-underline-offset: 3px;
107
124
  }
108
125
 
109
126
  .fetchMeta {