mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-11-09 20:07:43 +08:00
Merge remote-tracking branch 'origin/master' into worktree/continue-generation-after-failure
This commit is contained in:
+2
-2
@@ -15,8 +15,8 @@
|
||||
en: 3aef37bb4f8a8e53
|
||||
zh: a7b477324d486ab3
|
||||
/agent-note-sandboxed-node-execution-for-ptc/decision/control-and-lifetime:
|
||||
en: 2c7a694b04951772
|
||||
zh: 133b95b0477b127f
|
||||
en: a8897bae00f5f674
|
||||
zh: 2c959353c5a885a4
|
||||
/agent-note-sandboxed-node-execution-for-ptc/decision/resource-limits:
|
||||
en: 3f1d8a30f6d48243
|
||||
zh: faa7c01d7c959893
|
||||
|
||||
@@ -28,7 +28,7 @@ The [experimental Python provider](../../../../packages/experimental/ptc-runtime
|
||||
|
||||
The subprocess owner supplies a dedicated inherited binary control channel, separate from program stdout/stderr and launcher lifecycle IPC. The host bounds frames, queued writes, pending calls and outstanding argument bytes, then validates call identity and the binding allowlist before dispatch. Model code can write to that channel, so its bytes remain untrusted.
|
||||
|
||||
Program completion, timeout, cancellation and protocol failure all close execution through the managed process owner. Result selection stops the execution timer; cleanup then waits for the direct outcome and managed-range quiescence. The PTC bridge separately aborts and drains nested tool dispatches before its outer tool result settles. A denial or transport failure never automatically replays a program whose effects may already have occurred.
|
||||
For temporary executions, program completion, timeout, cancellation and protocol failure all close execution through the managed process owner. Result selection stops the execution timer; cleanup then waits for the direct outcome and managed-range quiescence. The PTC bridge separately aborts and drains nested tool dispatches before its outer tool result settles. A denial or transport failure never automatically replays a program whose effects may already have occurred.
|
||||
|
||||
### Resource limits
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Host 在子进程启动时保留 `ELECTRON_RUN_AS_NODE`;bootstrap 在求值前
|
||||
|
||||
子进程所有者提供专用的继承式二进制控制通道,与程序 stdout/stderr 及 launcher 生命周期 IPC 分开。Host 限制帧、排队写入、待处理调用和未完成参数字节,然后在分派前验证调用身份与绑定允许列表。模型代码可以写入该通道,因此其中字节仍不可信。
|
||||
|
||||
程序完成、超时、取消与协议失败都通过受管进程所有者关闭执行。选择结果后停止执行计时器;清理随后等待直接结果与受管范围停稳。PTC 桥接在外层工具结果结算前,另行取消并排空嵌套工具分派。拒绝或传输失败绝不自动重放可能已经产生副作用的程序。
|
||||
对于临时执行,程序完成、超时、取消与协议失败都通过受管进程所有者关闭执行。选择结果后停止执行计时器;清理随后等待直接结果与受管范围停稳。PTC 桥接在外层工具结果结算前,另行取消并排空嵌套工具分派。拒绝或传输失败绝不自动重放可能已经产生副作用的程序。
|
||||
|
||||
### 资源限制
|
||||
|
||||
|
||||
+4
-4
@@ -36,8 +36,8 @@
|
||||
en: 21a74f9031f86a92
|
||||
zh: c6abd0a5dfbf56a8
|
||||
/agent-note-definition-owned-grouping-with-generic-node-group-assembly/verification:
|
||||
en: b42409d029fc0fde
|
||||
zh: 0f8ab7d206cab772
|
||||
en: ff829b9a2772df95
|
||||
zh: df0e6524d00034df
|
||||
/agent-note-definition-owned-grouping-with-generic-node-group-assembly/consequences:
|
||||
en: 96f1d81b28534661
|
||||
zh: 8cb1c7a0f000c335
|
||||
en: 236ddec90acc6ca1
|
||||
zh: d4807f635e217766
|
||||
|
||||
@@ -159,7 +159,7 @@ Business behavior is verified through keyed-update regressions and recorded Web
|
||||
| Evidence | Required observation |
|
||||
|---|---|
|
||||
| Keyed notifications and React updates | Content growth affects the owning Node and affected Group; unchanged historical rows and unrelated Turns receive no extra notifications. Mode changes preserve keys and member parents. |
|
||||
| Recorded Web replay | Current accessible titles, group disclosure, hidden ordinary Context, trigger notices, and footer placement match the committed expected output. |
|
||||
| Recorded Web replay | Current accessible titles, group disclosure, Context rows, trigger notices, and footer placement match the committed expected output. |
|
||||
|
||||
[Recorded Web scenarios](../../../../apps/web/tests/steering.e2e.ts) cover online steering, reconnect handoff, and grouped presentation through the shipped Web profile. These behavior checks do not establish a quantified latency or memory improvement.
|
||||
|
||||
@@ -171,4 +171,4 @@ Business behavior is verified through keyed-update regressions and recorded Web
|
||||
- Stable mounting does not eliminate layout, paint, or retained-memory costs. No measured latency or optimal-performance claim is made.
|
||||
- Real group splits, merges, first-member changes, and pagination repairs can change identity. The mode-switch guarantee does not prohibit those legitimate changes.
|
||||
- Revealing hidden parts, selection/copy, interruption notices, group disclosure, and outer Turn interaction belong to Chat's business adaptation; generic reference tests alone do not establish those behaviors.
|
||||
- Turn status/duration replaces the old tool/message-count title, so status, accessible names, and empty-process Turns require explicit presentation coverage. Hiding ordinary Context retains non-human waking notices and original Session/Trajectory inspection; footer placement depends on the real Turn end rather than group membership.
|
||||
- Turn status/duration replaces the old tool/message-count title, so status, accessible names, and empty-process Turns require explicit presentation coverage. Non-human waking notices remain independent of Context rows, and Session/Trajectory inspection is unchanged; footer placement depends on the real Turn end rather than group membership.
|
||||
|
||||
@@ -159,7 +159,7 @@ React key 由引用 kind、NodeKey/groupPart 或 GroupKey 的无歧义元组派
|
||||
| 证据 | 必须观察到的结果 |
|
||||
|---|---|
|
||||
| 按键通知与 React 更新 | 正文增长影响所属 Node 和相关 Group;未变化历史行及其他 Turn 没有额外通知。模式变化保留 key 与成员父级。 |
|
||||
| 已录制的 Web 回放 | 当前可访问标题、组开合、普通 Context 隐藏、触发通知及页脚位置与提交的预期输出一致。 |
|
||||
| 已录制的 Web 回放 | 当前可访问标题、组开合、Context 行、触发通知及页脚位置与提交的预期输出一致。 |
|
||||
|
||||
[已录制的 Web 场景](../../../../apps/web/tests/steering.e2e.ts)通过正式 Web profile 覆盖在线 steering、重连接续与分组展示。这些行为检查不构成量化延迟或内存改善的证明。
|
||||
|
||||
@@ -171,4 +171,4 @@ React key 由引用 kind、NodeKey/groupPart 或 GroupKey 的无歧义元组派
|
||||
- 稳定挂载不消除布局、绘制或保留内存的成本,不宣称实测延迟或最优性能。
|
||||
- 真实组拆并、首成员变化及分页修正可以改变身份,模式切换保证不禁止这些正常变化。
|
||||
- 隐藏部分的揭示、选择复制、中断提示、组开合与外层 Turn 联动属于 Chat 业务适配,仅凭通用引用测试不能确认这些行为。
|
||||
- 整轮状态与用时替代旧工具/消息计数标题,因此状态、可访问名称和空过程轮次需要明确的展示覆盖。隐藏普通 Context 时保留非人工唤醒通知及原始 Session/Trajectory 查看能力;页脚定位依赖真实 Turn 结束,不依赖分组成员。
|
||||
- 整轮状态与用时替代旧工具/消息计数标题,因此状态、可访问名称和空过程轮次需要明确的展示覆盖。非人工唤醒通知独立于 Context 行,Session/Trajectory 查看能力不变;页脚定位依赖真实 Turn 结束,不依赖分组成员。
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: e42db40b879fdbf4
|
||||
zh: 6017da9c4da17835
|
||||
/agent-note-node-ptc-execution-modes-and-in-memory-state/decision:
|
||||
en: 7b5808a31493ac1e
|
||||
zh: 8d9c698c41dbf18e
|
||||
en: 411054071757d5fe
|
||||
zh: e683ef7d80f33fc6
|
||||
/agent-note-node-ptc-execution-modes-and-in-memory-state/alternatives-considered:
|
||||
en: 396157a9fc293198
|
||||
zh: a061f05b4423e26f
|
||||
|
||||
@@ -18,7 +18,7 @@ One npm package and one plugin entry keep existing installation and loading path
|
||||
|
||||
The Session records submitted programs, nested tool calls and returned output. It does not serialize the REPL heap or rerun earlier programs after restart, resume or fork: replaying automation could repeat external side effects. A missing variable is a normal program error, and the model-visible instructions require recreating variables and reacquiring resources. No environment identifier or new durable Session event is required because the provider exposes no unlogged model input.
|
||||
|
||||
Cancellation, deadline expiry, protocol or output-limit failure, owner disposal and process exit discard the environment. Ordinary exceptions retain earlier mutations. A retained function uses the active evaluation's host bindings; asynchronous work from an earlier evaluation cannot acquire a later evaluation's tool authority or output sink.
|
||||
Protocol or output-limit failure, owner disposal and process exit discard the environment. The [interruption decision](2026-10-29-retained-ptc-interruption.md) owns timeout and cancellation recovery with retained state. Ordinary exceptions retain earlier mutations. A retained function uses the active evaluation's host bindings; asynchronous work from an earlier evaluation cannot acquire a later evaluation's tool authority or output sink.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Status: implemented
|
||||
|
||||
Session 记录提交的程序、嵌套工具调用和返回输出。它不序列化 REPL 堆,也不在重启、恢复或派生后重新执行旧程序,因为重放自动化可能重复外部操作。缺失变量产生普通程序错误,模型指引要求重新创建变量并获取资源。提供方没有引入日志之外的模型输入,因此不需要环境标识或新的持久 Session 事件。
|
||||
|
||||
取消、截止时间到期、协议失败、输出超限、所有者销毁和进程退出都会丢弃环境。普通异常保留此前发生的修改。保留的函数使用当前执行的宿主绑定;较早执行中的异步任务不能获取后续执行的工具权限或向其输出。
|
||||
协议失败、输出超限、所有者销毁和进程退出都会丢弃环境。[中断决策](2026-10-29-retained-ptc-interruption.zh.md)负责保留状态的超时和取消恢复。普通异常保留此前发生的修改。保留的函数使用当前执行的宿主绑定;较早执行中的异步任务不能获取后续执行的工具权限或向其输出。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
+6
-6
@@ -9,14 +9,14 @@
|
||||
en: 8112c1c059d1cf10
|
||||
zh: 852d23c56a93a555
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/decision:
|
||||
en: 90d74300f045bf6e
|
||||
zh: 088e88944478ca2a
|
||||
en: 3cf1a8f986cea871
|
||||
zh: 3105cf2608108ecd
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/deferred-naming-cleanup:
|
||||
en: 4c428f65ef611d07
|
||||
zh: 1fdc5467b0203d08
|
||||
en: 7f59c9d14a36e133
|
||||
zh: 2e7829c59b4fcd24
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/alternatives-considered:
|
||||
en: 65af55b5775c9bc5
|
||||
zh: 2ea75b3fd33ac0c2
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/consequences:
|
||||
en: e8e96b0354f1a3c6
|
||||
zh: ad1bfe3600e14ff1
|
||||
en: 08613c24e7c552ab
|
||||
zh: 6f2509d3e61cb2c7
|
||||
|
||||
+3
-4
@@ -10,7 +10,7 @@ Moving a capability into the experimental directory changes its ownership and su
|
||||
|
||||
## Decision
|
||||
|
||||
The moved packages retain the published names declared by the [experimental naming policy](../../../../scripts/experimental-package-policy.ts). That typed directory-to-name map is the sole maintained inventory of naming exceptions. Package manifests, consumers, installation checks, and bundler checks agree with it; a missing, moved, or duplicated exception is rejected. [Experimental status](../../../../packages/experimental/README.md#status) remains independent of spelling, Official discovery, and explicit selection.
|
||||
The moved packages retain the published names declared by the [experimental naming policy](../../../../scripts/experimental-package-policy.ts). That typed directory-to-name map is the sole maintained inventory of unprefixed experimental names; under the [unprefixed naming decision](2026-11-04-unprefixed-experimental-package-names.md), every new experimental package joins it. Package manifests, consumers, installation checks, and bundler checks agree with it; a missing, moved, or duplicated entry is rejected. [Experimental status](../../../../packages/experimental/README.md#status) remains independent of spelling, Official discovery, and explicit selection.
|
||||
|
||||
Existing badge, Ralph, and native delegation row IDs remain valid Host row selectors. New isolation groups and newly introduced capabilities retain their new IDs. Later user layers still replace configuration or disable rows. Selecting an optional bundle is still explicit: a patch targeting an absent row does not select its bundle.
|
||||
|
||||
@@ -20,10 +20,9 @@ Compatibility protection covers Web and Desktop profiles, including existing nat
|
||||
|
||||
| Retained identity | Unconstrained cleanup | Why it is deferred |
|
||||
|---|---|---|
|
||||
| Published names in the naming-exception map | Give every experimental package the `@deepseek-ai/dsh-experimental-` prefix | Existing dependencies, imports, module declarations, and profile files use the established names |
|
||||
| Existing badge, Ralph, and native-tool row IDs | Give optional contributions consistently distinct `optional-*` selectors | Existing user overrides use those IDs; the ownership move does not require a new selector |
|
||||
|
||||
These naming changes are deferred, not prerequisites for experimental classification. A later cleanup requires an explicit release and migration decision, an inventory of affected Web/Desktop configurations, and installed-profile upgrade evidence. Directory moves or pre-stable API status alone do not retire these exceptions. The package list stays in the typed policy rather than being copied into this note or package READMEs.
|
||||
These naming changes are deferred, not prerequisites for experimental classification. A later cleanup requires an explicit release and migration decision, an inventory of affected Web/Desktop configurations, and installed-profile upgrade evidence. Directory moves or pre-stable API status alone do not retire these row IDs. The package list stays in the typed policy rather than being copied into this note or package READMEs.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -35,6 +34,6 @@ These naming changes are deferred, not prerequisites for experimental classifica
|
||||
|
||||
## Consequences
|
||||
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides and disabled state. No general alias or automatic profile migration mechanism is introduced.
|
||||
The repository carries a typed map of unprefixed experimental names and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides and disabled state. No general alias or automatic profile migration mechanism is introduced.
|
||||
|
||||
The [classification decision](../process/2026-10-05-product-package-classification.md), [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md), and [on-demand discovery decision](2026-10-05-official-on-demand-bundles.md) retain their separate responsibilities. Released Session generations and persisted hook event names remain protected independently of this profile compatibility scope.
|
||||
|
||||
+3
-4
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
迁移的包保留[实验包命名策略](../../../../scripts/experimental-package-policy.ts)声明的已发布名称。这个带类型的目录到包名映射是命名例外唯一手工维护的清单。包清单、消费者、安装检查和打包检查与其保持一致;缺失、迁移到其他位置或重复归属的例外会被拒绝。[实验状态](../../../../packages/experimental/README.zh.md#status)仍独立于名称拼写、Official 发现和显式选择。
|
||||
迁移的包保留[实验包命名策略](../../../../scripts/experimental-package-policy.ts)声明的已发布名称。这个带类型的目录到包名映射是不带前缀的实验包名唯一手工维护的清单;按照[不带前缀命名决策](2026-11-04-unprefixed-experimental-package-names.zh.md),每个新实验包都加入其中。包清单、消费者、安装检查和打包检查与其保持一致;缺失、迁移到其他位置或重复归属的条目会被拒绝。[实验状态](../../../../packages/experimental/README.zh.md#status)仍独立于名称拼写、Official 发现和显式选择。
|
||||
|
||||
既有徽章、Ralph 和原生委派行 ID 作为 Host 行选择器保持有效。新增隔离组和新引入的能力保留各自的新 ID。后续用户层仍可替换配置或禁用行。可选 bundle 仍须显式选择:指向缺失行的 patch 不会自动选择其 bundle。
|
||||
|
||||
@@ -20,10 +20,9 @@ Status: implemented
|
||||
|
||||
| 保留的身份 | 不受兼容性约束时的清理 | 暂缓原因 |
|
||||
|---|---|---|
|
||||
| 命名例外映射中的已发布名称 | 为每个实验包使用 `@deepseek-ai/dsh-experimental-` 前缀 | 既有依赖、导入、模块声明和 profile 文件使用原有名称 |
|
||||
| 既有徽章、Ralph 和原生工具行 ID | 为可选贡献统一使用独立的 `optional-*` 选择器 | 既有用户覆盖使用这些 ID;归属迁移不要求新的选择器 |
|
||||
|
||||
这些命名变更被暂缓,不是实验分类的前提。后续清理需要明确的发布与迁移决策、受影响 Web/Desktop 配置清单,以及已安装 profile 的升级验证。仅目录迁移或公共 API 处于稳定前阶段,不足以删除这些例外。包清单保留在带类型的策略中,不复制到本记录或各包 README。
|
||||
这些命名变更被暂缓,不是实验分类的前提。后续清理需要明确的发布与迁移决策、受影响 Web/Desktop 配置清单,以及已安装 profile 的升级验证。仅目录迁移或公共 API 处于稳定前阶段,不足以删除这些行 ID。包清单保留在带类型的策略中,不复制到本记录或各包 README。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -35,6 +34,6 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置和禁用状态的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
仓库维护带类型的不带前缀实验包名映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置和禁用状态的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
|
||||
[分类决策](../process/2026-10-05-product-package-classification.zh.md)、[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)和[按需发现决策](2026-10-05-official-on-demand-bundles.zh.md)继续承担各自独立的职责。已发布 Session 代际和持久化 hook 事件名的保护独立于此 profile 兼容范围。
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-29-retained-ptc-interruption.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-29-retained-ptc-interruption.md
|
||||
/agent-note-retained-ptc-interruption:
|
||||
en: 482adc6f938bc494
|
||||
zh: 2bcc4856df4fb39e
|
||||
/agent-note-retained-ptc-interruption/problem:
|
||||
en: 1d2ac1280f14aa80
|
||||
zh: c5875c914cf23d73
|
||||
/agent-note-retained-ptc-interruption/decision:
|
||||
en: 77f44a7ce832934c
|
||||
zh: 4744934c60ca6f3d
|
||||
/agent-note-retained-ptc-interruption/alternatives-considered:
|
||||
en: 8c57872ffaf660b2
|
||||
zh: 4765cbf561ea5dc9
|
||||
/agent-note-retained-ptc-interruption/consequences:
|
||||
en: f679322d48a740e9
|
||||
zh: 4a255df6e019a32c
|
||||
@@ -0,0 +1,23 @@
|
||||
# Agent Note: Retained PTC interruption
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-29-retained-ptc-interruption.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Terminating a retained Node process on every timeout or cancellation loses unrelated application objects and imported modules. A control handler on the evaluation thread cannot stop a synchronous loop. The [in-memory execution decision](2026-09-23-best-effort-persistent-ptc.md) still owns disposable state and Session behavior; this decision replaces its timeout and cancellation cleanup policy.
|
||||
|
||||
## Decision
|
||||
|
||||
The host revokes execution authority immediately and requests cooperative stop over the existing control channel. The runtime rejects pending bindings; the original `run()` response confirms that evaluation has settled. Revocation is a one-way notification. The ordered pipe and single active evaluation determine notification ownership; RPC requests associate replies with callers. Acknowledgment permits reuse of the VM and retained objects; earlier mutations remain. Callers await acknowledgment or fallback process cleanup before starting another evaluation.
|
||||
|
||||
The host bounds acknowledgment with `stopGraceMs`. Missing or failed acknowledgment terminates the managed process range and awaits cleanup. Results retain the original timeout or cancellation kind and report whether variables survived. Confirmed termination allows a fresh environment on the next call; failed cleanup blocks reuse until host release succeeds. Explicit reset, disposal and policy invalidation terminate immediately. Reset uses the existing release path and exposes no additional model tool.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
Unconditional termination unnecessarily discards state during ordinary asynchronous waits. Cooperative stop alone cannot bound synchronous loops. Inspector termination is unsuitable as a state-preserving default: local probes on Node 24.9 and 26.6 crashed native async-context handling when terminating an awaited VM loop with an enabled async hook; Node 24 REPL also crashed without an explicit user hook. A successful Inspector command does not establish that the runtime remains reusable. Any future forced in-process interruption must demonstrate safe recovery with the supported Node versions and async hooks.
|
||||
|
||||
## Consequences
|
||||
|
||||
Canceled tools throw normally, allowing user `catch` and `finally` handlers to run. A blocked handler or unresolved user Promise prevents acknowledgment and triggers fallback termination. Timers, arbitrary user Promises, I/O callbacks and user-created Workers or subprocesses have no per-evaluation cancellation guarantee and can still mutate retained state after acknowledgment. Acknowledgment confirms revocation of managed execution, not cancellation of all Node activity. Fallback termination loses all state in that process; neither path rolls back external effects. Host reset remains available when retained resources cannot be trusted.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Agent Note: 保留状态的 PTC 中断
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-29-retained-ptc-interruption.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
每次超时或取消都终止保留的 Node 进程,会丢失无关的应用对象与导入模块。执行线程上的控制处理器无法停止同步死循环。[内存执行决策](2026-09-23-best-effort-persistent-ptc.zh.md)仍负责可丢弃状态与 Session 行为;本决策替代其中的超时与取消清理策略。
|
||||
|
||||
## 决策
|
||||
|
||||
宿主立即撤销本次执行的权限,并通过现有控制通道请求协作式停止。运行时拒绝未完成的绑定,由原 `run()` 响应确认该次执行已结束。撤销是单向通知。有序管道和唯一的活动执行确定通知归属;RPC 请求将回复关联到调用方。收到确认后可复用 VM 和保留对象;此前发生的修改继续存在。调用方等待确认或兜底进程清理完成后,才能开始下一次执行。
|
||||
|
||||
宿主通过 `stopGraceMs` 限制确认等待时间。未收到确认或确认失败时,终止受管进程范围并等待清理。结果保留原来的超时或取消类型,并报告变量是否保留。确认终止后,下一次调用可以创建新环境;清理失败则阻止复用,直到宿主释放成功。显式重置、销毁和策略失效立即终止进程。重置复用现有释放路径,不向模型增加工具。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
无条件终止会在普通异步等待期间丢弃本可保留的状态。仅靠协作式停止无法限制同步死循环。Inspector 终止不适合作为保留状态的默认机制:本地探针在 Node 24.9 和 26.6 启用 async hook 后,终止 await 后的 VM 死循环会导致原生异步上下文处理崩溃;Node 24 REPL 在未显式启用用户 hook 时也会崩溃。Inspector 命令成功不能说明运行时仍可复用。未来若引入进程内强制中断,必须验证支持的 Node 版本及 async hooks 下能安全恢复。
|
||||
|
||||
## 影响
|
||||
|
||||
取消的工具正常抛错,允许用户的 `catch` 和 `finally` 运行。处理器阻塞或用户 Promise 无法完成时,不能确认停止,将触发兜底终止。定时器、任意用户 Promise、I/O 回调及用户创建的 Worker 或子进程不承诺按执行取消,收到确认后仍可能修改保留状态。确认表示受管执行已撤销,不表示所有 Node 活动均已取消。兜底终止会丢失该进程中的全部状态;两条路径均不回滚外部效果。保留资源不可信时,仍可使用宿主重置。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-11-04-unprefixed-experimental-package-names.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-11-04-unprefixed-experimental-package-names.md
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix:
|
||||
en: b84bbfa015f35105
|
||||
zh: dff2c32694314996
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/problem:
|
||||
en: 84638e34c7b1d291
|
||||
zh: 0c3928b3424db016
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/decision:
|
||||
en: 0f13619da3b05747
|
||||
zh: fac563c2a8b61270
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/alternatives-considered:
|
||||
en: 1c6a08a813dcb3a7
|
||||
zh: 16bf31184377c80b
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/consequences:
|
||||
en: 2b536c42672e8850
|
||||
zh: f6a551964855308e
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Name new experimental packages without the experimental prefix
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-11-04-unprefixed-experimental-package-names.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
An experimental package named `@deepseek-ai/dsh-experimental-<name>` must be renamed when it moves to a product group. The rename changes every import, dependency entry, module declaration, and profile file that names the package, so promotion carries a migration that is unrelated to the capability's behavior.
|
||||
|
||||
## Decision
|
||||
|
||||
New experimental packages use `@deepseek-ai/dsh-<name>` and never the `experimental` prefix. Each one is declared in `EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS` in the [experimental package policy](../../../../scripts/experimental-package-policy.ts), because isolation checks and plugin-record ownership recognize an unprefixed experimental name only through that map. Promotion moves the package and removes its map entry; its npm name stays the same.
|
||||
|
||||
Existing packages with the prefix are listed in `LEGACY_PREFIXED_EXPERIMENTAL_PACKAGE_DIRECTORIES`. `pnpm run constraints` rejects the prefix in any other directory and rejects a listed directory that no longer holds a prefixed package, so the list can only shrink. A legacy package is renamed when it is promoted; renaming it earlier needs the release and migration evidence that the [plugin identity decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) requires.
|
||||
|
||||
This decision replaces the deferred cleanup that the plugin identity decision recorded, which gave every experimental package the prefix. The [package rules](../../../../packages/experimental/AGENTS.md) state the naming rule for authors.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the prefix as the default.** The name shows experimental status, but each promotion then renames the package and migrates its consumers and installed profiles.
|
||||
|
||||
**Rename the legacy packages now.** One rename removes the legacy list, but it changes published names that existing dependencies and profiles use, without a promotion that needs the change.
|
||||
|
||||
**Document the rule without a gate.** A new package copied from a legacy one would keep the prefix with every check passing.
|
||||
|
||||
## Consequences
|
||||
|
||||
The unprefixed-name map grows with every new experimental package; the legacy list only shrinks. Directory placement and the map, not the npm name, identify an unprefixed package as experimental, and an unprefixed name does not grant admission to the default product.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: 新实验包命名不使用 experimental 前缀
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-11-04-unprefixed-experimental-package-names.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
名为 `@deepseek-ai/dsh-experimental-<name>` 的实验包迁入产品分组时必须重命名。重命名会改变引用该包名的每个导入、依赖项、模块声明和 profile 文件,因此晋升会附带一次与能力行为无关的迁移。
|
||||
|
||||
## 决策
|
||||
|
||||
新实验包使用 `@deepseek-ai/dsh-<name>`,不使用 `experimental` 前缀。每个新实验包都在[实验包命名策略](../../../../scripts/experimental-package-policy.ts)的 `EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS` 中声明,因为隔离检查和插件记录归属只通过这个映射识别不带前缀的实验包名。晋升时移动该包并删除其映射条目;其 npm 名称保持不变。
|
||||
|
||||
带前缀的既有包列在 `LEGACY_PREFIXED_EXPERIMENTAL_PACKAGE_DIRECTORIES` 中。`pnpm run constraints` 拒绝其他目录中出现该前缀,也拒绝已列出但不再包含带前缀包的目录,因此该清单只能缩减。遗留包在晋升时重命名;提前重命名需要[插件身份决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)要求的发布与迁移依据。
|
||||
|
||||
本决策取代插件身份决策记录的暂缓清理,该清理为每个实验包加上前缀。[包规则](../../../../packages/experimental/AGENTS.md)向作者说明命名规则。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**默认保留前缀。** 名称能体现实验状态,但每次晋升都要重命名该包,并迁移其消费方和已安装 profile。
|
||||
|
||||
**现在重命名遗留包。** 一次重命名即可删除遗留清单,但会在没有晋升需要的情况下改变既有依赖和 profile 使用的已发布名称。
|
||||
|
||||
**只记录规则,不设门禁。** 从遗留包复制的新包会保留前缀,而所有检查仍会通过。
|
||||
|
||||
## 后果
|
||||
|
||||
不带前缀包名映射随每个新实验包增长;遗留清单只会缩减。识别不带前缀的包是否为实验包依据的是目录位置和该映射,而不是 npm 名称;不带前缀的名称也不因此获准进入默认产品。
|
||||
@@ -18,11 +18,11 @@
|
||||
en: 4cd76a34f4712e1a
|
||||
zh: 303ef72a18644d7c
|
||||
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/the-run-code-tool-and-the-dispatch-bridge:
|
||||
en: 6a0e2698b082978e
|
||||
zh: fbb03503520b30e9
|
||||
en: c3d079330748cd24
|
||||
zh: 4e2cc41d00227780
|
||||
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/observability-tool-ptc-dispatch:
|
||||
en: b8e4cf9433c7ee18
|
||||
zh: e5f4e8d7d03d3a7b
|
||||
en: 5423da23d4e8d1d9
|
||||
zh: f1198d4f224f3fe6
|
||||
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/the-ptc-runtime-seam:
|
||||
en: 4c423f4e9cef6055
|
||||
zh: 5c23a3637eb420c0
|
||||
|
||||
@@ -56,13 +56,15 @@ Under `'ptc'` the registry owns `run_code` as a reserved presentation transport
|
||||
|
||||
**Concurrency is bounded, not serialized.** Each run owns a dispatch queue that starts calls strictly in submission order and classifies each one through `registry.executionMode`, the same fail-closed `isConcurrencySafe` contract the native loop uses. Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (default 10; `1` restores serial dispatch); an exclusive call drains the pool and runs alone. Settlement abandons queued calls that have not started. This note shipped the serialized placeholder; the [live-parallel reference](../../../../packages/core/tools/README.md) owns the scheduler that replaced it.
|
||||
|
||||
**Tool-call allowance.** Counting attempts before validation and queuing bounds loops that repeatedly fail or enqueue work. The allowance scales with the resolved timeout and permits bursts; it does not enforce a rolling window. Exhaustion uses the run-scoped abort path so a program cannot catch a binding failure and keep running. Active calls are canceled, queued calls are abandoned, and settlement waits for cleanup. Completed effects remain; a new run receives a fresh allowance. The [executor README](../../../../packages/core/tool-executor-ptc/README.md#use-this-package) owns configuration and failure reporting.
|
||||
|
||||
**Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so the TUI and host/client runtime (Web) complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md).
|
||||
|
||||
### Observability: `tool/ptc-dispatch`
|
||||
|
||||
Each sub-dispatch appends a log-only `tool/ptc-dispatch-start` event at pool entry and a `tool/ptc-dispatch` settle event containing parent and child call ids, tool identity, normalized arguments, and the complete rendered `content`/`isError` outcome. It remains outside model history but available to persistence and UIs. Appends occur inside the open `run_code` turn. Direct executions without an agent still run but cannot log the event.
|
||||
|
||||
New sub-calls use `<parent>:ptc:<n>` ids, numbered in submission order. All call ids are opaque to consumers: migration preserves every historical id byte-for-byte, including `:code:` substrings, so dispatch pairs, spill references, and other correlations remain intact. The bridge attributes forwarded image context to `{ kind: 'plugin', plugin: 'tools-ptc' }`.
|
||||
New sub-calls use `<parent>:ptc:<n>` ids, numbered by tool-call attempt. Invalid arguments consume a number without a dispatch event, so logged ids may have gaps. All call ids are opaque to consumers: migration preserves every historical id byte-for-byte, including `:code:` substrings, so dispatch pairs, spill references, and other correlations remain intact. The bridge attributes forwarded image context to `{ kind: 'plugin', plugin: 'tools-ptc' }`.
|
||||
|
||||
The [V2-to-V3 PTC specification](../../../../packages/session/session-format-v2-to-v3/README.md#ptc-vocabulary) owns exact historical tag and attribution conversion; [native V3 admission](../../../../packages/session/session-format-v2-to-v3/README.md#native-v3-admission) owns predecessor-tag refusal. These are not runtime aliases: an opaque extension must not acquire PTC lifecycle meaning merely through a version change.
|
||||
|
||||
|
||||
@@ -56,13 +56,15 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一
|
||||
|
||||
**并发是有界的,而非被序列化。** 每次 run 拥有一个分发队列,严格按提交顺序启动调用,并通过 `registry.executionMode` 对每个调用分类——与原生循环所用的 fail-closed `isConcurrencySafe` 约定相同。连续的 parallel 类调用最多重叠 `maxParallelSubCalls` 个(默认 10;设为 `1` 恢复串行分发);exclusive 类调用会排空池并单独运行。结算时放弃尚未开始的排队调用。本 note 交付的是被序列化的占位实现;取代它的调度器由[实时并行 参考](../../../../packages/core/tools/README.zh.md) 负责。
|
||||
|
||||
**工具调用额度。** 在验证和排队之前对尝试计数,可以限制重复失败或持续入队的循环。额度随最终超时时间缩放,允许集中调用,不实施滑动窗口限制。额度耗尽时使用运行级取消路径,使程序无法捕获绑定错误后继续运行。活动调用被取消,排队调用被放弃,结算会等待清理完成。已完成的操作仍然生效;新运行获得完整额度。[执行器 README](../../../../packages/core/tool-executor-ptc/README.zh.md#use-this-package) 负责配置与失败报告。
|
||||
|
||||
**呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.zh.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy spill 预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md)。
|
||||
|
||||
### 可观测性:`tool/ptc-dispatch`
|
||||
|
||||
每次子分发在进入分发池时追加一个仅日志的 `tool/ptc-dispatch-start` 事件,并以一个 `tool/ptc-dispatch` 结算事件收尾,后者包含父子 call id、工具标识、规范化参数以及完整渲染后的 `content`/`isError` 结果。它不进入模型历史,但可供持久化和 UI 使用。追加发生在开放的 `run_code` 轮次内。没有 agent 的直接执行仍然运行,但无法记录该事件。
|
||||
|
||||
新子调用使用 `<parent>:ptc:<n>` 标识,按提交顺序编号。消费者将所有 call id 视为不透明值:迁移逐字节保留每个历史标识,包括 `:code:` 子串,因此分发事件对、spill 引用及其他关联保持完整。桥接层将转发图片上下文的来源标记为 `{ kind: 'plugin', plugin: 'tools-ptc' }`。
|
||||
新子调用使用 `<parent>:ptc:<n>` 标识,按工具调用尝试编号。无效参数会占用编号而不产生分发事件,因此日志中的编号可能有空缺。消费者将所有 call id 视为不透明值:迁移逐字节保留每个历史标识,包括 `:code:` 子串,因此分发事件对、spill 引用及其他关联保持完整。桥接层将转发图片上下文的来源标记为 `{ kind: 'plugin', plugin: 'tools-ptc' }`。
|
||||
|
||||
[V2 到 V3 PTC 规范](../../../../packages/session/session-format-v2-to-v3/README.zh.md#ptc-vocabulary)负责精确的历史标签与归属转换;[原生 V3 准入](../../../../packages/session/session-format-v2-to-v3/README.zh.md#native-v3-admission)负责前代标签拒绝。这些不是运行时别名:不透明扩展不能仅因版本变化就获得 PTC 生命周期含义。
|
||||
|
||||
|
||||
@@ -1,76 +1,118 @@
|
||||
---
|
||||
name: dsh-client-ui-ux
|
||||
description: Design and review DeepSeek Harness client UI changes — visual token discipline, reuse-before-adding, feedback surfaces (toast vs in-place notice vs empty state), overlay and menu safety, platform window adaptation, loading states, and error copy placement. Use when adding or changing product-user-visible GUI behavior in packages/client, or when reviewing such a PR.
|
||||
description: "Design and review DSH user-visible UI: alignment, spacing, icons, visual foundation, geometry and interaction. Use before UI changes in client, experimental and app packages; verify affected rendered results before handoff."
|
||||
---
|
||||
|
||||
# DeepSeek Harness Client UI/UX
|
||||
# DeepSeek Harness UI development
|
||||
|
||||
This skill is guidance, not a complete checklist. It covers judgment calls that lint, typecheck, and the i18n gate cannot make: where feedback appears, what gets reused, and how surfaces behave at window edges and across platforms. Copy quality itself follows [dsh-prose-standard](../dsh-prose-standard/SKILL.md); locale ownership follows the [locale-owned copy decision](../../notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md); styling ownership and token rules live in [docs/web-styling.md](../../../docs/web-styling.md) and the [ui-primitives component catalog](../../../packages/client/ui-primitives/README.md#component-catalog) — this skill adds judgment on top of those rules, not a second copy of them.
|
||||
Use this skill as the entry point for UI design and review. It owns the workflow, visual foundation, geometry and interaction guidance; alignment, spacing and icon details live in its references below. Load only the references relevant to the task; examples explain judgment, not coverage.
|
||||
|
||||
## Architecture prerequisites
|
||||
<a id="required-review-loop"></a>
|
||||
## UI change workflow
|
||||
|
||||
Component structure, state flow, and effect usage follow the architecture rules; read them before redesigning a component:
|
||||
Use applicable design rules for UI changes, including visible loading, error, disabled, expanded and focus states. Choose effort by impact and regression risk; ordinary development does not require a full design audit.
|
||||
|
||||
- [packages/client/AGENTS.md](../../../packages/client/AGENTS.md) — Slot and props discipline, reactive read, export, ctx, and layering rules.
|
||||
- [Client component rules](../../../docs/defensive-patterns/client-components.md) — those requirements stated with the defects they prevent.
|
||||
| Change | Required inspection | Additional records |
|
||||
|---|---|---|
|
||||
| Local copy, icon reuse, color or spacing | Inspect the actual page, affected neighbors and relevant states; run applicable tests. Apply the minimum checks below. | Briefly report results and material gaps. No separate plan, axis drawing, scene inventory, source snapshot or screenshot report is required by default. |
|
||||
| Page layout, shared components or global styles | Identify affected relationships and consumers; inspect distinct affected layout, content and state variants in assembled pages. Reuse evidence only for justified equivalents. | Briefly state scope. Preserve the starting version and comparable images when requested or needed to explain broad changes or assess regression risk. |
|
||||
| Explicit comprehensive review | Cover the agreed regions and relevant states; distinguish inspected results, defects and uninspected scope. | Use the [review cookbook](../../../docs/cookbook/reviewing-ui-design.md) for coverage records, findings and supporting evidence. A comparison request alone does not require a product-wide audit. |
|
||||
|
||||
## Visual foundation
|
||||
### Minimum checks for affected UI
|
||||
|
||||
- **Use existing tokens for color, radius, and spacing.** Before introducing a literal value, search the target page and its nearest established sibling (the Plugins page is the usual reference for full-page surfaces) for the token or value already in use, and adopt it. The same applies to whole treatments — toasts, menus, in-message artifact previews, sidebar tab layout, colors, clearance rules, page padding: reuse the established component or rule wherever one exists instead of inventing a parallel one.
|
||||
- **Verify every color in both light and dark mode.** An ink/background pair must stay readable under both themes before it ships. Dimmed tones are the standing counter-example: they suit decorative surfaces, but on text they routinely fall below readability in one theme — never put a dimmed color on text without checking both.
|
||||
- **Font weight tops out at 500 in feature CSS you add or change.** Do not use 600+ for emphasis; differentiate with size or ink instead. The markdown typography tokens owned by ui-theme are the standing exception.
|
||||
- **Pick font sizes from the theme typography variables when an existing role matches, otherwise from the page's existing scale.** Parallel content shares one size; never invent a size for a single element, and keep the number of distinct sizes on a page small (13px is the established row/detail size). When the page itself has no precedent, borrow the size and spacing ratios of the most similar existing page or element.
|
||||
- **Icons, text, and neighbouring elements stay proportionate and aligned.** Every element lines up with its siblings along a deliberate axis (baseline, vertical center, or shared edge), and an icon's size stays in proportion to the text beside it; never ship an element that aligns to nothing or visibly outweighs its neighbours.
|
||||
These checks apply even to small changes; optional documentation does not make inspection optional. Open the actual page and inspect the changed element with its directly affected neighbors, rather than judging CSS or an isolated component alone.
|
||||
|
||||
- **Color or opacity:** inspect light and dark themes, including affected foreground/background and state combinations.
|
||||
- **Layout, spacing, typography or sizing:** inspect a constrained container and long or wrapping content; check both sides of a changed breakpoint, alignment, clipping and access to actions.
|
||||
- **Interaction:** exercise the changed path and affected focus, disabled, loading, error, expanded and overlapping states. Check only states the change can affect, not every possible combination.
|
||||
- **Shared components or global styles:** trace affected consumers and inspect distinct assembled layouts and states; one isolated example cannot establish coverage.
|
||||
|
||||
If a required scene is unavailable, state what was not checked, why and how it can be verified; a labeled production-component fixture can provide partial evidence, not an actual-page pass. Do not claim verification complete while required checks remain missing. Run the original applicable tests; source inspection and passing tests alone do not establish the rendered result.
|
||||
|
||||
Read unchanged owners once; consult examples for uncertain judgments. Inspect relevant states and actual usage sizes, not a fixed screenshot count or every width × theme × state combination. Batch related edits and reuse a current application instance and valid evidence. Documentation-only changes and refactors with evidence of unchanged rendering need no new UI captures; state that basis.
|
||||
|
||||
When a before/after comparison is needed, preserve the actual starting implementation, including uncommitted work, and use comparable capture conditions. Never recreate a missing before image and present it as an original. Protect existing work regardless of whether extra evidence is needed.
|
||||
|
||||
Before handoff, review or push, check only new cumulative interactions, missing required checks and invalidated evidence; do not repeat completed verification at each stage. Report results and material gaps accurately. Do not claim full coverage or visual acceptance when required scenes or checks are missing.
|
||||
|
||||
These effort levels change only the additional design-review procedure. Existing [client test requirements](../../../packages/client/AGENTS.md), [GUI PR GIF requirements](../record-browser-gif/SKILL.md#every-gui-pull-request-includes-a-gif), and [designer/product approval](#design-review-escalation) still apply, including to local changes when their original triggers are met.
|
||||
|
||||
## Select the rule owners
|
||||
|
||||
For layout changes, read the alignment and spacing references. Read the icon reference when selecting, drawing or reviewing artwork, or when adding, changing or reviewing icon buttons, icon-only actions or informational icons, including reuse without artwork changes. Other tasks use the relevant sections below. Do not load all references by default. Existing responsive and official-artwork skills retain their scopes; numeric scales stay with their implementation owners.
|
||||
|
||||
| Decision needed before implementation | Section or reference |
|
||||
|---|---|
|
||||
| Shared axes, text starts, centering, tree indentation, complete icon boxes | [Alignment](references/alignment.md) |
|
||||
| Semantic groups, inner/outer gaps, repeated rhythm, section separation | [Spacing and grouping](references/spacing.md) |
|
||||
| Interface icon selection and artwork; icon buttons and icon-only actions, tooltips, informational hover content, accessible labels and focus | [Icons](references/icons.md) |
|
||||
| Color, typography and existing treatments | [Visual foundation](#visual-foundation); [Web styling](../../../docs/web-styling.md) owns tokens/materials |
|
||||
| Component geometry, nested contours and state fills | [Geometry decisions](#component-geometry); [radius standard](../../../docs/ui-radius.md) owns values and exceptions |
|
||||
| Feedback lifetime, loading, overlays, scrolling and platform clearance | [Interaction and feedback](#interaction-and-feedback); [error handling](../dsh-error-handling/SKILL.md) owns failure guarantees |
|
||||
| Responsive layout rules, mobile-specific interactions, and truncation/ellipsis rules for components like text and buttons | [Responsive UI](../dsh-responsive-ui/SKILL.md) |
|
||||
| Official plugin artwork and feature previews | [Official artwork](../dsh-official-plugin-artwork/SKILL.md) |
|
||||
| Copy, locale ownership and component semantics | [Prose standard](../dsh-prose-standard/SKILL.md), [locale ownership](../../notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md), [primitives catalog](../../../packages/client/ui-primitives/README.md#component-catalog) |
|
||||
| Density, information hierarchy, combined control states and component choice | [Principle reference](../../../docs/ui-design-review-cases.md#density) sections 3–6 |
|
||||
|
||||
## Reuse before adding
|
||||
|
||||
- **Extend an existing component, container, or interaction before creating a new one.** A new capability that fits an existing surface (an extra menu item, a new prop on an existing primitive) beats a parallel element stacked beside it.
|
||||
- **Icons come from the existing icon library.** Pick the semantically closest existing icon; a new glyph requires designer approval before it lands — when reviewing a PR that adds one, suggest requesting a designer review.
|
||||
- **Right-sidebar content registers a slot in the existing sidebar**, never a separate sidebar. Every sidebar tab declares a tab icon matching its meaning.
|
||||
- **Icon-only actions whose meaning is not obvious get a Tooltip.** Informational content the pointer must rest on or select uses HoverCard instead. Prefer these shared primitives over ad-hoc title attributes.
|
||||
Extend existing components, containers and interactions when the capability fits. Register right-sidebar content through a slot in the existing sidebar; do not create a separate sidebar. Follow [icon reuse](references/icons.md#reuse-existing-icons-first). Before restructuring, read [client instructions](../../../packages/client/AGENTS.md) and [client defensive patterns](../../../docs/defensive-patterns/client-components.md) for component/state ownership and layering.
|
||||
|
||||
## Feedback surfaces
|
||||
## Visual foundation
|
||||
|
||||
Apply [dsh-error-handling](../dsh-error-handling/SKILL.md#localized-actionable-visible-reports) to failure reports. Choose the surface by the lifetime of the message relative to the surface that produced it:
|
||||
- Reuse tokens, values and complete treatments from the target page or nearest established sibling before adding literals; Plugins is the usual full-page reference.
|
||||
- Verify colors in light and dark themes; keep ink/background pairs readable, especially dimmed text.
|
||||
- Feature CSS you add or change uses **font weight ≤500**; emphasize with size or ink. ui-theme markdown typography tokens are the exception.
|
||||
- Use theme typography roles, then the page's scale, then a comparable page's size/spacing ratios. Do not invent a size for a single element. Peers share sizes; keep the scale small (13px is the established row/detail size).
|
||||
- Apply [alignment](references/alignment.md) and the [same-foreground rule](references/icons.md#placement-and-use) to icon/text relationships, including value chevrons and search prompts.
|
||||
|
||||
- **Transient operation outcomes use the app-wide Toast primitive**, not in-place notices, and the toast's state and rendering live in a host that outlives the reporting surface (a `shell.overlay` entry, as RowActionToast does) — a toast rendered by the panel itself unmounts with that panel. A deletion can close the panel or tab that requested it; only a toast held outside that surface survives the close. Report success and failure; do not report still-pending states.
|
||||
- **A failed operation keeps the data visible.** A failed deletion keeps its row; the toast announces the failure. Never blank content to show an error for a transient operation.
|
||||
- **In-place notices are for states tied to the surface itself**: a query failure with its Retry action, field validation on the form that owns the field. A populated panel keeps a compact notice beside its retained content; only an empty panel centers a full error state.
|
||||
- **Error copy is plain language, and short.** No internal jargon or technical nouns unless an error code must be exposed for debugging. Prefer one clear sentence; anything explainable in two sentences must not grow into paragraphs. A Chinese notice of at most two sentences omits the trailing 句号(。). The notice must not break the existing layout — reserve its space or overlay it; never shift neighbouring elements.
|
||||
## Component geometry
|
||||
|
||||
## Loading states
|
||||
Choose component role and size variant, then its container relationship. Compare height, radius, curve, inset and material together; equal radii alone do not establish coherence, and wrapping/expansion alone does not justify larger radii. [Radius standards](../../../docs/ui-radius.md#radius-scale) own values and exceptions.
|
||||
|
||||
- Lists use their skeleton; every other page-level load centers a bare spinner. Never place a spinner in a corner of the page.
|
||||
- One loading treatment per page/component — concurrent regions must not each flash their own style.
|
||||
- No loading text unless the context needs it (narrow sidebars almost never do).
|
||||
- **Inset:** coordinate inner/outer curves and inset. Radius subtraction is exact for concentric circular arcs; for superellipses, subtraction is only a nominal sizing rule, so inspect diagonal whitespace as well as side gaps.
|
||||
- **Flush or adjoining:** the enclosure owns exposed corners; internal joins stay square. Hover fills and pseudo-elements follow that contour without clipping menus or focus.
|
||||
- **Independent child card/group:** use its own role/size tier, not the dialog radius or subtraction of all parent padding.
|
||||
- **Circles/capsules:** preserve shape, including avatar container and image, using the theme's round-curve exception; ordinary controls use the shared curve.
|
||||
|
||||
## Overlays and menus
|
||||
Keep fills, strokes and overlays geometrically stable across hover, pressed, selected, loading and disabled states. Reuse [card/elevation materials](../../../docs/ui-radius.md#settings-cards): shadows already include neutral hairlines; retain semantic state-colored borders without doubling neutral edges.
|
||||
|
||||
Every menu, popover, and tooltip must be verified for all three before merge:
|
||||
Compare related controls, including small selection controls, at actual size in affected themes/states and relevant long-label/narrow layouts. Static checks reject unsupported declarations; rendered comparison judges fit. Consult [geometry cases](../../../docs/ui-design-review-cases.md#geometry) when needed. Apply to scoped changes, not bulk restyling.
|
||||
|
||||
- **Dismissable**: outside click (and Escape where focusable) closes it.
|
||||
- **Viewport-fitting**: it slides or flips to stay inside the window with an edge margin, and flips only into a side that actually fits (no oscillation when neither side fits).
|
||||
- **Unclipped**: it escapes `overflow` ancestors and containing blocks (portal to body where needed) and is never covered by neighbouring elements.
|
||||
## Interaction and feedback
|
||||
|
||||
Scrollbars stay inside their container: use the shared scrollbar styles rather than component-specific selectors, and verify the bar neither overflows the container nor gets clipped by its rounded corners or edges — keep a small inset instead of letting it sit flush against the container edge.
|
||||
### Feedback surfaces
|
||||
|
||||
## Responsive layouts
|
||||
Follow [error handling](../dsh-error-handling/SKILL.md#localized-actionable-visible-reports). Choose feedback by message lifetime:
|
||||
|
||||
Use [dsh-responsive-ui](../dsh-responsive-ui/SKILL.md) for responsive layouts, mobile and touch detection, text truncation, title wrapping, icon-only controls, and focus during width changes.
|
||||
- Transient success/failure uses the app-wide Toast, with state and rendering in a host that survives the reporting panel/tab, such as a `shell.overlay` entry. A deletion may close its source panel; a Toast owned by that panel would disappear with it. Do not toast pending states.
|
||||
- Failed operations retain visible data; failed deletion keeps the row.
|
||||
- Surface-owned failures use in-place notices: query failure with Retry or field validation. Keep a compact notice beside populated content; only empty panels center a full error state.
|
||||
- Use short plain-language errors, preferably one sentence, without internal jargon except necessary diagnostic codes. Chinese notices of at most two sentences omit the trailing 句号(。). Reserve space or overlay notices so neighbors do not shift.
|
||||
|
||||
## Platform window adaptation
|
||||
### Loading states
|
||||
|
||||
- Any new header element or full page must account for macOS and Windows window chrome. On macOS the titlebar clearance is `--dsh-frame-top-clearance`, consumed by Menu, Modal, and overlay primitives as their top safe margin — **never change the global variable to fix one page**. Add a scoped `[data-platform='darwin']` override on the page's own inset instead, and assert the override in the page's stylesheet spec.
|
||||
- State the intended total offset (clearance + page inset) when reviewing, so two pages aligning to "the same top spacing" agree on the sum, not just one addend.
|
||||
Lists use skeletons; other page-level loads center a bare spinner. Keep one loading treatment per page/component, including concurrent regions. Add loading text only when context requires it.
|
||||
|
||||
## Spacing review
|
||||
### Overlays and menus
|
||||
|
||||
- Review every changed region for gaps: nothing sits flush against its neighbour without an intentional gap, and adjacent icons and elements keep the region's established spacing.
|
||||
- Prefer symmetry for parallel elements; an unexplained one-off gap or offset usually signals a missed shared value.
|
||||
- **For complex pages and containers, establish shared horizontal and vertical alignment axes before adjusting local spacing.** Consider hover, selected, and expanded/collapsed states, as well as the overall relationship with adjacent panels and other page elements. Choose from the following references according to layout similarity:
|
||||
- **Sidebar:** align corresponding text starts and leading icons across vertically stacked rows. Align hover and selected row backgrounds with the buttons above the list to maintain visual balance.
|
||||
- **Plugins page:** keep content left-aligned along a shared edge. List-row hover backgrounds may extend beyond that edge while the content stays in place.
|
||||
- **Conversation and right-sidebar headers:** align the first header rows across both panels and match their heights. Apply the same rule to the second rows, and align corresponding underlines and dividers on shared horizontal baselines.
|
||||
Before merge, verify every menu, popover and tooltip closes on outside click (and Escape when focusable), fits the viewport with an edge margin, and escapes ancestor clipping, containing blocks and neighbor coverage, using a portal to body when needed. Flip only into a side that fits; never oscillate when neither fits.
|
||||
|
||||
Use shared scrollbar styles and a small inset; bars must stay inside containers without being clipped by rounded corners or edges.
|
||||
|
||||
### Platform window adaptation
|
||||
|
||||
New headers/pages account for macOS and Windows chrome. Menu, Modal and overlay primitives consume `--dsh-frame-top-clearance`; fix local macOS insets with scoped `[data-platform='darwin']` overrides and stylesheet assertions, not global-variable changes. Review the total clearance + page inset.
|
||||
|
||||
## Check the rules together
|
||||
|
||||
Inspect the composition, then affected internal/cross-group relationships, distant peers and expanded content. Match both endpoints of axes; compare complete icon boxes before optical adjustments. Preserve popup/focus visibility, field feedback, narrow-layout actions and drafts. Check affected theme materials/platform backdrops. Full-interface claims cover all regions; fixtures/crops establish only what they render.
|
||||
|
||||
## Maintain one owner per rule
|
||||
|
||||
Update the owning section or reference within this skill; do not expose its design specialties as separate skills. Keep examples in the [case reference](../../../docs/ui-design-review-cases.md); AGENTS and PR workflows link here instead of duplicating rules.
|
||||
|
||||
Rule changes state applicability, decisions, exceptions and observable verification; update a distinguishing case and remove superseded statements elsewhere. Declare migration versus future-edit scope without implying existing UI conforms. Automate only mechanically decidable checks. Local review tools consume repository rules and retain source/rule revisions rather than maintaining another standards copy.
|
||||
|
||||
## Design review escalation
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# UI alignment
|
||||
|
||||
Apply within the [UI development loop](../SKILL.md#required-review-loop). Align comparable peers, including slots for optional icons and actions.
|
||||
|
||||
## Layout structure first, visual calibration second
|
||||
|
||||
Distinguish click target, painted button, complete icon box and drawn contour. Intentional icon whitespace belongs to the layout box; do not remove it merely because painted pixels miss a guide. Enlarging a target must not move that box. Check containers, slots, columns, gaps and unintended overrides before optical corrections. Neither matching CSS boxes nor aligning every painted contour alone establishes balance.
|
||||
|
||||
A glyph inconsistent across uses belongs to its icon owner. A composition-specific optical correction needs a scoped justification and comparison with peers. Preserve intended hierarchy: a child icon may align with parent text, but DOM nesting alone does not determine indentation. When comparing a bare icon with a bordered control, extra hit padding is not a shared visible edge.
|
||||
|
||||
Inline SVG `vertical-align: middle` follows font baseline/x-height, not visible text center. Check font, size, line height and icon box across CJK, Latin, monospace and wrapping. Do not make multiline links unwrappable just to center an icon.
|
||||
|
||||
## Choose both the object and the axis
|
||||
|
||||
Name the aligned object and reference container. Equal padding need not align text starts; equal SVG dimensions need not align visible centers. For a close control beside footer actions, explicitly choose the control edge or complete icon-box edge; do not switch between them while measuring.
|
||||
|
||||
| Alignment | Use it for | Judge the actual relationship | Common mistake |
|
||||
|---|---|---|---|
|
||||
| Left edge | Navigation labels, list titles, field labels, reading content | Same-role text starts on one deliberate axis, including across groups | Every component has its own plausible but different inset |
|
||||
| Horizontal center | A standalone primary button or short empty-state stack | Center the icon-plus-label or full stack in the named container | Center the label alone and attach an icon outside it |
|
||||
| Vertical center | A single-line icon/label row or peer toolbar controls | Visible centers balance; the whole clickable unit keeps a stable height | Equal CSS heights hide different SVG whitespace or line boxes |
|
||||
| Baseline / first line | Mixed text sizes, multiline labels with leading icons | Text baselines agree; an icon belongs to the intended first line | Center an icon beside the entire multiline paragraph |
|
||||
| Two edges | Title/action rows and left/right composer groups | Left group follows the content edge; right group follows the trailing edge | Distribute every icon across the available width |
|
||||
| Repeated columns | Action clusters, timestamps, file rows | Corresponding slots share centers or edges across rows | Only the last icon aligns; preceding icons drift |
|
||||
|
||||
For Chinese/English navigation, identify leading icon centers, text starts and trailing controls by role. Headings, avatars and expandable search may differ. Continue axes only where the composition establishes them, including shared plugin reading edges even when hover paint extends beyond them, and conversation/right-sidebar header heights and baselines.
|
||||
|
||||
For a trigger and its menu, or search field and result rows, compare the complete text inset: outer padding, borders, leading slot and gap. Different icon sizes can retain one text start. For adjacent statistics, check font family, size, weight and line height together before moving the icon.
|
||||
|
||||
Center a button within its own bounds, a welcome group within the main pane, and an empty state within the content remaining below headings/filters. A fixed top margin is not centering. When a short window cannot fit the group, preserve scroll access with a compact top-aligned layout instead of clipping.
|
||||
|
||||
Normalize peer control sizes and slots before any necessary small optical offsets; verify comparable uses. Preserve usable, non-overlapping hit targets and do not hide inconsistent parent layout with transforms. Transparent hit padding can exceed the painted icon slot; check neighboring target edges after tightening a cluster. Check [spacing](spacing.md) without moving the chosen axes; consult [cases](../../../../docs/ui-design-review-cases.md#alignment) for uncertain judgments.
|
||||
@@ -0,0 +1,60 @@
|
||||
# UI icons
|
||||
|
||||
Apply within the [UI development loop](../SKILL.md#required-review-loop), in the affected page or modal context.
|
||||
|
||||
## Reuse existing icons first
|
||||
|
||||
Search the library and comparable UI for the intended meaning and supported variants. Reuse the shared component; do not copy its SVG into a feature or create a near-duplicate. Size, color and placement mismatches belong to the variant or consumer layout, not a new drawing.
|
||||
|
||||
Draw only when existing artwork cannot express the meaning or the user requests a distinct design. Before drawing, explain the closest alternatives and the unmet meaning or requested design difference. New glyphs require designer approval before landing; requested drafts can be prepared locally. Do not redraw a shared glyph to fix one consumer.
|
||||
|
||||
The [primitives icon contract](../../../../packages/client/ui-primitives/README.md#controls-and-icons) and icon source own available variants, stroke scaling and menu sizes; [official artwork](../../dsh-official-plugin-artwork/SKILL.md) owns package artwork.
|
||||
|
||||
## System style
|
||||
|
||||
- Use minimal, geometric, functional glyphs with familiar meanings. Match peers in contour, endings, joins, weight, whitespace and complexity; avoid decorative, playful or illustration-like detail. A supplied reference guides the requested meaning and distinctive form within this system.
|
||||
- Use simple geometry and restrained rounding. Around 1px at a 16px design size is a starting point for softened corners, not a rule for every turn. Preserve useful sharp corners.
|
||||
- Default to monochrome outlines inheriting `currentColor`, without gradients, shadows, textures or 3D effects. Preserve intentional fills, status dots, selected marks and knockouts. File, site, plugin and brand artwork retain their own palettes and roles.
|
||||
- Balance structure and whitespace without requiring symmetry; avoid accidental tilt or a top-heavy silhouette. Preserve meaningful direction. Use gaps, overlap, nesting and broken obscured lines to clarify complex meanings; avoid accidental tangencies, dark knots and disappearing gaps at actual size. Add contour detail only when it improves recognition; do not force every subject into a closed outline.
|
||||
|
||||
## Optical size and clarity before implementation
|
||||
|
||||
Use the owning control's supported size and weight. [Shared menu sizes and stroke scaling](../../../../packages/client/ui-primitives/README.md#controls-and-icons) belong to primitives; [permission triggers](../../../../packages/client/ui-permission-presets/README.md) and [settings navigation](../../../../packages/client/ui-settings-general/README.md) belong to their feature packages. A reviewed proposal for a larger or heavier trigger is not a statement that every existing consumer already uses it. Compare at the owning control's size, weight, colors and label, using an outlined circle and rectangular control as references. Open silhouettes may need more extent than dense fills; supporting chevrons, handles and dots keep their roles. Do not normalize by ink area or fill every view box.
|
||||
|
||||
A shared view-box change affects every consumer at its own CSS size. Inspect both compact menus and larger icon-only controls; a clearer small chevron can become oversized in a floating button. Adjust a consumer’s supported icon size when needed while retaining its button base and hit target.
|
||||
|
||||
For softness, check CSS dimensions and ancestor transforms, view box and drawing bounds, stroke versus filled contour, device-pixel placement, then color/opacity. Non-16 view boxes are valid; compare identical artwork before attributing sharpness to inline SVG versus sprites.
|
||||
|
||||
Use the [implemented stroke behavior](../../../../packages/client/ui-primitives/README.md#controls-and-icons): source stroke widths scale with the SVG viewport, and filled outlines scale as geometry. Do not add non-scaling strokes or convert filled contours during an ordinary placement or reuse fix. A change to that rendering contract belongs to a separately scoped library change with consumer validation; retain intentional silhouettes and knockouts.
|
||||
|
||||
Check straight-stroke edges at actual DPR after all scaling: center and width jointly determine coverage. Integer source coordinates alone do not guarantee crisp edges. Round positioned overlays in device pixels only when layout places otherwise crisp artwork between pixels; preserve anchoring and viewport containment. Keep antialiasing on curves/diagonals; do not globally round paths or use `crispEdges` to hide placement errors. Partial coverage is legitimate for 1.3px strokes.
|
||||
|
||||
## Draw a new icon
|
||||
|
||||
Establish meaning, placement, actual sizes, peers and states; ask only when missing information changes the design. Produce editable SVG; shared line icons need Regular and Medium variants sharing construction; intentional fill-only variants may share geometry. Tune silhouette, gaps, center and weight at intended size using component/theme conventions, not consumer-specific stroke or scale overrides.
|
||||
|
||||
Deliver editable artwork and a rendered peer comparison, including its label or control when relevant. Explain the semantic choice and unresolved alternatives. Keep drafts local; when implementing, register reusable components and update their documentation. Distinguish review-ready proposals from approved glyphs.
|
||||
|
||||
## Normalize user-supplied artwork
|
||||
|
||||
Before changing supplied artwork, establish its meaning, recognizable features and intended use. Preserve the original and recognizable form. Show an existing equivalent as a reference when available, but do not substitute it for the drawing the user asked to normalize. Separate technical fixes (canvas, scale, stroke, color inheritance, placement) from silhouette or metaphor changes; identify any required simplification.
|
||||
|
||||
Apply the same system style and verification criteria as new artwork. Inspect SVG paths, transforms, fills, strokes and references. Reconstruct raster references without tracing rasterization or compression artifacts; identify interpreted details and ask about unrecoverable defining features. Deliver editable SVG and original/normalized/peer comparisons under identical conditions at intended size. Keep artwork changes separate from consumer layout fixes.
|
||||
|
||||
## Placement and use
|
||||
|
||||
Distinguish click target, visible button base, icon slot and painted glyph. Equal targets or SVG dimensions do not imply equal optical size; compare silhouette, stroke, whitespace and center beside the label. Fix containers and unintended CSS overrides first. Correct shared artwork only when the imbalance follows it across uses.
|
||||
|
||||
An icon and its associated text for one action, value or input prompt must share foreground color, effective opacity and state changes. This includes submenu value chevrons and search placeholders; inherit the shared role instead of assigning an independent icon token. Typed text and independent clear actions may differ; status, warning and brand roles retain semantic colors. Check computed color and ancestor opacity in both themes. Apply the rule to changed consumers; updating it does not certify untouched UI. Compare native file icons and folder outlines deliberately; do not normalize logos, status dots or decorative illustrations to toolbar icons.
|
||||
|
||||
Every right-sidebar tab needs a meaningful icon. [Alignment](alignment.md) owns positioning and [spacing](spacing.md) owns gaps.
|
||||
|
||||
## Icon controls and interaction
|
||||
|
||||
Use the shared Tooltip for unclear icon-only actions and HoverCard for informational content requiring hover or selection; prefer these primitives over ad-hoc `title` attributes. Preserve accessible labels and focus. [Responsive UI](../../dsh-responsive-ui/SKILL.md) owns mobile tooltips and label collapse.
|
||||
|
||||
## Verification and handoff
|
||||
|
||||
For ordinary icon reuse or placement changes, inspect the affected control at its actual size and relevant states; no fixed size/DPR matrix or separate raster report is required. For new or changed artwork, strokes or rendering, compare affected sizes and weights with peers; check DPR 1 and 2 when stroke scaling or pixel placement can change, and both themes when color or optical weight is affected. Keep before/after captures when needed for the change or requested deliverable. Judge recognition, clipping, center, gaps and neighboring text at actual size; enlarged views diagnose pixels only.
|
||||
|
||||
After a set-wide change, trace affected families, consumers and weights, including unchanged artwork whose rendering can change. Use unchanged peers for comparison. An explicitly requested full-set review inventories and compares every glyph under matching conditions. Consolidate duplicate artwork only when necessary for the current fix; record unrelated duplicates separately instead of expanding the task. Consult [cases](../../../../docs/ui-design-review-cases.md#icon-optics) for uncertain judgments.
|
||||
@@ -0,0 +1,19 @@
|
||||
# UI spacing and grouping
|
||||
|
||||
Apply within the [UI development loop](../SKILL.md#required-review-loop). Distance should explain which text, input and action belong together.
|
||||
|
||||
### Identify the group before choosing the gap
|
||||
|
||||
Group elements by one object, action or repeated unit, not DOM siblings, background or React component. State that relationship before positioning. Check each nesting level independently; larger outer gaps do not require larger inner gaps.
|
||||
|
||||
For example, a title and description introduce one section; three conversation actions form one operation group; member cards form a section separate from tasks. In a model form, checkbox and label form an option, options form a field, fields form a model, and models form a list. Check each relationship rather than applying one gap everywhere.
|
||||
|
||||
Without an explicit divider, comparable neighboring groups need visibly greater separation between groups than within them. Equality is only a lower bound against reversal. Repeated compositions share gaps, padding and alignment; label length does not justify unrelated spacing.
|
||||
|
||||
When wrapping, metadata, subgroups or independent actions increase complexity, increase enclosing separation enough to retain grouping. A compact icon/label pair and a section of multiline cards need different spacing levels. Do not derive gaps from card area or multiply every margin. Dividers and distinct surfaces may reinforce separation, but headings must remain closer to their own content than to preceding groups.
|
||||
|
||||
Judge actual whitespace at normal size, including padding, line-height, glyph whitespace and borders. CSS margin ratios alone do not establish grouping; explanatory labels need one unambiguous owner. For example, declared 16px/8px margins can still leave a heading closer to the preceding content once line-height and container padding are included; compare the rendered gaps rather than adopting that pair as a rule.
|
||||
|
||||
When matching two visible gaps, name both pairs of endpoints and include each container’s padding, border and text line box. Equal visible gaps may require unequal margins. A control’s painted edge and a text glyph’s edge are different endpoints; keep that choice stable through review.
|
||||
|
||||
Reuse page spacing and [style tokens](../../../../docs/web-styling.md), preserve [alignment](alignment.md) axes, and consult [cases](../../../../docs/ui-design-review-cases.md#grouping) for uncertain judgments. This skill defines no numeric scale.
|
||||
@@ -39,6 +39,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo —
|
||||
|
||||
## Manual checks
|
||||
|
||||
- **UI review coverage:** apply the [UI change workflow](../dsh-client-ui-ux/SKILL.md#required-review-loop) to the cumulative visible diff. Verify its [minimum checks](../dsh-client-ui-ux/SKILL.md#minimum-checks-for-affected-ui) for the changed properties and consumers. Missing required inspection remains a gap; missing optional reports does not. Do not demand full-audit records for ordinary changes. Recheck only new interactions or invalidated evidence. Existing tests, GUI GIFs and designer/product approval remain required under their original triggers.
|
||||
- **Intent and interface contracts:** trace both sides of every changed interface. Confirm the implementation matches the PR and any Agent Note, including errors, cancellation, ownership, and disposal.
|
||||
- **Error handling:** apply [dsh-error-handling](../dsh-error-handling/SKILL.md) to changed failure paths, including their state guarantees and final user-visible reports. Reject catch-and-continue unless the state owner establishes that subsequent work's preconditions hold, including after recovery.
|
||||
- **Lifecycle and concurrency:** for async setup, callbacks, processes, or teardown, apply [defensive-patterns/general.md](../../../docs/defensive-patterns/general.md). Check races before publication, cancellation during awaits, independent error reporting, callback containment, ownership before reentry, complete detach cleanup, and quiescent disposal.
|
||||
|
||||
@@ -7,6 +7,8 @@ description: Draw and review official DeepSeek Harness plugin icons and optional
|
||||
|
||||
Draw recognizable artwork that belongs beside the existing official icons. Official and official experimental plugins share this visual language; the UI label communicates experimental status. This guidance does not replace designer approval.
|
||||
|
||||
When reviewing the containing page, use the [UI review baseline](../../../docs/ui-design-review-cases.md#review-baseline) to combine artwork balance with its tile, label, neighboring cards and theme material. Keep package artwork distinct from action icons: coherent optical weight does not require equal drawing bounds, monochrome fills or identical geometry across those roles.
|
||||
|
||||
## Optional feature previews
|
||||
|
||||
Before drawing a larger image for the top of an official bundle's detail document, read and follow [adding a plugin feature preview](../../../docs/cookbook/adding-a-plugin-preview.md). It owns optionality, image count, design canvas, position, language variants, file location, and acceptance. The guide also owns the embedded-raster exception for SVG previews; small icons remain geometry-only. The icon dimensions below do not apply to previews. Inspect the real feature and preserve its produced assets.
|
||||
@@ -14,7 +16,7 @@ Before drawing a larger image for the top of an official bundle's detail documen
|
||||
## Establish the subject
|
||||
|
||||
- Inspect the actual feature, its output, and its owning manifest before drawing. A badge-producing skill may need a tag, not a medal. Reuse designer-supplied assets; do not invent a brand mark or change product names, identifiers, or unrelated icons without approval.
|
||||
- Read the [existing artwork](../../../packages/client/ui-primitives/src/plugin-artwork.tsx) and the nearest relevant package SVGs. Borrow one coherent treatment, not every reference's effects. Action icons remain with [dsh-client-ui-ux](../dsh-client-ui-ux/SKILL.md#reuse-before-adding).
|
||||
- Read the [existing artwork](../../../packages/client/ui-primitives/src/plugin-artwork.tsx) and the nearest relevant package SVGs. Borrow one coherent treatment, not every reference's effects. Action icons remain with [the UI design entry](../dsh-client-ui-ux/SKILL.md#select-the-rule-owners).
|
||||
- Check every affected entry, list, detail, and component row. Bundle artwork and a component's name and icon can have separate metadata owners; changing one does not update the other.
|
||||
|
||||
## Draw and refine
|
||||
|
||||
@@ -33,6 +33,7 @@ When the outgoing change adds or changes a resource-owning or asynchronous test,
|
||||
- **Package or script behavior:** run the owning Vitest file or focused test name. Add adjacent package tests when a shared contract changes; leave repository-wide coverage to CI unless the change is genuinely cross-cutting or the user requests it.
|
||||
- **Remote mock typing:** unbuilt `any` is an explicit local fallback, not strict evidence. Run `pnpm run typecheck` before handing off Remote/mock changes; rebuild missing, stale, or partial generated declarations before diagnosing remaining errors. Keep the exception in the [test proxy](../../../packages/test-support/remote-mock/README.md#remote-proxy), never in production Remote types, ambient flags, or copied signatures.
|
||||
- **Documentation, Agent Notes, catalogs, or doc-linked comments:** run `pnpm run doc-sync`; run full lint when the documentation workflow requires it.
|
||||
- **User-visible UI:** use the [UI change workflow](../dsh-client-ui-ux/SKILL.md#required-review-loop) and its [minimum checks](../dsh-client-ui-ux/SKILL.md#minimum-checks-for-affected-ui) to verify required inspection and new cumulative effects. Disclose missing checks; optional paperwork is not a substitute for inspecting the result. Reuse valid evidence; ordinary changes do not need a separate full-audit report at push time. Keep existing test, GUI GIF and designer/product approval requirements. Tests alone do not establish visual acceptance.
|
||||
- **Model-, editor-, CLI-, or terminal-visible output:** run the focused keyless snapshot or real runnable-example scenario that owns the output.
|
||||
- **Expected-output placement:** a test whose selected recorded Session generation is replay input and expected persisted output belongs under top-level `snapshots/`, with `snapshot.yml` naming its shipped `dsh` profile and composition/header pin. Canonical parent files are `session[.vN].jsonl`, children are `session.<ordinal>[.vN].jsonl`, and the harness selects the highest generation per role. ARIA, geometry, generator, CLI, and unit expectations without that Session round trip stay beside their owning test under `tests/expected/`; do not place them in `snapshots/` or give them a `*.snapshot.ts` owner. Use the owning `test:expected`, `test:web`, or `test` lane.
|
||||
- **Profile and configuration placement:** cross-package behavior of a shipped `dsh` profile belongs under `apps/cli/tests/profiles/`; a package-specific Loader composition belongs under that package's `tests/fixtures/`. User-facing optional overlays live under `apps/cli/config/examples/` and pair with a guide under `docs/user/`.
|
||||
|
||||
@@ -5,6 +5,8 @@ description: Use when designing, implementing, or reviewing responsive client la
|
||||
|
||||
# Responsive client UI
|
||||
|
||||
For visual reviews, select the [impact scope](../../../docs/cookbook/reviewing-ui-design.md#review-scope), then apply these behavioral requirements and the [UI review baseline](../../../docs/ui-design-review-cases.md#review-baseline). Compare affected wrapping and layout relationships in the whole page or modal's context while preserving drafts, focus and action access. Inspect all regions for an explicit full-interface review. Examples do not define coverage; fitting the viewport alone is insufficient.
|
||||
|
||||
## 1. Minimum viewport
|
||||
|
||||
Use a viewport width of 390 CSS px as the primary acceptance minimum. There must be no unintended overlap, clipping, page-wide horizontal scrolling, or inaccessible primary actions. Below 390px, layout requirements may be relaxed.
|
||||
|
||||
@@ -40,6 +40,7 @@ apps/web/dist/
|
||||
.artifacts/
|
||||
.dsh-build/
|
||||
.dsh-report/
|
||||
.ui-review/
|
||||
.playwright-mcp/
|
||||
.orig
|
||||
.worktrees/
|
||||
|
||||
@@ -157,6 +157,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and
|
||||
- **Create Agent Notes only for durable decision rationale;** mechanical/local edits are exempt, including local UI changes ([scope](.agents/notes/README.md#when-to-write-one)). Archived notes are frozen: never edit or treat them as current authority ([archive policy](.agents/notes/README.md#archiving-and-deletion)).
|
||||
- **Official plugin artwork:** use [dsh-official-plugin-artwork](.agents/skills/dsh-official-plugin-artwork/SKILL.md) for official and official experimental plugin icons; obtain designer approval before landing new glyphs.
|
||||
- **Client UI copy is locale-owned.** Route product text through typed dictionaries and `t` or localized primitive props; `verify-client-ui-i18n` rejects hardcoded copy ([decision](.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md)). English copy, including plugin titles, uses sentence case ([rule](.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location)).
|
||||
- **UI changes:** use [dsh-client-ui-ux](.agents/skills/dsh-client-ui-ux/SKILL.md) as the design and review entry point; load its references only as needed.
|
||||
- **Testing policy** — [docs/testing.md](docs/testing.md). Every non-trivial model- or product-user-visible change updates a keyless recorded-session snapshot; [snapshot ownership](snapshots/AGENTS.md) reserves the top-level tree for session-driven cases and keeps other expected output owner-local. Fixtures replay on macOS/Linux; fix fixtures, not normalizers.
|
||||
- **Design each tool's UI presentation up front.** Host presenters stay pure; Web cards derive from raw events and persisted result metadata ([cookbook](docs/cookbook/adding-a-tool.md)).
|
||||
- **Plan unit, e2e, and snapshot coverage** for capability seams, lifecycle paths, and transcript output; include missing snapshot-harness support in the same change.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/** Production styles keep oversized floating panels aligned with their triggers. */
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { chromium, webkit } from 'playwright'
|
||||
import { expect, it, onTestFinished } from 'vitest'
|
||||
|
||||
const PANELS = [
|
||||
{ file: 'client/ui-primitives/src/Menu.module.css', classes: 'list portal scrollable', align: 'start' },
|
||||
{ file: 'client/ui-primitives/src/Menu.module.css', classes: 'list portal scrollable', align: 'end' },
|
||||
{ file: 'client/ui-schedule/src/client/TaskMenu.module.css', classes: 'list portal scrollable', align: 'start' },
|
||||
{ file: 'client/ui-schedule/src/client/TaskMenu.module.css', classes: 'list portal scrollable alignEnd', align: 'end' },
|
||||
{ file: 'client/ui-schedule/src/client/PickerPopover.module.css', classes: 'panel', align: 'start' },
|
||||
{ file: 'client/ui-schedule/src/client/ScheduleCatalogAction.module.css', classes: 'menu', align: 'start' },
|
||||
{ file: 'client/ui-subagent/src/client/SubagentHeaderLineage.module.css', classes: 'menu', align: 'start' },
|
||||
{ file: 'client/ui-approval/src/client/ApprovalPanel.module.css', classes: 'warningPopover', align: 'end' },
|
||||
{ file: 'client/ui-jobs/src/client/JobListAction.module.css', classes: 'menu', align: 'start' },
|
||||
{ file: 'client/ui-conversation/src/client/skeleton/ContextMeter.module.css', classes: 'panel', align: 'start' },
|
||||
{ file: 'client/ui-chat/src/client/chat/stat-dialog.module.css', classes: 'panel', align: 'start' },
|
||||
{ file: 'client/ui-sidebar-documentpreview/src/client/office/FontNotice.module.css', classes: 'panel', align: 'start' },
|
||||
{ file: 'client/ui-plugin-manager/src/client/PluginManagerPage.module.css', classes: 'registry', align: 'end' },
|
||||
{ file: 'experimental/session-inspector/src/client/views/inspector.module.css', classes: 'typeFilterPanel', align: 'start' },
|
||||
] as const
|
||||
|
||||
it.each([{ name: 'Chromium', engine: chromium }, { name: 'WebKit', engine: webkit }])(
|
||||
'retains horizontal anchors when tall panels exhaust both sides ($name)', async ({ engine }) => {
|
||||
const browser = await engine.launch()
|
||||
onTestFinished(() => browser.close())
|
||||
const page = await browser.newPage({ viewport: { width: 1400, height: 600 } })
|
||||
for (const panel of PANELS) {
|
||||
const css = await readFile(new URL(`../../../packages/${panel.file}`, import.meta.url), 'utf8')
|
||||
// Only content volume is synthetic; placement and viewport limits come from each owner.
|
||||
await page.setContent(`<style>${css}
|
||||
#panel { display: flex; flex-direction: column; }
|
||||
#scroller { overflow: auto; min-height: 0; }
|
||||
#content { height: 2000px; width: 180px; flex: none; }
|
||||
#trigger { position: absolute; left: 700px; top: 286px; width: 100px; height: 28px; anchor-name: --probe; }
|
||||
</style><button id="trigger">Trigger</button>
|
||||
<div id="panel" class="${panel.classes}" data-anchor-align="${panel.align}" style="position-anchor: --probe">
|
||||
<div id="scroller"><div id="content">Long list</div></div>
|
||||
</div>`)
|
||||
const card = page.locator('#panel')
|
||||
const horizontalError = () => card.evaluate((node, align) => {
|
||||
const bounds = node.getBoundingClientRect()
|
||||
const trigger = document.querySelector('#trigger')!.getBoundingClientRect()
|
||||
return Math.abs(align === 'end' ? bounds.right - trigger.right : bounds.left - trigger.left)
|
||||
}, panel.align)
|
||||
await expect.poll(horizontalError, { message: `${engine.name()}: ${panel.file} (${panel.align})` }).toBeLessThan(1)
|
||||
const bounds = await card.boundingBox()
|
||||
expect(bounds).not.toBeNull()
|
||||
expect(bounds!.y).toBeGreaterThanOrEqual(12)
|
||||
expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(588)
|
||||
expect(await page.locator('#scroller').evaluate(node => node.scrollHeight > node.clientHeight)).toBe(true)
|
||||
|
||||
// Each opening mounts a new portal, without a previous native fallback choice.
|
||||
await page.locator('#content').evaluate((node) => { node.style.height = '60px' })
|
||||
await card.evaluate((node) => { node.replaceWith(node.cloneNode(true)) })
|
||||
await expect.poll(() => card.evaluate(node => node.getBoundingClientRect().height)).toBeLessThan(120)
|
||||
await expect.poll(horizontalError).toBeLessThan(1)
|
||||
const short = await card.boundingBox()
|
||||
expect(short!.y + short!.height <= 286 || short!.y >= 314).toBe(true)
|
||||
|
||||
const opensAbove = /(?:ApprovalPanel|ContextMeter|stat-dialog)/.test(panel.file)
|
||||
await page.locator('#trigger').evaluate((node, top) => { node.style.top = `${top}px` }, opensAbove ? 24 : 548)
|
||||
await card.evaluate((node) => { node.replaceWith(node.cloneNode(true)) })
|
||||
await expect.poll(() => card.evaluate((node, above) => {
|
||||
const rect = node.getBoundingClientRect()
|
||||
return above ? rect.top >= 52 : rect.bottom <= 548
|
||||
}, opensAbove), { message: `${engine.name()}: flipped ${panel.file}` }).toBe(true)
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 600 })
|
||||
await page.locator('#trigger').evaluate((node) => { node.style.left = '145px'; node.style.top = '286px' })
|
||||
await page.locator('#content').evaluate((node) => { node.style.height = '2000px' })
|
||||
await expect.poll(() => card.evaluate((node) => {
|
||||
const rect = node.getBoundingClientRect()
|
||||
return rect.left >= 12 && rect.right <= 378 && rect.top >= 12 && rect.bottom <= 588
|
||||
}), { message: `${engine.name()}: constrained ${panel.file}` }).toBe(true)
|
||||
await page.setViewportSize({ width: 1400, height: 600 })
|
||||
}
|
||||
})
|
||||
@@ -70,6 +70,79 @@ it.skipIf(process.platform !== 'darwin')('keeps system requests pending until fr
|
||||
expect(await approval.getByText('/Applications/Fixture.app', { exact: true }).count()).toBe(0)
|
||||
await compareOrRefreshGolden(fileURLToPath(new URL('./expected/cua-permissions/application-approval.expected.md', import.meta.url)),
|
||||
await captureStableAria(page, '[data-approval-key]', scaffold.workspaceCwd), webSnapshotMode())
|
||||
const risks = approval.getByRole('button', { name: 'Risks', exact: true })
|
||||
const warning = page.getByRole('dialog', { name: 'Computer use risks' })
|
||||
await risks.hover()
|
||||
await warning.waitFor()
|
||||
expect(await warning.evaluate(element => element === document.activeElement)).toBe(false)
|
||||
await warning.hover()
|
||||
expect(await warning.count()).toBe(1)
|
||||
await approval.locator('p').hover()
|
||||
await warning.waitFor({ state: 'hidden' })
|
||||
await risks.hover()
|
||||
await warning.waitFor()
|
||||
await approval.getByRole('button', { name: 'Reject', exact: true }).focus()
|
||||
await warning.waitFor({ state: 'hidden' })
|
||||
await approval.locator('p').hover()
|
||||
await risks.hover()
|
||||
await warning.waitFor()
|
||||
await page.keyboard.press('ControlOrMeta+Slash')
|
||||
const shortcuts = page.getByRole('dialog', { name: 'Keyboard shortcuts', exact: true })
|
||||
await shortcuts.waitFor()
|
||||
await warning.waitFor({ state: 'hidden' })
|
||||
expect(await shortcuts.evaluate(element => element.contains(document.activeElement))).toBe(true)
|
||||
await page.keyboard.press('Escape')
|
||||
await shortcuts.waitFor({ state: 'hidden' })
|
||||
expect(await approval.count()).toBe(1)
|
||||
await approval.locator('p').hover()
|
||||
for (const selectText of [false, true]) {
|
||||
await risks.hover()
|
||||
await warning.waitFor()
|
||||
const paragraph = warning.locator('p').first()
|
||||
if (selectText) {
|
||||
await paragraph.dblclick({ position: { x: 8, y: 10 } })
|
||||
expect(await page.evaluate(() => window.getSelection()?.toString().trim().length ?? 0)).toBeGreaterThan(0)
|
||||
} else {
|
||||
await paragraph.click()
|
||||
}
|
||||
expect(await warning.evaluate(element => element === document.activeElement)).toBe(true)
|
||||
await approval.locator('p').hover()
|
||||
await warning.waitFor({ state: 'hidden' })
|
||||
}
|
||||
await page.keyboard.press('Tab')
|
||||
await risks.focus()
|
||||
await warning.waitFor()
|
||||
expect(await risks.evaluate(element => element === document.activeElement)).toBe(true)
|
||||
await compareOrRefreshGolden(fileURLToPath(new URL('./expected/cua-permissions/application-risks.expected.md', import.meta.url)),
|
||||
await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd), webSnapshotMode())
|
||||
await page.keyboard.press('Escape')
|
||||
await warning.waitFor({ state: 'hidden' })
|
||||
expect(await approval.count()).toBe(1)
|
||||
for (const { width, height } of [{ width: 1000, height: 800 }, { width: 390, height: 800 }, { width: 390, height: 480 }]) {
|
||||
await page.setViewportSize({ width, height })
|
||||
for (const colorScheme of ['light', 'dark'] as const) {
|
||||
await page.emulateMedia({ colorScheme })
|
||||
await expect.poll(() => page.evaluate(() => document.body.hasAttribute('data-ds-dark-theme'))).toBe(colorScheme === 'dark')
|
||||
await risks.dispatchEvent('pointerdown', { pointerType: 'touch' })
|
||||
await risks.click()
|
||||
await warning.waitFor()
|
||||
await expect.poll(async () => warning.evaluate((element) => {
|
||||
const bounds = element.getBoundingClientRect()
|
||||
return bounds.left >= 12 && bounds.top >= 12 && bounds.right <= innerWidth - 12 && bounds.bottom <= innerHeight - 12
|
||||
})).toBe(true)
|
||||
expect(await warning.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true)
|
||||
expect(await approval.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true)
|
||||
if (webSnapshotMode() === 'refresh') {
|
||||
const directory = fileURLToPath(new URL('../../../.artifacts', import.meta.url))
|
||||
await mkdir(directory, { recursive: true })
|
||||
await page.screenshot({ path: join(directory, `cua-popover-${width}-${height}-${colorScheme}.png`), animations: 'disabled' })
|
||||
}
|
||||
await page.mouse.click(4, height / 2)
|
||||
await warning.waitFor({ state: 'hidden' })
|
||||
}
|
||||
}
|
||||
await page.emulateMedia({ colorScheme: null })
|
||||
await page.setViewportSize({ width: 1680, height: 1000 })
|
||||
await approval.getByRole('button', { name: 'Always allow', exact: true }).click()
|
||||
expect((await grant).results).toMatchObject([{ result: { isError: false } }])
|
||||
await approval.waitFor({ state: 'hidden' })
|
||||
|
||||
@@ -36,8 +36,8 @@ describe.skipIf(MODE === 'record')('web e2e: default Workspace', () => {
|
||||
await input.waitFor()
|
||||
// Language-neutral on disk, localized in the hero chip. The chip's
|
||||
// aria-label is its fixed call to action, so the label is its text.
|
||||
await expect.poll(() => page.getByRole('button', { name: 'Choose workspace', exact: true }).textContent())
|
||||
.toBe('Default workspace')
|
||||
await page.getByRole('button', { name: 'Choose workspace', exact: true })
|
||||
.getByText('Default workspace', { exact: true }).waitFor()
|
||||
expect(scaffold.ctx.workspaceRegistry.list()).toHaveLength(1)
|
||||
const initialSession = scaffold.ctx.sessions.list()[0]!
|
||||
expect(scaffold.ctx.sessions.list()).toHaveLength(1)
|
||||
@@ -82,7 +82,7 @@ describe.skipIf(MODE === 'record')('web e2e: default Workspace', () => {
|
||||
})
|
||||
await page.goto(scaffold.authenticatedUrl)
|
||||
const trigger = page.getByRole('button', { name: 'Choose workspace', exact: true })
|
||||
await expect.poll(() => trigger.textContent()).toBe('Default workspace')
|
||||
await trigger.getByText('Default workspace', { exact: true }).waitFor()
|
||||
await trigger.tap()
|
||||
const menu = page.getByRole('menu').filter({ has: page.getByRole('menuitem', { name: 'Default workspace', exact: true }) })
|
||||
await menu.waitFor({ state: 'visible', timeout: 10_000 })
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [expanded]
|
||||
- button "Called tools" [expanded]
|
||||
- button "Context injection runtime-context"
|
||||
- button "Think I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package."
|
||||
- button "Inspect plugins 0 dynamic plugins"
|
||||
- button "Think No dynamic Plugins are present, so I will define the requested Host and Client Package."
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
- text: Waiting for approval
|
||||
- group "Approval details":
|
||||
- strong: Fixture
|
||||
- text: Allow computer use to launch, read, capture and control this application?
|
||||
- paragraph: Allowing or denying applies to every version and copy sharing the same application identity. Session access and denial apply only to this conversation.
|
||||
- text: Allow agent to control this application via computer use?
|
||||
- button "Risks"
|
||||
- paragraph: Manage app permissions for all sessions on the plugin page.
|
||||
- button "Reject"
|
||||
- button "Allow for this session"
|
||||
- button "Always allow"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- dialog "Computer use risks":
|
||||
- heading "Computer use risks" [level=3]
|
||||
- paragraph: The agent can access only apps you approve, and can read and control them in the background.
|
||||
- paragraph: Online games, messaging apps, and other apps may restrict automation. Violating their rules could result in your account with that app being suspended or banned.
|
||||
- paragraph: Proceed at your own risk. To stop automated control at any time, press Stop in DeepSeek Harness.
|
||||
@@ -21,7 +21,10 @@
|
||||
- text: GitHub event received
|
||||
- time: {{clock}}
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [disabled]
|
||||
- button "Completed in {{duration}}" [expanded]
|
||||
- button "Injected context" [expanded]
|
||||
- button "Context injection runtime-context"
|
||||
- button "Context injection time-context"
|
||||
- paragraph: "Review complete: no actionable findings."
|
||||
- button "Copy"
|
||||
- button "Good response"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- text: GitHub event received
|
||||
- time: {{clock}}
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [disabled]
|
||||
- button "Completed in {{duration}}"
|
||||
- paragraph: "Review complete: no actionable findings."
|
||||
- button "Copy"
|
||||
- button "Good response"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- text: Research notes what changed? Referenced session · Research notes {{clock}}
|
||||
- button "Copy"
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [disabled]
|
||||
- button "Completed in {{duration}}"
|
||||
- textbox "Message or run a task, / commands, @ files or sessions"
|
||||
- button "Add files or run commands"
|
||||
- 'button "Access mode, current: Workspace Write"': Workspace Write
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
- text: and confirm the fixture wiring {{clock}}
|
||||
- button "Copy"
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [disabled]
|
||||
- button "Completed in {{duration}}" [expanded]
|
||||
- button "Injected context" [expanded]
|
||||
- button "Context injection runtime-context"
|
||||
- button "Context injection user-invoke-demo"
|
||||
- button "Context injection time-context"
|
||||
- paragraph: USER_INVOKE_REPLY acknowledged; following the injected skill.
|
||||
- button "Copy"
|
||||
- button "Good response"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
- text: and confirm the fixture wiring {{clock}}
|
||||
- button "Copy"
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [disabled]
|
||||
- button "Completed in {{duration}}"
|
||||
- paragraph: USER_INVOKE_REPLY acknowledged; following the injected skill.
|
||||
- button "Copy"
|
||||
- button "Good response"
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
- button "More conversation views"
|
||||
- text: Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop. {{clock}}
|
||||
- button "Copy"
|
||||
- button "Context injection runtime-context"
|
||||
- button "Context injection time-context"
|
||||
- button "Think The user wants me to ask them a checkpoint question first, then continue with whatever they interject. Let me do exactly that."
|
||||
- status: Deep diving
|
||||
- text: "Deep diving for {{duration}} ··· Interjection: include the word BANANA in your final reply."
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [disabled]
|
||||
- button "Asked questions" [expanded]
|
||||
- button "Context injection runtime-context"
|
||||
- button "Context injection time-context"
|
||||
- button "Think The user wants me to ask them a checkpoint question first, then continue with whatever they interject. Let me do exactly that."
|
||||
- button "Ask question 1/1 answered"
|
||||
- group "Questions and answers":
|
||||
|
||||
@@ -1154,6 +1154,63 @@ it.skipIf(MODE === 'record')('uses horizontal arrows to navigate files while pre
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps tall branch and changed-file menus aligned with their triggers', async () => {
|
||||
const { scaffold, page, project, tripwire } = await launchPanel('web-e2e-forge-tall-menus')
|
||||
await Promise.all(Array.from({ length: 45 }, (_, index) =>
|
||||
git(project, 'update-ref', `refs/heads/worktree/long-menu-regression-${index}`, 'HEAD')))
|
||||
const option = page.locator('[data-forge-worktree]')
|
||||
await option.getByRole('checkbox', { name: '新建工作树', exact: true }).check()
|
||||
const base = option.getByRole('button', { name: '基于 main', exact: true })
|
||||
await base.click()
|
||||
const menu = page.getByRole('menu')
|
||||
await menu.getByRole('menuitem', { name: 'worktree/long-menu-regression-44', exact: true }).waitFor()
|
||||
await expect.poll(async () => {
|
||||
const trigger = await base.boundingBox()
|
||||
const card = await menu.boundingBox()
|
||||
return trigger === null || card === null ? Infinity : Math.abs(trigger.x + trigger.width - card.x - card.width)
|
||||
}).toBeLessThan(1)
|
||||
await page.keyboard.press('Escape')
|
||||
expect(await base.getAttribute('aria-expanded')).toBe('false')
|
||||
await option.getByRole('checkbox', { name: '新建工作树', exact: true }).uncheck()
|
||||
|
||||
await git(project, 'update-ref', 'refs/remotes/origin/main', 'HEAD')
|
||||
await git(project, 'symbolic-ref', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/main')
|
||||
await mkdir(join(project, 'src'))
|
||||
await Promise.all(Array.from({ length: 60 }, (_, index) =>
|
||||
writeFile(join(project, 'src', `long-file-list-${index}.ts`), `export const value = ${index}\n`)))
|
||||
await git(project, 'add', 'src')
|
||||
await git(project, 'commit', '--quiet', '-m', 'long file list')
|
||||
const { settled } = await sendPrompt(page, scaffold)
|
||||
await settled
|
||||
await page.locator('[data-forge-header]').click()
|
||||
const panel = page.locator('[data-forge-panel]')
|
||||
await panel.getByRole('button', { name: '刷新状态', exact: true }).click()
|
||||
await panel.getByRole('button', { name: /^查看本会话的变更:60 个文件/ }).click()
|
||||
const changes = page.locator('[data-forge-changes="local"]')
|
||||
const files = changes.getByRole('button', { name: '全部 60 个文件', exact: true })
|
||||
await files.click()
|
||||
await menu.getByRole('textbox', { name: '筛选文件…', exact: true }).waitFor()
|
||||
await expect.poll(async () => {
|
||||
const trigger = await files.boundingBox()
|
||||
const card = await menu.boundingBox()
|
||||
return trigger === null || card === null ? Infinity : Math.abs(trigger.x - card.x)
|
||||
}).toBeLessThan(1)
|
||||
await menu.getByRole('textbox', { name: '筛选文件…', exact: true }).fill('long-file-list-59')
|
||||
expect(await menu.getByRole('menuitem', { name: 'src/long-file-list-59.ts', exact: true }).isVisible()).toBe(true)
|
||||
await page.keyboard.press('Escape')
|
||||
expect(await menu.count()).toBe(0)
|
||||
await page.setViewportSize({ width: 390, height: 700 })
|
||||
await files.click()
|
||||
await menu.getByRole('textbox', { name: '筛选文件…', exact: true }).waitFor()
|
||||
await expect.poll(() => menu.evaluate((node) => {
|
||||
const rect = node.getBoundingClientRect()
|
||||
return rect.left >= 12 && rect.right <= 378 && rect.top >= 12 && rect.bottom <= 688
|
||||
})).toBe(true)
|
||||
await page.keyboard.press('Escape')
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps localized status columns separate and long file lists inside one vertical scroller', async () => {
|
||||
const { scaffold, page, project, tripwire } = await launchPanel('web-e2e-forge-layout')
|
||||
await page.locator('[data-forge-current-branch]').waitFor()
|
||||
|
||||
@@ -9,7 +9,7 @@ const cases = [
|
||||
{ name: 'above placement near the desktop titlebar', side: 'top', align: 'start', x: 120, y: 110, width: 160, height: 80, left: 120, top: 142, clearance: 60 },
|
||||
{ name: 'below placement near the bottom edge', side: 'bottom', align: 'start', x: 120, y: 330, width: 160, height: 100, left: 120, top: 226, clearance: 12 },
|
||||
{ name: 'right placement near the right edge', side: 'right', align: 'start', x: 540, y: 100, width: 160, height: 100, left: 376, top: 100, clearance: 12 },
|
||||
{ name: 'window fallback when neither vertical side fits', side: 'bottom', align: 'start', x: 120, y: 180, width: 300, height: 320, left: 12, top: 60, clearance: 60 },
|
||||
{ name: 'window fallback retains horizontal anchoring when neither vertical side fits', side: 'bottom', align: 'start', x: 120, y: 180, width: 300, height: 320, left: 120, top: 60, clearance: 60 },
|
||||
] as const
|
||||
|
||||
type Placement = (typeof cases)[number]
|
||||
|
||||
@@ -392,7 +392,7 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
})
|
||||
|
||||
it('renders the direct message while retaining the following recall only in the log', async () => {
|
||||
it('renders the direct message followed by its logged recall as a collapsed Context row', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-reference-order'))
|
||||
const group = page.getByRole('treeitem', { name: /Ungrouped/ })
|
||||
await group.waitFor({ timeout: 15_000 })
|
||||
@@ -410,12 +410,19 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
|
||||
expect(inputs.map(event => event.data.source.kind)).toEqual(['user', 'session-reference'])
|
||||
expect(inputs[0]?.seq).toBeLessThan(inputs[1]!.seq)
|
||||
expect(JSON.stringify(inputs[1]?.data)).toContain('<referenced-sessions>snapshot</referenced-sessions>')
|
||||
expect(await page.locator('[data-chat-flow-kind="context"]').count()).toBe(0)
|
||||
const recall = page.locator('[data-chat-flow-kind="context"]')
|
||||
expect(await recall.count()).toBe(1)
|
||||
expect(await recall.locator('[data-context-recall-icon]').count()).toBe(1)
|
||||
expect(await recall.locator('[aria-expanded]').getAttribute('aria-expanded')).toBe('false')
|
||||
// The recall follows the direct message, matching their logged order.
|
||||
expect(await page.locator('[data-chat-flow-kind="user"], [data-chat-flow-kind="context"]')
|
||||
.evaluateAll(rows => rows.map(row => row.getAttribute('data-chat-flow-kind')))).toEqual(['user', 'context'])
|
||||
|
||||
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
|
||||
.split(TARGET_SESSION_ID).join('{{targetId}}')
|
||||
await compareOrRefreshGolden(ORDER_EXPECTED, snapshot, MODE)
|
||||
expect(snapshot).toContain('Research notes what changed?')
|
||||
// The recall row folds into the settled Turn process.
|
||||
expect(snapshot).not.toContain('Session recall Research notes')
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
|
||||
@@ -423,7 +423,10 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
await scaffold.ctx.sessions.flush(agent.session)
|
||||
expect(agent.session.snapshotEvents()).toContainEqual(injected)
|
||||
await waitForContextInClient(page, injected.seq)
|
||||
expect(await page.locator('[data-chat-flow-kind="context"]').count()).toBe(0)
|
||||
const contextRows = page.locator('[data-chat-flow-kind="context"]')
|
||||
await expect.poll(() => contextRows.count()).toBe(1)
|
||||
expect(await contextRows.getByRole('button', { name: /^Context injection/ }).getAttribute('aria-expanded')).toBe('false')
|
||||
expect(await contextRows.locator('[data-context-injection-body]').count()).toBe(0)
|
||||
}, 60_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('matches the historical conversation aria golden', async () => {
|
||||
@@ -479,7 +482,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('omits ordinary instructions from Chat while retaining their logged content', async () => {
|
||||
it.skipIf(MODE === 'record')('shows ordinary instructions as one collapsed Context row that expands to their logged content', async () => {
|
||||
const session = scaffold.ctx.sessions.get(SessionId(SEED_ID))
|
||||
if (session === undefined) throw new Error('seeded session is unavailable')
|
||||
const instructions = session.snapshotEvents().filter(event => event.type === 'user/message'
|
||||
@@ -487,7 +490,18 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
expect(instructions).toHaveLength(1)
|
||||
expect(instructions[0]?.data).toMatchObject({ source: { form: 'instructions', changes: [{ path: 'AGENTS.md' }] } })
|
||||
expect(JSON.stringify(instructions[0]?.data)).toContain('Instruction 24: preserve the logged context contract.')
|
||||
expect(await page.locator('[data-chat-flow-kind="context"], [data-context-injection-body]').count()).toBe(0)
|
||||
const row = page.locator('[data-chat-flow-kind="context"]')
|
||||
expect(await row.count()).toBe(1)
|
||||
const disclosure = row.getByRole('button', { name: /^Context injection/ })
|
||||
expect(await disclosure.getAttribute('aria-expanded')).toBe('false')
|
||||
await disclosure.click()
|
||||
try {
|
||||
await expect.poll(() => row.locator('[data-context-injection-body]').textContent())
|
||||
.toContain('Instruction 24: preserve the logged context contract.')
|
||||
} finally {
|
||||
await disclosure.click()
|
||||
}
|
||||
await expect.poll(() => row.locator('[data-context-injection-body]').count()).toBe(0)
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('restores the active turn rail mark across Chat and Trajectory', async () => {
|
||||
@@ -797,9 +811,11 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
}
|
||||
}, 60_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps short injected context out of Chat without dropping the event', async () => {
|
||||
it.skipIf(MODE === 'record')('shows short injected context as a collapsed Context row without dropping the event', async () => {
|
||||
const agent = scaffold.ctx.agents.get(SessionId(SEED_ID))
|
||||
if (agent === undefined) throw new Error('seeded session did not attach an agent')
|
||||
const contextRows = page.locator('[data-chat-flow-kind="context"]')
|
||||
const before = await contextRows.count()
|
||||
const injected = agent.session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'Short injected context.' }],
|
||||
source: { kind: 'fixture' },
|
||||
@@ -808,8 +824,16 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
await scaffold.ctx.sessions.flush(agent.session)
|
||||
expect(agent.session.snapshotEvents()).toContainEqual(injected)
|
||||
await waitForContextInClient(page, injected.seq)
|
||||
expect(await page.locator('[data-chat-flow-kind="context"]').count()).toBe(0)
|
||||
expect(await page.getByText('Short injected context.', { exact: true }).count()).toBe(0)
|
||||
await expect.poll(() => contextRows.count()).toBe(before + 1)
|
||||
const row = contextRows.last()
|
||||
const disclosure = row.getByRole('button', { name: /^Context injection/ })
|
||||
expect(await disclosure.getAttribute('aria-expanded')).toBe('false')
|
||||
await disclosure.click()
|
||||
try {
|
||||
await expect.poll(() => row.locator('[data-context-injection-body]').getByText('Short injected context.', { exact: true }).count()).toBe(1)
|
||||
} finally {
|
||||
await disclosure.click()
|
||||
}
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('restores the recorded file preview in its original tab after page reload', async () => {
|
||||
|
||||
@@ -130,7 +130,12 @@ describe.skipIf(MODE === 'record')('web e2e: user-explicit skill invocation thro
|
||||
// every later Node rebuild of the Turn (process publication, turn close).
|
||||
expect(await bubble.count()).toBe(1)
|
||||
expect(await bubble.textContent()).toBe(`/${SKILL_NAME}`)
|
||||
expect(await page.locator('[data-chat-flow-kind="context"]').count()).toBe(0)
|
||||
// The loaded instructions stay out of the bubble as their own collapsed Context row.
|
||||
// The folded Turn process holds it, so match the row without requiring it to be visible.
|
||||
const skillContext = page.locator('[data-chat-flow-kind="context"] [data-disclosure-row]')
|
||||
.filter({ hasText: new RegExp(`^Context injection${SKILL_NAME}`) })
|
||||
expect(await skillContext.count()).toBe(1)
|
||||
expect(await skillContext.getAttribute('aria-expanded')).toBe('false')
|
||||
const session = scaffold.ctx.sessions.get(sessionId)
|
||||
if (session === undefined) throw new Error('skill invocation session is unavailable')
|
||||
const injected = session.snapshotEvents().flatMap(event => event.type === 'user/message'
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
- button "More conversation views"
|
||||
- text: Stream one TypeScript fence for the highlighting snapshot. {{clock}}
|
||||
- button "Copy"
|
||||
- button "Context injection runtime-context"
|
||||
- button "Context injection time-context"
|
||||
- text: ts
|
||||
- button "Wrap lines" [pressed]
|
||||
- button "Copy"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="540" viewBox="0 0 1000 540" role="img">
|
||||
<title>Sidebar alignment follows roles and states</title>
|
||||
<rect width="1000" height="540" fill="#fff"/>
|
||||
<g font-family="Arial,sans-serif" fill="#263044">
|
||||
<text x="28" y="34" font-size="17">ALIGNMENT · schematic only; use the recorded master design for dimensions</text>
|
||||
<rect x="28" y="60" width="430" height="450" rx="16" fill="#f8f9fb" stroke="#d9dfe7"/>
|
||||
<rect x="52" y="80" width="382" height="46" rx="12" fill="#fff" stroke="#d9dfe7"/>
|
||||
<text x="243" y="109" text-anchor="middle" font-size="18">+ New conversation</text>
|
||||
<g fill="none" stroke="#647184" stroke-width="2"><circle cx="74" cy="160" r="7"/><circle cx="74" cy="200" r="7"/><rect x="67" y="300" width="14" height="12" rx="2"/></g>
|
||||
<g font-size="17"><text x="98" y="166">Navigation</text><text x="98" y="206">Navigation</text><text x="52" y="268" fill="#707b8a">Workspaces</text><text x="98" y="312">Project</text><text x="98" y="352">Conversation</text><text x="98" y="392">Conversation</text></g>
|
||||
<g fill="none" stroke="#647184" stroke-width="2"><circle cx="333" cy="262" r="6"/><circle cx="374" cy="262" r="6"/><circle cx="415" cy="262" r="6"/><circle cx="341" cy="346" r="5"/><circle cx="375" cy="346" r="5"/><circle cx="409" cy="346" r="5"/></g>
|
||||
<path d="M74 142V218 M98 142V218 M74 290V407 M98 290V407" stroke="#20826a" stroke-dasharray="5 5" fill="none"/>
|
||||
<path d="M52 432H434" stroke="#d9dfe7"/><circle cx="76" cy="472" r="17" fill="#d9e4ee"/><text x="110" y="478" font-size="17">Signed-in account</text>
|
||||
<g font-size="19"><text x="500" y="108">Center the primary action as a unit.</text><text x="500" y="177">Continue axes across intended peers.</text><text x="500" y="267">Header and row controls have distinct roles.</text><text x="500" y="307">Search expansion changes header geometry.</text><text x="500" y="386">Compare paint and hit targets separately.</text><text x="500" y="476">Keep the account row’s own geometry.</text></g>
|
||||
</g></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.5 KiB |
@@ -2805,6 +2805,8 @@ export interface Config {
|
||||
maxMessageBytes?: number
|
||||
/** Maximum simultaneous host binding calls accepted from a program. */
|
||||
maxPendingCalls?: number
|
||||
/** Time to wait for cooperative stop acknowledgment before terminating the process. */
|
||||
stopGraceMs?: number
|
||||
/** Managed process termination and output-drain grace in milliseconds. */
|
||||
graceMs?: number
|
||||
}
|
||||
@@ -4076,6 +4078,8 @@ export interface Config {
|
||||
```ts config-catalog
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Nested tool-call attempts per minute of resolved runtime timeout, including failed calls. */
|
||||
maxSubCallsPerMinute: number
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
|
||||
@@ -2807,6 +2807,8 @@ export interface Config {
|
||||
maxMessageBytes?: number
|
||||
/** Maximum simultaneous host binding calls accepted from a program. */
|
||||
maxPendingCalls?: number
|
||||
/** Time to wait for cooperative stop acknowledgment before terminating the process. */
|
||||
stopGraceMs?: number
|
||||
/** Managed process termination and output-drain grace in milliseconds. */
|
||||
graceMs?: number
|
||||
}
|
||||
@@ -4078,6 +4080,8 @@ export interface Config {
|
||||
```ts config-catalog
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Nested tool-call attempts per minute of resolved runtime timeout, including failed calls. */
|
||||
maxSubCallsPerMinute: number
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Bilingual-pair consistency record for reviewing-ui-design.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/reviewing-ui-design.md
|
||||
/:
|
||||
en: 7a1a3135b7683184
|
||||
zh: b518133afc663a6e
|
||||
/cookbook-review-a-ui-design:
|
||||
en: d42b121ca63177aa
|
||||
zh: 68b637fae3252c7b
|
||||
/cookbook-review-a-ui-design/summary:
|
||||
en: 8a1ff691b28a2533
|
||||
zh: 8c54ae85df6f6e12
|
||||
/cookbook-review-a-ui-design/table-of-contents:
|
||||
en: 84af073b2a7eca95
|
||||
zh: 750633fa3c458d5b
|
||||
/cookbook-review-a-ui-design/1-define-the-page-or-modal-in-scope:
|
||||
en: c9ee94acd903d809
|
||||
zh: c8d07c75ba94531b
|
||||
/cookbook-review-a-ui-design/1-define-the-page-or-modal-in-scope/select-scope-by-visual-impact:
|
||||
en: ef9ad1428375f1d9
|
||||
zh: 8f59cd04947c16dd
|
||||
/cookbook-review-a-ui-design/2-inspect-the-task-and-its-implementation:
|
||||
en: 86301e7487e9cd74
|
||||
zh: f13bc3da0b2ac2d3
|
||||
/cookbook-review-a-ui-design/2-inspect-the-task-and-its-implementation/reconcile-the-baseline-across-the-whole-interface:
|
||||
en: 76d008d0bac44f1f
|
||||
zh: 2357d78f350b5540
|
||||
/cookbook-review-a-ui-design/2-inspect-the-task-and-its-implementation/make-the-relationships-explicit:
|
||||
en: 4d26eca838abeb7a
|
||||
zh: 30f2046b2dc70ed6
|
||||
/cookbook-review-a-ui-design/2-inspect-the-task-and-its-implementation/keep-a-coverage-record:
|
||||
en: 61b9e821e98b042a
|
||||
zh: 9b67719f013e7c3c
|
||||
/cookbook-review-a-ui-design/3-preserve-the-starting-point:
|
||||
en: a0f8e8d720097515
|
||||
zh: 4ff54f0c03bfdb48
|
||||
/cookbook-review-a-ui-design/4-change-the-smallest-responsible-component:
|
||||
en: 8bcdb909b6825a5e
|
||||
zh: e140ca3873b302b9
|
||||
/cookbook-review-a-ui-design/5-verify-behavior-and-comparable-images:
|
||||
en: 437f3a735dbfa486
|
||||
zh: 7d6ccfd8da698c49
|
||||
/cookbook-review-a-ui-design/6-deliver-the-complete-evidence:
|
||||
en: cea04ecd81785ca2
|
||||
zh: b321ae244f050b1c
|
||||
/cookbook-review-a-ui-design/6-deliver-the-complete-evidence/comparison-delivery-and-cumulative-changes:
|
||||
en: c81cdec4ac3decca
|
||||
zh: 9dcb16b292414fda
|
||||
/cookbook-review-a-ui-design/further-exploration:
|
||||
en: 98ac3512211d94ae
|
||||
zh: 055418a72f29a15b
|
||||
/cookbook-review-a-ui-design/dev-note:
|
||||
en: b75c8458928b72b9
|
||||
zh: 003ba4e17b4b5269
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
description: "Review a DSH page or modal as a whole, explain findings through design principles, and verify supported changes."
|
||||
---
|
||||
|
||||
# Cookbook: review a UI design
|
||||
|
||||
English | [中文](reviewing-ui-design.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Use this cookbook for explicitly requested comprehensive reviews and for evidence steps justified by a change's impact or regression risk. Ordinary development follows the lightweight path in the [UI change workflow](../../.agents/skills/dsh-client-ui-ux/SKILL.md#required-review-loop); it does not execute every section below. The [principles and case reference](../ui-design-review-cases.md) supplies judgment criteria, not a mandatory checklist. Existing tests, GUI PR GIFs and designer/product approval remain governed by their original owners.
|
||||
|
||||
Check the complete icon box, including intentional internal whitespace, text columns and gaps before judging the drawn contour. Hit targets are not icon boxes; do not align each glyph’s painted pixels independently. See the [alignment principle](../ui-design-review-cases.md#alignment).
|
||||
|
||||
Check associated icons and text against the [same-foreground rule](../../.agents/skills/dsh-client-ui-ux/references/icons.md#placement-and-use), including submenu values with chevrons and search placeholders with magnifiers. Compare rendered color and opacity in both themes; preserve semantic exceptions.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [1. Define the page or modal in scope](#proposition)
|
||||
- [2. Inspect the task and its implementation](#inspect)
|
||||
- [3. Preserve the starting point](#baseline)
|
||||
- [4. Change the smallest responsible component](#implement)
|
||||
- [5. Verify behavior and comparable images](#verify)
|
||||
- [6. Deliver the complete evidence](#deliver)
|
||||
- [Further Exploration](#further-exploration)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
<a id="proposition"></a>
|
||||
## 1. Define the page or modal in scope
|
||||
|
||||
Identify the user task and affected page or modal. Record scope when several regions, consumers or design choices need coordination; a straightforward local change needs no separate plan. For an explicitly requested comprehensive review, enumerate the requested pages/modals and relevant states. Do not present local inspection as a whole-page result.
|
||||
|
||||
Use the [judgment priorities](../ui-design-review-cases.md#judgment) across that scope: alignment and spatial grouping first, then density, hierarchy, states, component choice, icon optics and geometry. Express each finding as an observable relationship, such as content appearing closer to the next group than to its own label. Follow the user’s requested order and authorization; a full review can identify multiple related defects, and permission to implement routine adjustments does not replace the [required designer/product review](../../.agents/skills/dsh-client-ui-ux/SKILL.md#design-review-escalation).
|
||||
|
||||
Read the [client UI skill](../../.agents/skills/dsh-client-ui-ux/SKILL.md) and the applicable architecture before editing. Use the [responsive skill](../../.agents/skills/dsh-responsive-ui/SKILL.md) for width and input changes, the [style reference](../web-styling.md) for tokens, and the [radius standard](../ui-radius.md) for corner geometry. This workflow adds review and evidence steps, not another set of visual tokens.
|
||||
|
||||
<a id="review-scope"></a>
|
||||
### Select scope by visual impact
|
||||
|
||||
Choose inspection scope from the table below. For broader changes, a few task-note sentences can track affected groups, consumers and risk conditions; use a coverage table only when it helps a multi-region review. Local changes do not need a scene inventory. File count and package boundaries do not determine visual reach.
|
||||
|
||||
| Change | Minimum inspection scope |
|
||||
|---|---|
|
||||
| Local copy, icon, color or spacing | Read the containing page or modal for context; inspect the owning group, neighboring groups and affected peers sharing its axes or role, including distant peers |
|
||||
| Page, form or card layout; new page or modal | Review the containing page or modal as a whole, including relevant scrolling and expanded content |
|
||||
| Shared component | Trace all affected consumers; inspect each distinct layout, content and state variant in its assembled context |
|
||||
| Global typography, tokens or styling | Map affected component families and page layouts; expand coverage across those families, rather than treating the change as local |
|
||||
| Behavior without an intended style change | Inspect the states that can change visible relationships, including loading, errors, disabled controls, expansion and focus |
|
||||
|
||||
A shared consumer can reuse evidence only when the relevant implementation, size, container constraints, content behavior and states are equivalent; record the grouping and its basis. Inspect uncertain cases directly. Source inspection of all consumers is not a claim to have opened every page. Expand scope when a defect recurs beyond the initial region or a fix changes adjacent relationships. Record unrelated discoveries separately instead of silently starting a product-wide redesign.
|
||||
|
||||
Apply the [minimum checks](../../.agents/skills/dsh-client-ui-ux/SKILL.md#minimum-checks-for-affected-ui) to each changed property: color requires both themes; layout, spacing, typography and sizing require constrained and long-content checks; interaction requires the affected path and states. These are inspection requirements, including for local edits, not a demand for a screenshot of every condition. Apply the owning responsive and behavior requirements; do not exhaust unrelated combinations or replace coverage reasoning with a fixed screenshot count. Reuse unchanged rules and valid evidence within the task, and recapture after a coherent batch of edits rather than every CSS adjustment. Read only the relevant principle sections and examples; an uncertain design decision is a reason to retrieve more detail.
|
||||
|
||||
<a id="inspect"></a>
|
||||
## 2. Inspect the task and its implementation
|
||||
|
||||
Inspect the affected result at the normal application entry point. Reuse an instance serving the current implementation; do not restart or rebuild solely for each small edit. Follow the relevant task path and use computed styles when needed to distinguish layout defects from content, loading or unfinished animation.
|
||||
|
||||
For ordinary changes, a finding needs its location, visible problem, correction and verification. Add source ownership, alternatives and conditions that would invalidate the correction when they affect the decision. Prioritize visible or shared defects; differing source numbers alone do not justify standardization.
|
||||
|
||||
When diagnosing spacing, record layout boxes, margins, padding, line boxes and visible glyph edges separately. State whether a close control aligns by its button edge or icon box, and check trigger/menu text insets across their different icon sizes. An annotation of visible whitespace is not a measured CSS margin.
|
||||
|
||||
### Reconcile the baseline across the whole interface
|
||||
|
||||
Use the [applicable-standards map](../ui-design-review-cases.md#review-baseline) to gather the current radius, material, typography, component, feedback, responsive and artwork rules that apply to this task. Record named exceptions and the relevant existing variants. When a personal or external skill differs, use the repository owner; when implementation differs from the owner, preserve the discrepancy as a finding rather than silently choosing one.
|
||||
|
||||
Apply the [whole-page and modal principles](../ui-design-review-cases.md#whole-interface-review) to the selected scope. Keep the whole composition as context and compare affected regions and repeated relationships before using local fixtures for detail. Explicit full reviews cover every region; local corrections must preserve neighboring alignment, grouping, nested corners, clipping and focus.
|
||||
|
||||
### Make the relationships explicit
|
||||
|
||||
Identify the relevant groups and alignment references. Draw axes or annotate a full-context screenshot only when the relationship is complex, disputed or needs explanation; this is not a prerequisite to reading CSS. The following prompts are optional aids for documenting a review, not fields to fill for every local change.
|
||||
|
||||
| Observation | Record | Example |
|
||||
|---|---|---|
|
||||
| Task and groups | What each group describes or operates on; its children and neighboring groups | Conversation title / three-action cluster; members section / tasks section |
|
||||
| Alignment | Objects, axis, and reference container; deliberate exceptions | Baseline-defined peer-row text starts; centered icon-plus-label in the primary button |
|
||||
| Spacing | Which gaps are internal, repeated, and between groups | Heading is closer to its cards than to the preceding section |
|
||||
| Peer geometry | Comparable roles and visible size, radius, and color relationships | Equal circular bases; one search-prompt foreground role |
|
||||
| States | Simultaneous and neighboring combinations | Selected conversation beside another hovered conversation |
|
||||
| Evidence scope | What is visible and what remains uninspected | Full Desktop sidebar versus an isolated conversation row |
|
||||
|
||||
Walk the region in three passes: whole composition, relationships between groups, and details within groups. Use source and computed geometry to explain the visible result afterward. For alignment, compare rendered text starts and slot centers; for spacing, compare visible boundaries as well as CSS boxes. Record CSS pixels separately from screenshot pixels and preserve the zoom/device scale. Do not infer a new design constant from a scaled image.
|
||||
|
||||
A useful finding states a violated relationship: “The title-to-action gap is smaller than the gaps inside the action cluster, so the title joins the first action visually; separate the two groups and retest long-title hover.” “Spacing looks off” and “all gaps use tokens” do not explain the judgment. A visible inconsistency can fail the design requirement even when it does not block a task; do not dismiss it as harmless because a click still succeeds.
|
||||
|
||||
Before declaring the page or modal reviewed, reconcile its actual regions and relevant states with the inspected evidence. Include content reached by scrolling, expansion or tab changes. A local fixture or example cannot substitute for an uninspected part of that whole. For a limited review, list the remaining scope explicitly.
|
||||
|
||||
Use locally maintained production-component fixtures to reproduce controlled states or compare variants. They supplement actual application inspection. Capture/report tools and their scene adapters can live outside the repository and consume the source checkout; they are not required product dependencies or CI acceptance. Read their local instructions for supported scenes and limits.
|
||||
|
||||
### Keep a coverage record
|
||||
|
||||
Use the following record for an explicitly requested comprehensive review or when several regions need tracking. Ordinary local changes need no coverage table or separate note reproducing every field. Inspect the affected relationships and preserve justified equivalence when grouping shared consumers.
|
||||
|
||||
| Page or dialog and entry | Semantic groups and cross-group relationships | Inspected conditions | Finding and owner | Evidence after correction | Uninspected |
|
||||
|---|---|---|---|---|---|
|
||||
| Name the actual entry and region | Specify both endpoints and complete boxes or text axes | Content, width, theme, state | Record the rule and responsible component | Comparison and relevant behavior results | State limits rather than an ambiguous covered status |
|
||||
|
||||
For complex findings, explain the compared objects, intended relationship, supporting evidence and risky neighboring effects. Include a plausible wrong fix or retest condition when it clarifies the decision; ordinary findings use the short form above. Keep unsupported candidates pending and investigate confirmed repeated problems within the chosen scope, without starting unrelated cleanup.
|
||||
|
||||
<a id="baseline"></a>
|
||||
## 3. Preserve the starting point
|
||||
|
||||
Preserve a recoverable starting version and relevant captures when a comparison is requested or needed to assess a broader change or regression risk. Routine local changes do not require an extra source snapshot or before image. When retaining a baseline, include existing uncommitted work rather than assuming `HEAD` represents the visible application, and record the source identity and capture conditions.
|
||||
|
||||
When captures are needed, save images and their conditions in `.ui-review/` at the checkout root or another explicitly selected local directory. The repository ignores `.ui-review/` for generated evidence; review tools can remain installed outside the checkout.
|
||||
|
||||
Use a scene map for comprehensive reviews or multi-state comparisons that need it; simple changes can be checked directly at the affected position. Include expanded rows, dialogs and relevant loading/disabled states when they affect the claim. A collapsed parent does not cover hidden controls. Use safe fixture data for destructive or unavailable states instead of changing a user's real task just for a screenshot.
|
||||
|
||||
<a id="implement"></a>
|
||||
## 4. Change the smallest responsible component
|
||||
|
||||
Fix the owner responsible for the task: the page for page layout, its primitive for a shared control state, or the library for glyph imbalance repeated across consumers. Reuse components and theme aliases, verify affected relationships and preserve independent workspace changes. A development fix need not first become a formal review finding.
|
||||
|
||||
Check the states that can conflict with the change: for example selected with hover or keyboard focus, expanded with focus, busy with disabled, and validation with long content. Check the narrow container and the width just on each side of a changed breakpoint. Preserve input drafts, keyboard focus, dismissal, and access to the primary action when layout changes. Follow the [testing policy](../testing.md) for checks that exercise behavior; do not add assertions that merely repeat CSS declarations.
|
||||
|
||||
Verify the causal structure first, confirm that styles actually apply, then inspect appearance and behavior. Record complete icon boxes, text starts, painted button edges, and hit-target positions separately where useful; target rectangles cannot stand in for every alignment measurement. Shared-component adjustments include consumers with and without optional descriptions, unequal content, and real neighbors rather than only the report fixture.
|
||||
|
||||
When build or hot-reload state is uncertain, create a separate application instance using the intended sources and verify its artifacts to avoid old scripts with new styles. An unchanged DOM snapshot, stale test selector, or failing capture hook cannot establish whether a visual correction passes or fails; identify the failure owner and verify current behavior with relevant tests without capture hooks.
|
||||
|
||||
<a id="verify"></a>
|
||||
## 5. Verify behavior and comparable images
|
||||
|
||||
Inspect the affected result and directly affected neighbors in the actual page. If a required scene is unavailable, report the missing check, reason and next verification; labeled production-component fixtures provide partial evidence, not an actual-page pass. Extra screenshots and comparisons are required when requested or needed to explain broad changes or assess regression risk; routine edits need no separate report. This changes recordkeeping, not the minimum inspection or existing GIF rules. For a comparison, match content, theme, locale, viewport, zoom, device scale, interaction state and content anchor; let fonts, data and transitions settle.
|
||||
|
||||
When producing a comparison, save final captures separately and pair them with the corresponding starting scene under matching conditions.
|
||||
|
||||
For retained comparisons, check dimensions and capture conditions; use scene IDs when there is an inventory. Correct mismatched conditions instead of stretching images, silently changing crops or presenting a reconstructed fixture as an earlier application screenshot. Inspect the delivered images and check that they support the stated scope.
|
||||
|
||||
Two captures with the same rendered-source fingerprint demonstrate the capture and reporting workflow, not a UI improvement. An actual before-and-after comparison captures the starting implementation before editing and the resulting implementation afterward. Keep that distinction visible in the delivered report.
|
||||
|
||||
Use equal-sized paired crops with the same content origin when a full page hides a small difference; retain enough context to show the relationship being judged. If a content change requires a different crop, disclose it and keep the original full-context captures available. Verify both themes when color, state, or icon balance is involved, and use enlarged views only as a supplement to the intended display size.
|
||||
|
||||
<a id="deliver"></a>
|
||||
## 6. Deliver the complete evidence
|
||||
|
||||
An ordinary handoff states what changed, what was checked and any material gap; a short explanation or screenshot can suffice. For a requested detailed case report, place the scene/problem and design judgment beside its image, caption the source and explain what to inspect. Avoid detached screenshot collections or images that do not show the claimed relationship.
|
||||
|
||||
When a before/after comparison is part of the deliverable, label both sides and distinguish application captures from isolated component scenes. If the user requests all changes, reconcile that scope with the final diff, including nested controls, and state missing evidence. Do not require a paired report for every ordinary correction.
|
||||
|
||||
For comprehensive reviews, distinguish inspected-without-findings, defects and uninspected regions. For ordinary changes, report the result and material gaps without a per-region status ledger. Add rule ownership, wrong-fix examples, exceptions or retest conditions only when useful to explain a finding. Known defects cannot be canceled by unrelated passing checks.
|
||||
|
||||
Report the checks actually run and their outcome, and distinguish automated metadata checks, behavior tests, and human visual inspection. Include material limitations such as an unavailable application state or an untested platform. Do not describe the work as fully verified while required scenes or checks remain outstanding.
|
||||
|
||||
Use the [UI change workflow](../../.agents/skills/dsh-client-ui-ux/SKILL.md#required-review-loop) at handoff, review and push to check new cumulative effects or invalidated evidence, not to repeat completed inspection. No extra report is required for an unchanged result. Tests and agent inspection do not replace the existing [designer/product approval requirement](../../.agents/skills/dsh-client-ui-ux/SKILL.md#design-review-escalation).
|
||||
|
||||
### Comparison delivery and cumulative changes
|
||||
|
||||
For a formal comparison report, use before on the left and after on the right. Add non-obscuring annotations only when helpful, on the after image, and retain originals; a removable overlay is useful for interactive reports. Choose a readable narrow-screen presentation rather than requiring a special report interface. Ordinary handoffs may use simple labeled images or concise text; the original GIF requirements remain unchanged.
|
||||
|
||||
Deduplicate cumulative summaries by location: pair the earliest reliable before image with the final implementation, retaining comparable conditions. Keep intermediate rounds only as historical evidence, not separate final results; reverted proposals are not current results. List another session's retained changes separately without claiming them. Disclose missing baselines or uncertain ownership. Record actual checks without adding repeated runs as extra coverage.
|
||||
|
||||
For an all-changes summary, trace each claim to the cumulative diff, current owner and retained evidence. Separate net changes from behavior already in the baseline; resolve later user revisions before naming final values. Label older captures as historical when they no longer show the current result, and distinguish source-confirmed behavior from a scene inspected again. Feed reusable decisions back to their rule owner and contrasting cases, not local pixel values or an unsupported whole-product pass.
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## Further Exploration
|
||||
|
||||
Use these owners for the details needed by a particular review.
|
||||
|
||||
- [UI design principles and review cases](../ui-design-review-cases.md) — whole-page and modal judgment, applicable rules, and explanatory examples.
|
||||
- [Rule ownership and maintenance](../../.agents/skills/dsh-client-ui-ux/SKILL.md#maintain-one-owner-per-rule) — where to change a principle and how local tools consume it.
|
||||
- [Web UI style reference](../web-styling.md) — component reuse and styling ownership.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## Dev Note
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
description: "把 DSH 页面或弹窗作为整体审查,按设计原则说明问题,并验证有依据的修改。"
|
||||
---
|
||||
|
||||
# Cookbook:审查 UI 设计
|
||||
|
||||
[English](reviewing-ui-design.md) | 中文
|
||||
|
||||
## 概览
|
||||
|
||||
本手册用于明确要求的完整审查,以及由改动影响或回归风险决定需要采用的证据步骤。普通开发按 [UI 改动流程](../../.agents/skills/dsh-client-ui-ux/SKILL.md#required-review-loop)走轻量路径,不逐节执行本手册。[原则与案例参考](../ui-design-review-cases.zh.md)提供判断依据,不是必做清单。既有测试、GUI PR GIF 和设计/产品审批继续按原有规则执行。
|
||||
|
||||
对齐审查先核对图标完整空间(含有意保留的内部留白)、文字列和间距,再检查实际轮廓。热区不等于图标框;不逐个拉齐 glyph 的像素边缘。判断顺序见[对齐原则](../ui-design-review-cases.zh.md#alignment)。
|
||||
|
||||
按[同前景色规则](../../.agents/skills/dsh-client-ui-ux/references/icons.md#placement-and-use)核对关联图标与文字,包括子菜单值与箭头、搜索占位文字与放大镜;检查明暗主题中的实际颜色和透明度,保留语义例外。
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 确定受审页面或弹窗](#proposition)
|
||||
- [2. 检查任务路径与实现](#inspect)
|
||||
- [3. 保留起点](#baseline)
|
||||
- [4. 修改最小责任组件](#implement)
|
||||
- [5. 验证行为与可比较的截图](#verify)
|
||||
- [6. 交付完整证据](#deliver)
|
||||
- [延伸阅读](#further-exploration)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
<a id="proposition"></a>
|
||||
## 1. 确定受审页面或弹窗
|
||||
|
||||
确定用户任务和受影响页面或弹窗。涉及多个区域、使用方或设计取舍时记录范围;明确的局部修改不需要单独写计划。用户明确要求完整审查时,列出指定页面/弹窗及相关状态。不能把局部检查表述成整页结论。
|
||||
|
||||
在这个范围内应用[判断优先级](../ui-design-review-cases.zh.md#judgment):先检查对齐与空间分组,再审查密度、层级、状态、组件选型、图标光学和几何。每个发现写成可观察的关系,例如内容与隔壁组的距离反而小于与自身标签的距离。遵循用户指定的顺序和授权;完整审查可以发现多个相关问题,常规调整的实施授权不能替代[既有设计/产品审查要求](../../.agents/skills/dsh-client-ui-ux/SKILL.md#design-review-escalation)。
|
||||
|
||||
编辑前阅读[客户端 UI Skill](../../.agents/skills/dsh-client-ui-ux/SKILL.md)和适用架构。宽度与输入方式变更使用[响应式 Skill](../../.agents/skills/dsh-responsive-ui/SKILL.md),令牌规则查阅[样式参考](../web-styling.zh.md),圆角几何查阅[圆角标准](../ui-radius.zh.md)。本流程补充审查和证据步骤,不另建一套视觉令牌。
|
||||
|
||||
<a id="review-scope"></a>
|
||||
### 按视觉影响选择范围
|
||||
|
||||
按下表选择检查范围。较大改动可用几句任务笔记追踪受影响的组、使用方与风险条件;多区域审查确有需要时再用覆盖表。局部修改无需场景清单。文件数量和包边界不能决定视觉影响范围。
|
||||
|
||||
| 改动 | 最小检查范围 |
|
||||
|---|---|
|
||||
| 局部文案、图标、颜色或间距 | 先看所属页面或弹窗的上下文;检查所属组、相邻组及共用轴线或角色的受影响并列元素,包括远处的并列元素 |
|
||||
| 页面、表单或卡片布局;新页面或弹窗 | 将所属页面或弹窗作为整体审查,包含相关滚动和展开内容 |
|
||||
| 共享组件 | 排查全部受影响使用方;在实际组合上下文中检查每种不同的布局、内容和状态变体 |
|
||||
| 全局字体、令牌或样式 | 梳理受影响的组件族与页面布局,按这些类别扩大覆盖,不能当成局部改动 |
|
||||
| 没有预期样式变化的行为改动 | 检查可能改变可见关系的状态,包括加载、错误、禁用、展开和焦点 |
|
||||
|
||||
只有相关实现、尺寸、容器约束、内容行为和状态等价时,共享组件使用方才能复用证据;记录归类和依据。不确定的场景直接检查。源码排查全部使用方不等于逐页打开过。问题在初始区域外重复出现,或改法影响邻接关系时,扩大范围。无关发现单独记录,不静默开始整个产品重设计。
|
||||
|
||||
对每项变化执行[最低检查要求](../../.agents/skills/dsh-client-ui-ux/SKILL.md#minimum-checks-for-affected-ui):颜色必须查明暗主题;布局、间距、字体和尺寸必须查窄容器与长内容;交互必须查受影响路径和状态。局部修改也要完成这些检查,但不要求每个条件都保存截图。遵守所属响应式与行为要求,不穷举无关组合,也不用固定截图数替代覆盖判断。同一任务中复用未变规则和有效证据,一组相关修改完成后再截图,不逐条 CSS 调整都重拍。只读取相关原则和案例;设计判断不明确时再补充查阅。
|
||||
|
||||
<a id="inspect"></a>
|
||||
## 2. 检查任务路径与实现
|
||||
|
||||
从正常应用入口检查受影响的结果。复用正在运行且使用当前实现的实例,不为每次微调单独重启或重建。沿相关任务路径检查,必要时使用计算样式区分布局缺陷与内容、加载或未结束动画造成的现象。
|
||||
|
||||
普通改动说明问题位置、可见现象、修正和验证即可。源码归属、替代方案及改法不适用的条件影响判断时再补充。优先处理可见或共享缺陷;源码数值不同本身不能成为统一数值的理由。
|
||||
|
||||
诊断间距时,分别记录布局框、margin、padding、文字行框和字形可见边缘。明确关闭控件按按钮边缘还是图标框对齐,并核对不同图标尺寸下入口与菜单的文本内缩。可见留白的标注不等于测得了 CSS margin。
|
||||
|
||||
### 在整个界面内核对适用规范
|
||||
|
||||
按[适用规范映射](../ui-design-review-cases.zh.md#review-baseline)收集本任务相关的当前圆角、材质、字体、组件、反馈、响应式和图案规则,记录明确例外与相关已有变体。个人或外部 skill 不同时,以仓库所有方为准;实现与所有方不同时,把差异保留为发现,不能默默任选一方。
|
||||
|
||||
将[整页与整个弹窗的审查原则](../ui-design-review-cases.zh.md#whole-interface-review)应用于选定范围。保留整体构图上下文,先比较受影响区域与重复关系,再用局部 fixture 分析细节。明确的完整审查覆盖全部区域;局部修正需保留邻接的对齐、分组、嵌套圆角、裁切与焦点关系。
|
||||
|
||||
### 把关系明确写出来
|
||||
|
||||
确定相关分组和对齐参照。关系复杂、有争议或需要解释时,才画轴线或标注保留上下文的截图;画图不是阅读 CSS 的前置条件。下表是记录审查时可选的提示,不要求每次局部修改逐项填写。
|
||||
|
||||
| 观察项 | 记录内容 | 示例 |
|
||||
|---|---|---|
|
||||
| 任务与分组 | 每组描述或操作什么,包含哪些子组,与谁相邻 | 会话标题/三操作组;成员区/任务区 |
|
||||
| 对齐 | 对象、轴线、参照容器及有意的例外 | 基线定义的同类行文字起点;主按钮内 icon+文字整体居中 |
|
||||
| 间距 | 哪些是组内、重复单元之间和组间距离 | 标题与所属卡片比与前一个分区更近 |
|
||||
| 并列几何 | 可比较角色的可见尺寸、圆角与颜色关系 | 圆形底板等大;搜索提示共用一个前景色角色 |
|
||||
| 状态 | 同时叠加及相邻共存的组合 | 当前会话选中,同时另一条会话悬停 |
|
||||
| 证据范围 | 能看到什么,哪些尚未检查 | 完整 Desktop 侧栏与隔离会话行的区别 |
|
||||
|
||||
按整体构图、组与组的关系、组内细节三层观察,再用源码与计算几何解释可见结果。对齐比较实际文字起点和槽位中心;间距同时比较可见边界与 CSS 框。区分 CSS 像素与截图像素,保留缩放和设备像素比,不从缩放后的图片推导新的设计常数。
|
||||
|
||||
有效发现要说明违反了什么关系:“标题到操作组的距离小于操作组内部距离,视觉上把标题粘到首个操作;分开两组,并重测长标题悬停。”只说“间距不对”或“都使用 token”都没有完成判断。明显不一致即使不阻塞任务,也能构成设计不通过,不能因为仍然点得动就忽略。
|
||||
|
||||
宣布页面或弹窗审查完成前,将它实际包含的区域和相关状态与已检查证据逐一核对,包含滚动、展开或切换标签页后才能看到的内容。局部 fixture 或案例不能替代这个整体中尚未检查的部分。局部审查则明确列出未覆盖范围。
|
||||
|
||||
用本地维护的生产组件 fixture 复现受控状态或比较变体,作为真实应用检查的补充。截图、报告工具及其场景适配可放在仓库外并读取源码工作树,不作为产品依赖或 CI 验收;支持场景与限制以工具的本地说明为准。
|
||||
|
||||
### 为审查建立覆盖记录
|
||||
|
||||
明确要求完整审查,或确有多个区域需要追踪时使用下表。普通局部修改不需要覆盖表,也不必另写笔记重述所有字段。检查受影响的关系,对共享使用方分组时保留有依据的等价判断。
|
||||
|
||||
| 页面/弹窗与入口 | 语义组及跨组关系 | 已查条件 | 发现与归属方 | 修正后证据 | 尚未检查 |
|
||||
|---|---|---|---|---|---|
|
||||
| 填实际入口与区域 | 指定比较两端、完整框或文字轴 | 内容、宽度、主题、状态 | 记录规则和责任组件 | 对照图与相关行为结果 | 写明限制,不留含糊的“已覆盖” |
|
||||
|
||||
复杂问题再说明比较对象、预期关系、支持证据和可能受损的邻接关系。错误修法或复查条件有助于判断时再写;普通发现采用前面的简短形式。证据不足的候选保留为待核实,在选定范围内排查已确认的重复问题,不顺带开展无关清理。
|
||||
|
||||
<a id="baseline"></a>
|
||||
## 3. 保留起点
|
||||
|
||||
明确要求对照,或较大改动及回归风险需要对照时,保留可恢复的起始版本与相关截图。普通局部修改不要求额外源码快照或修改前截图。保留基线时需包含已有未提交改动,不能假定 `HEAD` 就是当前可见应用,并记录源码身份与截图条件。
|
||||
|
||||
需要截图时,把图像及其条件保存在工作树根目录的 `.ui-review/`,或另行明确选择的本地目录。仓库忽略 `.ui-review/` 中的生成证据;审查工具仍可安装在工作树之外。
|
||||
|
||||
完整审查或需要追踪多个状态的对照使用场景映射;简单改动可直接检查受影响位置。展开行、弹窗及相关加载/禁用状态影响结论时应纳入,折叠父组件不能覆盖隐藏控件。破坏性或暂不可达状态使用安全 fixture 数据,不为截图而修改用户的真实任务。
|
||||
|
||||
<a id="implement"></a>
|
||||
## 4. 修改最小责任组件
|
||||
|
||||
修改负责该任务的归属方:页面布局由页面负责,共享控件状态由基础组件负责,跨使用方重复的 glyph 失衡由图标库负责。复用组件与主题别名,验证受影响关系并保留工作区已有独立改动。开发修正无需先写成一条正式审查发现。
|
||||
|
||||
检查可能与本次修改冲突的状态,例如选中与悬停或键盘焦点叠加、展开与焦点叠加、忙碌与禁用叠加,以及校验信息与长内容共存。检查窄容器和变更断点两侧的临界宽度。布局变化时保留输入草稿、键盘焦点、关闭方式及主操作的可达性。按[测试策略](../testing.zh.md)验证行为,不添加只复述 CSS 声明的断言。
|
||||
|
||||
先验证造成问题的结构,再确认样式实际生效,最后检查视觉与行为。可记录完整 icon 框、文字起点、按钮可见边缘和热区各自的计算位置;不要拿热区矩形代替全部对齐证据。公共组件的调整至少覆盖有/无可选说明、长短内容与实际相邻控件,不能仅修复报告中的一个 fixture。
|
||||
|
||||
构建或热更新状态不确定时,重新建立使用目标源码的独立应用实例并核对产物,避免旧脚本配上新样式。仅因 DOM 快照没变、测试选择器过期或截图钩子失败,不能判定视觉修正通过或失败;定位失败归属,再以更新后的真实行为预期和无截图钩子的相关用例验证。
|
||||
|
||||
<a id="verify"></a>
|
||||
## 5. 验证行为与可比较的截图
|
||||
|
||||
在实际页面中检查受影响结果及直接相关的邻接元素。必要场景不可用时,说明缺了哪项检查、原因和下一步验证方法;标明来源的生产组件 fixture 可提供部分证据,不能算作实际页面通过。用户要求对照,或解释较大改动、评估回归风险需要时,才额外保存截图和对照;常规修改无需独立报告。这里简化的是留档,不是最低检查或既有 GIF 要求。制作对照时保持内容、主题、语言、视口、缩放、设备像素比、交互状态和内容锚点一致,并等待字体、数据和过渡稳定。
|
||||
|
||||
制作对照时,将最终截图单独保存,并按对应的起始场景和相同条件配对。
|
||||
|
||||
保留对照时检查尺寸和截图条件;有场景清单时再核对场景 ID。修正不匹配的条件,不拉伸图片、默默改变裁剪,或把重建 fixture 当作更早的应用截图。检查交付图片,确认它们支持所声明的范围。
|
||||
|
||||
两次截图的渲染源码指纹相同时,只能演示截图与报告流程,不能证明 UI 改进。真正的前后对照应当在编辑前拍摄起始实现,编辑后拍摄结果实现。交付报告中也要清楚保留这一区别。
|
||||
|
||||
整页截图掩盖细小差异时,可使用尺寸相同、内容起点相同的成对裁剪图,并保留足以判断关系的上下文。内容变化确实需要不同裁剪时,说明差异,并保留完整上下文的原图。涉及颜色、状态或图标平衡时验证明暗两种主题;放大图只能补充实际显示尺寸下的检查。
|
||||
|
||||
<a id="deliver"></a>
|
||||
## 6. 交付完整证据
|
||||
|
||||
普通交付说明改了什么、检查结果和重要缺口,简短解释或截图即可。明确要求详细案例报告时,将场景/问题、设计判断与图片放在一起,图注说明来源及关注位置。避免脱离正文的截图集合或无法展示所述关系的图片。
|
||||
|
||||
交付需要前后对照时,标明两侧含义并区分真实应用截图与隔离组件场景。用户要求全部修改时,将该范围与最终 diff 核对,包含嵌套控件,并说明缺少的证据。普通修正不要求每项制作成对报告。
|
||||
|
||||
完整审查区分已查无发现、有缺陷和尚未检查的区域。普通修改报告结果和重要缺口,无需逐区域状态账目。规则归属、错误修法、例外或复查条件有助于解释发现时再补充。无关检查通过不能抵消已知缺陷。
|
||||
|
||||
报告实际运行的检查及结果,区分自动元数据校验、行为测试与人工读图。说明重要限制,例如应用状态暂不可达或某个平台尚未测试。必需场景或检查未完成时,不要声称已经完整验证。
|
||||
|
||||
交付、评审和推送时按 [UI 改动流程](../../.agents/skills/dsh-client-ui-ux/SKILL.md#required-review-loop)检查新增累计影响或失效证据,不重复已经完成的检查。结果未变时无需额外报告。测试和 agent 检查不能替代既有[设计/产品审批要求](../../.agents/skills/dsh-client-ui-ux/SKILL.md#design-review-escalation)。
|
||||
|
||||
### 对比交付与累计改动
|
||||
|
||||
正式对照报告采用左前右后。有助于解释时才在修改后图片上添加不遮挡内容的标注,并保留原图;交互报告可使用可关闭的叠加层。窄屏选择可读的呈现方式,不强制制作专门的报告界面。普通交付可用简单标明含义的图片或简短文字;既有 GIF 要求保持不变。
|
||||
|
||||
累计汇总按位置去重:在条件可比的前提下,将最早可靠的修改前截图与最终实现配对。中间轮次只保留为历史证据,不作为独立最终成果;撤销方案不计入现存成果。另一会话的保留改动单列,不归为本次成果。缺少起点或归属不确定时明确说明。记录实际检查,不将重复执行数量当作新增覆盖。
|
||||
|
||||
汇总全部修改时,将每项结论追溯到累计 diff、当前实现所有方及保留证据。区分净改动与基线已有行为,先消解用户后续修订,再给出最终数值。旧截图不再代表当前结果时标为历史证据,并区分源码核验与重新打开场景检查。将可复用的判断回写规则所有方及正反案例,不把局部像素值或缺乏依据的全产品通过结论变成规范。
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## 延伸阅读
|
||||
|
||||
按本次审查需要查阅对应归属文档。
|
||||
|
||||
- [UI 设计原则与审查案例](../ui-design-review-cases.zh.md)——整页与整个弹窗的判断原则、适用规则及解释性案例。
|
||||
- [规则归属与维护](../../.agents/skills/dsh-client-ui-ux/SKILL.md#maintain-one-owner-per-rule)——原则的修改位置及本地工具的读取方式。
|
||||
- [Web UI 样式参考](../web-styling.zh.md)——组件复用与样式归属。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## 开发备注
|
||||
|
||||
无。
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 6cc2cd9ced70bb09
|
||||
zh: 1c0e6cb4ba2ed637
|
||||
/tool-schema-catalog/tool-package-map:
|
||||
en: 17d0b1d1a3c15c7f
|
||||
zh: 1a9d90fc99e0e848
|
||||
en: 7e4516e824b1500e
|
||||
zh: b0ea6f61792d98a0
|
||||
/tool-schema-catalog/deepseek-ai-dsh-plugin-manager:
|
||||
en: e0143916bb634762
|
||||
zh: e0143916bb634762
|
||||
@@ -72,8 +72,8 @@
|
||||
en: 8c8643ef46fd075f
|
||||
zh: 8c8643ef46fd075f
|
||||
/tool-schema-catalog/deepseek-ai-dsh-tool-executor-ptc/run-code:
|
||||
en: a5d49232a604a2fb
|
||||
zh: 720040d11a3e31c2
|
||||
en: 7917e16cbc2c366f
|
||||
zh: c39eb0420544f04a
|
||||
/tool-schema-catalog/deepseek-ai-dsh-plan-mode:
|
||||
en: b500b530845e26a9
|
||||
zh: b500b530845e26a9
|
||||
@@ -264,8 +264,8 @@
|
||||
en: b53326fedeab850d
|
||||
zh: ecb75b6ce31da009
|
||||
/tool-schema-catalog/deepseek-ai-dsh-tool-subagent-control/wait-agent:
|
||||
en: f3cf5a0c56ddc07a
|
||||
zh: 7f6754d71e1b4ae4
|
||||
en: 4b95d11b610d34ca
|
||||
zh: 9b0d9b6012be30d8
|
||||
/tool-schema-catalog/deepseek-ai-dsh-tool-jobs:
|
||||
en: 9be673b1604d0114
|
||||
zh: 9be673b1604d0114
|
||||
|
||||
@@ -21,7 +21,7 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`, `stagehand_extract`, `stagehand_navigate`, `stagehand_observe`, `stagehand_screenshot`, `stagehand_tabs` | `ctx.browserUse`, `ctx.agents`, `ctx.tools`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`, `ctx.agents`, `ctx.sessionProjections`, `ctx.remoteAccess` | `tool/call`, `tool/result`, `plugin:action-cards/decision`, `user/message` | - | Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized recommendations bundle supplies the shipped kinds. |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`, `ctx.userQuestions` | `tool/call`, `tool/result after an answer or timeout`, `late user/message` | - | ask_user_question keeps the original blocking behavior by default; set `mode: timed` to opt into a foreground timeout and pending result while the question remains answerable. In timed mode, `timeout: -1` keeps that call blocking indefinitely. |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`, `ctx.tools`, `ctx.ptcRuntime`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`, `ctx.tools`, `ctx.ptcRuntime`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. The resolved timeout and maxSubCallsPerMinute set the total call allowance; exceeding it terminates the program. |
|
||||
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agents`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. |
|
||||
| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.workingDirectory`, `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. With a job registry composed every call registers with the generic `ctx.jobs` runtime as it starts, collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; without one, or with `enableRunInBackground: false`, the tool registers a foreground-only schema without the `run_in_background` parameter. |
|
||||
| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.workingDirectory`, `ctx.tools`, `ctx.fs`, `ctx.agents` | `tool/call`, `deliverables/presented after a successful final result`, `tool/result` | - | Deliveries belong to the calling Session; Web ui-deliverables supplies source-file opening and cards. |
|
||||
@@ -661,7 +661,7 @@ Execute a TypeScript program against the available tools. Takes two required arg
|
||||
|
||||
Source: [`packages/core/tool-executor-ptc/src/ptc.ts`](../packages/core/tool-executor-ptc/src/ptc.ts)
|
||||
|
||||
The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program.
|
||||
The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. The resolved timeout and maxSubCallsPerMinute set the total call allowance; exceeding it terminates the program.
|
||||
|
||||
<a id="deepseek-aidsh-plan-mode"></a>
|
||||
|
||||
@@ -2157,7 +2157,7 @@ Source: [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/sub
|
||||
|
||||
### `wait_agent`
|
||||
|
||||
Wait for input at your next step, including agent messages, completion notifications, and user steering. Returns immediately when that input is already pending.
|
||||
Wait for input at your next step, including agent messages, completion notifications, and user steering. Returns immediately if new input is pending. `timedOut: false` means new input is pending. Pending input is delivered to the model after code execution finishes.
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
| `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`、`stagehand_extract`、`stagehand_navigate`、`stagehand_observe`、`stagehand_screenshot`、`stagehand_tabs` | `ctx.browserUse`、`ctx.agents`、`ctx.tools`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`、`ctx.agents`、`ctx.sessionProjections`、`ctx.remoteAccess` | `tool/call`、`tool/result`、`plugin:action-cards/decision`、`user/message` | - | 只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化推荐 Bundle 提供随产品发布的种类。 |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`、`ctx.userQuestions` | `tool/call`、`tool/result after an answer or timeout`、`late user/message` | - | ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才启用前台超时与 pending 结果,同时问题仍可回答;timed 模式内 `timeout: -1` 让本次调用无限期阻塞。 |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`、`ctx.tools`、`ctx.ptcRuntime`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。 |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`、`ctx.tools`、`ctx.ptcRuntime`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。最终超时时间与 maxSubCallsPerMinute 决定调用总额度;超限会终止程序。 |
|
||||
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agents`、`ctx.userQuestions (execution time, opportunistic)` | `tool/call`、`plan/mode inactive on an approved review`、`tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 |
|
||||
| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.workingDirectory`、`ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`、`tool/result` | - | bash 工具是 bash 执行器 seam 面向模型的消费方。组合中有 job 注册表时,每次调用一启动就注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;没有注册表或 `enableRunInBackground: false` 时,工具注册不带 `run_in_background` 参数的纯前台 schema。 |
|
||||
| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.workingDirectory`、`ctx.tools`, `ctx.fs`, `ctx.agents` | `tool/call`, `deliverables/presented 在成功的最终结果之后`, `tool/result` | - | 交付归调用方 Session 所有;Web ui-deliverables 提供源文件打开与卡片。 |
|
||||
@@ -665,7 +665,7 @@ ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才
|
||||
|
||||
来源:[`packages/core/tool-executor-ptc/src/ptc.ts`](../packages/core/tool-executor-ptc/src/ptc.ts)
|
||||
|
||||
PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。
|
||||
PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。最终超时时间与 maxSubCallsPerMinute 决定调用总额度;超限会终止程序。
|
||||
|
||||
<a id="deepseek-aidsh-plan-mode"></a>
|
||||
|
||||
@@ -2163,7 +2163,7 @@ subagent_oar 工具在为外部提供方配置的别名之间选择。本目录
|
||||
|
||||
### `wait_agent`
|
||||
|
||||
等待下一步可处理的输入,包括 Agent 消息、完成通知和用户引导。已有此类待处理输入时立即返回。
|
||||
等待下一步可处理的输入,包括 Agent 消息、完成通知和用户引导。有新的待处理输入时立即返回。`timedOut: false` 表示有新输入待处理。待处理输入会在代码执行结束后交付给模型。
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# Bilingual-pair consistency record for ui-design-review-cases.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/ui-design-review-cases.md
|
||||
/:
|
||||
en: 1da13fa1c536aa24
|
||||
zh: 52ef6218c8cea359
|
||||
/ui-design-principles-and-review-cases:
|
||||
en: 4be954bceeeb7940
|
||||
zh: d91f6595cd3e85f0
|
||||
/ui-design-principles-and-review-cases/summary:
|
||||
en: 75120a3fd02294c9
|
||||
zh: 498343d7b9b30e46
|
||||
/ui-design-principles-and-review-cases/table-of-contents:
|
||||
en: c5680dfc600a7495
|
||||
zh: c76ec05c69f1e93c
|
||||
/ui-design-principles-and-review-cases/what-a-review-must-judge:
|
||||
en: eeff4a5f5e487908
|
||||
zh: 51eab38ccd10190b
|
||||
/ui-design-principles-and-review-cases/review-the-entire-page-or-modal-as-one-unit:
|
||||
en: 324c8d9a0ff36ebb
|
||||
zh: 3a4c64e15b59f98f
|
||||
/ui-design-principles-and-review-cases/review-the-entire-page-or-modal-as-one-unit/judge-the-whole-the-groups-and-the-elements:
|
||||
en: 08450af012ed9ae9
|
||||
zh: 7776aad3791eee79
|
||||
/ui-design-principles-and-review-cases/review-the-entire-page-or-modal-as-one-unit/relate-each-finding-back-to-the-whole:
|
||||
en: 40b5b26e5ff31ae8
|
||||
zh: 0fdb1fdef7243c35
|
||||
/ui-design-principles-and-review-cases/review-the-entire-page-or-modal-as-one-unit/use-a-relationship-inventory-to-establish-coverage:
|
||||
en: 138392750105bf23
|
||||
zh: 2cf34065251d93df
|
||||
/ui-design-principles-and-review-cases/combine-the-applicable-standards-first:
|
||||
en: fd2d0f3433eb7e20
|
||||
zh: 2034723f45360e79
|
||||
/ui-design-principles-and-review-cases/choose-cases-that-expose-a-design-decision:
|
||||
en: f8b0f72251aa044f
|
||||
zh: a8b05ce5f3da4221
|
||||
/ui-design-principles-and-review-cases/principle-examples:
|
||||
en: 7b8a8bdc5acc11d6
|
||||
zh: 96820c11d2e61787
|
||||
/ui-design-principles-and-review-cases/1-alignment:
|
||||
en: 0cc9d24851a11497
|
||||
zh: f05bda4fcdf5deac
|
||||
/ui-design-principles-and-review-cases/1-alignment/text-metrics-and-unequal-content:
|
||||
en: 14558eb00e9b4902
|
||||
zh: 333b28dabfccbd6c
|
||||
/ui-design-principles-and-review-cases/1-alignment/example-a1-role-based-sidebar-alignment:
|
||||
en: 593b824ab3fc6474
|
||||
zh: 19692388cdf7da7d
|
||||
/ui-design-principles-and-review-cases/1-alignment/example-a2-distinct-header-and-row-action-groups:
|
||||
en: 81d165cc1c5c1f7e
|
||||
zh: 4aef022e141ced64
|
||||
/ui-design-principles-and-review-cases/1-alignment/example-a3-alignment-between-rows-and-compound-controls:
|
||||
en: 9e7fae1a4e649ffd
|
||||
zh: c3992d7dbedbb247
|
||||
/ui-design-principles-and-review-cases/1-alignment/example-a4-centering-a-content-group-within-its-container:
|
||||
en: 478771b47daf2cba
|
||||
zh: 0eee64751cff6c94
|
||||
/ui-design-principles-and-review-cases/1-alignment/example-a5-visible-size-and-center-of-peer-controls:
|
||||
en: 376359e2a1de44e9
|
||||
zh: 2a0b0e4472efefb7
|
||||
/ui-design-principles-and-review-cases/1-alignment/example-a6-text-starts-across-triggers-and-menus:
|
||||
en: 1afbbce3f4908bf2
|
||||
zh: aaf54b32b90a2c82
|
||||
/ui-design-principles-and-review-cases/1-alignment/example-a7-choosing-the-close-control-edge:
|
||||
en: c5e9a409c13637bc
|
||||
zh: 37c915f187d6e9f7
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping:
|
||||
en: 5a2aebab7e8ab498
|
||||
zh: 46a18d4ddde7fccc
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping/example-g1-separating-content-from-its-action-group:
|
||||
en: a4c0b1f1ad441155
|
||||
zh: 360ed71994a101a8
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping/example-g2-spacing-between-complex-sections:
|
||||
en: 2585b4f2a35bda67
|
||||
zh: 59b6c9d987ee95bb
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping/example-g3-nested-groups-along-the-reading-order:
|
||||
en: 4d24915951f86d97
|
||||
zh: 4d6c348fdb7b78de
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping/example-g3-nested-groups-along-the-reading-order/primer-equal-control-widths-do-not-fix-a-broken-reading-order:
|
||||
en: 65f62d6b2d933d6e
|
||||
zh: 3332fa1c82053c71
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping/example-g3-nested-groups-along-the-reading-order/carbon-different-distances-carry-different-meanings:
|
||||
en: 9d7e04a0314ac527
|
||||
zh: fc654332df43d0c1
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping/example-g4-separating-groups-with-different-purposes:
|
||||
en: 77bc7ec83d75ff29
|
||||
zh: caab9079ae3e416a
|
||||
/ui-design-principles-and-review-cases/2-spatial-grouping/composed-whitespace-and-responsive-grouping:
|
||||
en: 47f8ea920c78c5be
|
||||
zh: e64ab054666c3727
|
||||
/ui-design-principles-and-review-cases/3-whole-page-density:
|
||||
en: 10fb29eddaac1951
|
||||
zh: e05636b47e860966
|
||||
/ui-design-principles-and-review-cases/4-information-hierarchy:
|
||||
en: 7e86e6ff5cd55742
|
||||
zh: 852a1bb0361134a6
|
||||
/ui-design-principles-and-review-cases/4-information-hierarchy/example-c1-color-emphasis-within-one-information-unit:
|
||||
en: c17aea1873e9a1a6
|
||||
zh: dc7a8df8a60e582d
|
||||
/ui-design-principles-and-review-cases/5-control-states:
|
||||
en: d247699132b484a0
|
||||
zh: b71e25d834ce2633
|
||||
/ui-design-principles-and-review-cases/5-control-states/example-s1-persistent-selection-beside-temporary-hover:
|
||||
en: 729e11936e471a67
|
||||
zh: d1da0c090e78efb5
|
||||
/ui-design-principles-and-review-cases/6-component-choice:
|
||||
en: 7a49fdd5a601aa48
|
||||
zh: e2f9de0d5628a878
|
||||
/ui-design-principles-and-review-cases/7-icon-optics:
|
||||
en: ff60eb03e9724e71
|
||||
zh: 8df625c33f3fc3aa
|
||||
/ui-design-principles-and-review-cases/7-icon-optics/example-o1-optical-alignment-of-different-icon-types:
|
||||
en: 3858a85bdf6ee111
|
||||
zh: 9c8242a594df069f
|
||||
/ui-design-principles-and-review-cases/7-icon-optics/example-o2-shared-artwork-at-different-consumer-sizes:
|
||||
en: 6f29eb80bfe3baf4
|
||||
zh: eb3161af01dd752e
|
||||
/ui-design-principles-and-review-cases/8-control-geometry-and-corner-relationships:
|
||||
en: 262cfa8e7dfb969f
|
||||
zh: 4987ad06db795885
|
||||
/ui-design-principles-and-review-cases/8-control-geometry-and-corner-relationships/decide-geometry-in-this-order:
|
||||
en: 26419c2cbae93712
|
||||
zh: 0f72032f89169b75
|
||||
/ui-design-principles-and-review-cases/8-control-geometry-and-corner-relationships/decide-geometry-in-this-order/fluent-the-split-button-notch-is-a-relationship-error:
|
||||
en: ac1410636b2683ae
|
||||
zh: c39f006a594cbc36
|
||||
/ui-design-principles-and-review-cases/8-control-geometry-and-corner-relationships/example-r1-radii-across-container-and-control-roles:
|
||||
en: d7833e6828c48bec
|
||||
zh: 66728e53ef3221dc
|
||||
/ui-design-principles-and-review-cases/8-control-geometry-and-corner-relationships/example-r2-small-selection-controls-within-a-form:
|
||||
en: e632805e838b1d3a
|
||||
zh: 6bda70ade710d4c0
|
||||
/ui-design-principles-and-review-cases/8-control-geometry-and-corner-relationships/example-r3-justified-geometry-differences-between-roles:
|
||||
en: 5137089b3644cfb5
|
||||
zh: e8b0da147f77a63e
|
||||
/ui-design-principles-and-review-cases/reproduce-and-compare:
|
||||
en: ec1ff65e6e15cea6
|
||||
zh: 9dc2344aadbbeea7
|
||||
/ui-design-principles-and-review-cases/external-references-and-what-dsh-adopts:
|
||||
en: 274dcca699c277ed
|
||||
zh: ecf31d4082bbd309
|
||||
/ui-design-principles-and-review-cases/external-references-and-what-dsh-adopts/codex-a-limited-reference-for-composition:
|
||||
en: 38e0bfd3e40da01d
|
||||
zh: 87aa406f9138be3e
|
||||
/ui-design-principles-and-review-cases/dev-note:
|
||||
en: b75c8458928b72b9
|
||||
zh: 003ba4e17b4b5269
|
||||
@@ -0,0 +1,445 @@
|
||||
---
|
||||
description: "Design judgment for DSH: alignment, spatial grouping, hierarchy, states, geometry, and worked review cases."
|
||||
---
|
||||
|
||||
# UI design principles and review cases
|
||||
|
||||
English | [中文](ui-design-review-cases.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Consult the relevant cases when a design relationship is unclear. Ordinary changes inspect affected relationships in page/modal context; they do not require every case, an inventory or a full-page audit. The whole-interface review sections apply when a comprehensive review is explicitly requested. Examples explain transferable principles rather than coverage. See the [review cookbook](cookbook/reviewing-ui-design.md) for optional detailed methods and the owning workflow for effort levels.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [What a review must judge](#judgment)
|
||||
- [Review the entire page or modal as one unit](#whole-interface-review)
|
||||
- [Combine the applicable standards first](#review-baseline)
|
||||
- [Choose cases that expose a design decision](#case-selection)
|
||||
- [Principle examples](#case-index)
|
||||
- [1. Alignment](#alignment)
|
||||
- [2. Spatial grouping](#grouping)
|
||||
- [3. Whole-page density](#density)
|
||||
- [4. Information hierarchy](#hierarchy)
|
||||
- [5. Control states](#states)
|
||||
- [6. Component choice](#component-choice)
|
||||
- [7. Icon optics](#icon-optics)
|
||||
- [8. Control geometry and corner relationships](#geometry)
|
||||
- [Reproduce and compare](#reproduce)
|
||||
- [External references and what DSH adopts](#references)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
<a id="judgment"></a>
|
||||
## What a review must judge
|
||||
|
||||
A well-composed interface makes relationships visible before the reader studies individual labels. Alignment lets people compare parallel items without repeatedly searching for their starting points. Spacing tells them which icon, label, description, control, and action belong together. These are primary design requirements, including for a fully functional screen; they are not optional polishing after token checks pass.
|
||||
|
||||
DSH prioritizes alignment and spatial grouping in visual review. First identify the task and semantic groups, then inspect shared axes across the whole region, then check each group's internal relationships. Follow with hierarchy, state, geometry, color, density, and optical detail. A blocked action or misleading state still takes priority over cosmetic work. Do not make every element equally large, equally spaced, or equally prominent in pursuit of consistency.
|
||||
|
||||
The page or modal provides the reference for composition; the [impact scope](cookbook/reviewing-ui-design.md#review-scope) determines which relationships require inspection for a change. Navigation, content, forms and actions may come from different components while sharing text axes and spacing rules. Explain exceptions through role, hierarchy or state; component ownership alone is not a design reason.
|
||||
|
||||
The reference diagrams explain relationships; they are not implementation captures or accepted before-and-after results. The examples below explain particular principles and do not establish coverage of the page or modal under review.
|
||||
|
||||
<a id="whole-interface-review"></a>
|
||||
## Review the entire page or modal as one unit
|
||||
|
||||
Treat the entire page or modal as one visual unit when choosing references and judging relationships. A routine change reviews its affected groups and cross-group relationships in that context; it does not automatically audit every region or the whole product. An explicit whole-interface review must inspect all its regions. A sidebar, form or toolbar remains part of the larger composition even when owned by a separate component.
|
||||
|
||||
For a modal, include its title, close control, all content sections, scrolling area and footer actions. For a page, include its navigation, header, toolbars, content and persistent actions. Content below the fold remains part of the same unit. Follow the page or modal's own edges when choosing alignment and centering references; an open modal has its own composition above the underlying page.
|
||||
|
||||
### Judge the whole, the groups and the elements
|
||||
|
||||
First read the whole composition: content edges, visual center, reading order, primary action and overall density. Then identify groups and subgroups from what they describe or operate on. Compare neighboring groups, followed by the elements within them. Repeated relationships must remain consistent across the entire page or modal, including distant sections.
|
||||
|
||||
| Aspect | Compare throughout the page or modal | Basis for judgment |
|
||||
|---|---|---|
|
||||
| Alignment | Text starts, icon centers, control edges, repeated action columns | Choose left, center or two-sided alignment by role; continue shared axes across groups; judge an icon and label together |
|
||||
| Spatial grouping | Label-to-content, item-to-item, group-to-group and section-to-section distances | Internal gaps are smaller than external gaps; more complex groups need greater separation; repeated group structures reuse internal gaps |
|
||||
| Size and proportion | Same-role controls, icon backgrounds, icons and adjacent text | Use consistent size variants and optical balance; adjusting size can support shared alignment |
|
||||
| Corner geometry | Containers, cards, fields, buttons and selection controls | Apply one role-based radius system throughout; distinguish inset, flush and independent relationships using the existing radius standard |
|
||||
| Color and emphasis | Text, associated icons, backgrounds, borders and secondary information | Match the emphasis of an icon and label that communicate together; explain role or state differences |
|
||||
| Typography and density | Headings, labels, descriptions, repeated rows and overall whitespace | Keep equal-level content consistent and make hierarchy readable; local spacing must fit the whole composition |
|
||||
| States and behavior | Selected, hovered, focused, disabled, expanded, loading and failed content | State changes preserve recognizable groups, alignment and action access; compare states that appear together |
|
||||
|
||||
Consistency means applying the same reasoning throughout this unit. Elements with the same role and size variant follow the same treatment; differences follow from role, hierarchy or state. Being implemented by a different component does not justify a different text start, spacing pattern or radius. Likewise, a centered title and a left-aligned reading column can coexist when their roles support that composition.
|
||||
|
||||
### Relate each finding back to the whole
|
||||
|
||||
Record which elements conflict, which relationship they should share, and how the correction fits the rest of the page or modal. Compare every occurrence of that relationship within the review scope. After a local adjustment, return to the whole composition and check affected axes, grouping, geometry and hierarchy together.
|
||||
|
||||
A full review covers all regions in scope, including relevant scroll positions, expanded content and tabs, and states that change their relationships. Inspect supported widths, themes and input methods where applicable. Preserve drafts, focus, dismissal and primary-action access; show failures with the appropriate recovery. Report uninspected regions and states explicitly. One clean crop, component fixture or selected example cannot establish that the whole page or modal is consistent.
|
||||
|
||||
### Use a relationship inventory to establish coverage
|
||||
|
||||
List the semantic groups in the assembled interface, then the relationships to compare: parent-child indentation, text columns across sections, repeated action slots, headings and descriptions, fields and validation, prose and inline controls, and scrolling content versus fixed actions. Inspect more than group interiors; every cross-group relationship needs two actually inspected endpoints. When changing a shared primitive, include consumers with and without descriptions, unequal content, and different action combinations.
|
||||
|
||||
A relationship passes only when its structure uses one reference, its rendered appearance communicates that relationship, and relevant content or state changes preserve it. An existing assertion, shared token, successful capture, or isolated crop cannot replace these three requirements. Prioritize by misleading effect, frequency, and reach; a difference of only a few pixels does not excuse repeated misalignment.
|
||||
|
||||
Record inspected without additional findings, fixed and rechecked, found but unresolved, and uninspected separately. Declare platform and exceptional-state limits where exhaustive coverage is unavailable; example counts, component-test counts, and page-entry counts do not establish that every design passes.
|
||||
|
||||
<a id="review-baseline"></a>
|
||||
## Combine the applicable standards first
|
||||
|
||||
Use the [rule-owner map](../.agents/skills/dsh-client-ui-ux/SKILL.md#select-the-rule-owners) to select current implementation guidance, including alignment, spacing and icons.
|
||||
|
||||
A review has two independent obligations: honor the applicable DSH rules and explain whether the assembled result communicates the intended relationships. A convincing silhouette does not excuse the wrong control semantics; legal tokens do not excuse bad alignment. For a scoped review, mark unrelated areas as out of scope. For a whole-interface review, apply the relevant rows below across all regions of the page or modal under review.
|
||||
|
||||
| Owner | What it contributes to the review | What to compare across the page or modal |
|
||||
|---|---|---|
|
||||
| [Radius standard](ui-radius.md#radius-scale), [nested geometry](ui-radius.md#nested-hover) | Role before size; inset, flush-fill, and independent-child arrangements; curve and documented exceptions | Outer surface, field, hover fill, indicator and clipped edges together |
|
||||
| [Web styling](web-styling.md#component-rules) | Semantic colors, typography pairs, hairlines, elevation, menu material and motion | Same-role surfaces across features, both themes, native Desktop backdrop |
|
||||
| [Client UI/UX skill](../.agents/skills/dsh-client-ui-ux/SKILL.md) | Component reuse, hierarchy, loading, feedback ownership and window clearance | Loaded/empty/error scenes and the actual surrounding page |
|
||||
| [Responsive skill](../.agents/skills/dsh-responsive-ui/SKILL.md) | Space allocation before truncation, input capabilities, focus and primary-action access | Wide, constrained, keyboard and touch use of the same task |
|
||||
| [Official artwork skill](../.agents/skills/dsh-official-plugin-artwork/SKILL.md) | Package artwork semantics, optical balance, transparent regions and neighboring references | Plugin artwork in list and detail contexts; action icons remain a separate family |
|
||||
| [Error-handling skill](../.agents/skills/dsh-error-handling/SKILL.md#localized-actionable-visible-reports) | Visible failure, retained valid state and a safe next action | Field correction, failed operation and failed query as distinct situations |
|
||||
| [Primitives catalog](../packages/client/ui-primitives/README.md#component-catalog) and the relevant package README | Existing variants, focus/interaction behavior and feature-specific requirements | Intended component role versus a visually similar substitute |
|
||||
|
||||
The current repository owner and its explicitly recorded exceptions take precedence over an older personal skill or external example. A user choice applies to its named family; it does not silently replace the global system. If implementation disagrees with its stated owner, report that discrepancy instead of treating the existing code as a new rule. Re-read the owner when selecting a radius or material rather than copying a scale into the review.
|
||||
|
||||
<a id="case-selection"></a>
|
||||
## Choose cases that expose a design decision
|
||||
|
||||
An example makes a principle observable through a task, related elements, surrounding context and a condition that can challenge the correction. Extract the relationship and apply it throughout the page or modal being reviewed. A sidebar or model-editor example does not restrict that principle to those components. Examples are explanatory evidence, not a review menu or coverage list.
|
||||
|
||||
Distinguish three kinds of evidence. An actual DSH scene establishes a defect in the displayed conditions. An official design-system example teaches a particular relationship; its dimensions and interaction policy remain that system's. An original diagram explains a proposed relation and must not be labeled an implemented improvement. A fixture only covers its rendered conditions. Keep source, compared objects, applied DSH rule and retest conditions adjacent to the case.
|
||||
|
||||
Present each case as scene and problem, design judgment, then its screenshot or paired comparison immediately below. Add a caption naming what to inspect, what each side means, and the source. Do not collect unrelated cases into a detached screenshot wall. If the difference is only faintly visible or the image omits the relationship, choose stronger evidence instead of presenting it as a design exemplar.
|
||||
|
||||
<a id="case-index"></a>
|
||||
## Principle examples
|
||||
|
||||
Use these examples to understand a relationship that is unclear. Apply the same principles to every relevant region of the current page or modal, including regions not illustrated here. Review scope comes from the requested interface, not from this example list.
|
||||
|
||||
| Case | Relationship to inspect |
|
||||
|---|---|
|
||||
| [A1](#case-sidebar-axes) | Role-based sidebar alignment |
|
||||
| [A2](#case-action-columns) | Distinct header and row action groups |
|
||||
| [A3](#case-preview-toolbar) | Alignment between rows and compound controls |
|
||||
| [A4](#case-empty-center) | Centering a content group within its container |
|
||||
| [A5](#case-composer-controls) | Visible size and center of peer controls |
|
||||
| [A6](#case-trigger-menu-text) | Text starts across triggers and menus |
|
||||
| [A7](#case-close-control-edge) | Choosing the close-control edge |
|
||||
| [G1](#case-title-actions) | Separating content from its action group |
|
||||
| [G2](#case-team-sections) | Spacing between complex sections |
|
||||
| [G3](#case-plugin-groups) | Nested groups along the reading order |
|
||||
| [G4](#case-sidebar-groups) | Separating groups with different purposes |
|
||||
| [C1](#case-search-color) | Color emphasis within one information unit |
|
||||
| [S1](#case-sidebar-state) | Persistent selection beside temporary hover |
|
||||
| [O1](#case-file-optics) | Optical alignment of different icon types |
|
||||
| [O2](#case-chevron-consumers) | Shared artwork at different consumer sizes |
|
||||
| [R1](#case-model-radius) | Radii across container and control roles |
|
||||
| [R2](#case-checkbox-radius) | Small selection controls within a form |
|
||||
| [R3](#case-automation-radius) | Justified geometry differences between roles |
|
||||
|
||||
<a id="alignment"></a>
|
||||
## 1. Alignment
|
||||
|
||||
The [alignment reference](../.agents/skills/dsh-client-ui-ux/references/alignment.md) owns shared axes, complete layout boxes, indentation and text metrics. Use the examples below to apply those decisions.
|
||||
|
||||

|
||||
|
||||
The schematic distinguishes peer-row axes from account and header geometry; it is not a pixel specification. Compare content, painted backgrounds and hit targets separately; different roles need not share every axis.
|
||||
|
||||
### Text metrics and unequal content
|
||||
|
||||
An inline icon refers to adjacent text, not the center of the entire paragraph line box. Cap height can calibrate a particular font combination, but is not a universal formula for every font and icon; do not copy one successful offset into every control. A status icon beside multiline guidance usually attaches to the first line, while a single-line button centers its icon and label together. Recheck that relationship after changes to font size, fallback, or language.
|
||||
|
||||
The comparison below uses actual Markdown rendering, before on the left and after on the right, with annotations only on the right. CJK, Latin, and code links retain icon dimensions while font metrics correct vertical placement; external and internal gaps separately establish link grouping. Inspect wrapping in the complete paragraph for long links; this crop cannot establish their usability.
|
||||
|
||||

|
||||
|
||||
Peer cards jointly establish heading starts, description first lines, and action edges. A short description must not independently center vertically because its neighbor is longer; place remaining height after the content group, while bottom actions may align separately. Equal card height does not establish a consistent reading order or require truncating real content to force equal heights.
|
||||
|
||||
The comparison below shows actual preset cards with unequal descriptions. On the left, automatic margins detach the short description from its heading; on the right, descriptions start together below their headings and actions remain aligned. The relationship also applies to result cards, choice cards, and parallel summaries; it does not require every page to use cards.
|
||||
|
||||

|
||||
|
||||
<a id="case-sidebar-axes"></a>
|
||||
### Example A1: Role-based sidebar alignment
|
||||
|
||||
**Scene and relationship.** Compare navigation, workspace rows, conversations and the account area in the expanded sidebar. Include the centered New conversation action and the painted row edges, not only text starts.
|
||||
|
||||
**Failure and correction.** Applying one leading-slot width to every role can move titles, enlarge spacing or distort the signed-in account row. Preserve the owning shared variables, compact icon slots and paint insets. Retain deliberate account geometry and hierarchy instead of manufacturing one text axis across all roles.
|
||||
|
||||
**Acceptance.** Use guides to compare same-role rows. Check short and long titles, optional icons, project expansion and signed-in/out account states. Revealing row actions must not move the title start. A list crop does not establish whole-sidebar alignment.
|
||||
|
||||
<a id="case-action-columns"></a>
|
||||
### Example A2: Distinct header and row action groups
|
||||
|
||||
**Scene and relationship.** Workspace search/filter/add and conversation more/archive/pin occupy the trailing sidebar region but have different roles and spacing. Compare repeated rows with each other; compare the header with its own collapsed and expanded search states.
|
||||
|
||||
**Failure and correction.** Choose control sizes, gaps and trailing insets by group role, including during search expansion. Keep the title-to-action separation independent of the gaps inside a cluster.
|
||||
|
||||
**Acceptance.** Show the header and row actions together, then expand search and collapse the sidebar. Verify the intended edge relationships, unclipped hit targets and long-title clearance. Do not add empty actions or offsets merely to make unrelated controls form vertical columns.
|
||||
|
||||
<a id="case-preview-toolbar"></a>
|
||||
### Example A3: Alignment between rows and compound controls
|
||||
|
||||
**Scene and relationship.** Panel controls sit above file-display controls and an “Open in app” split button. The two rows belong to one preview header, while each row contains operations with different scopes.
|
||||
|
||||
**Failure and correction.** Each component may be internally centered while the assembled header has unrelated right insets, heights, and group gaps. Relate the upper icon boxes to the complete lower control, preserving internal icon whitespace rather than aligning extra hit padding or an individual glyph’s painted pixels. Align peer control centers; keep display controls together and separate the external-open action. Treat the split button as one unit. Reducing its outer width can equalize visible gaps while retaining the preceding icon columns; verify both segment targets and the frame’s contribution to layout. Do not force its arrow onto a column intended for an unrelated panel action.
|
||||
|
||||
**Acceptance.** Inspect both rows with the path, tabs, and content edge visible. Repeat with a long path and a narrow pane. A row of standalone icons is not a substitute for the actual split control.
|
||||
|
||||
<a id="case-empty-center"></a>
|
||||
### Example A4: Centering a content group within its container
|
||||
|
||||
**Scene and relationship.** The automation page has a heading and filters above an illustration, explanation, and create action. The empty-state group belongs in the available content region, with one shared horizontal center and a deliberate vertical position.
|
||||
|
||||
**Failure and correction.** Horizontally centering each child while giving the parent fixed top padding leaves the whole group stranded near the top of a tall viewport. Let the content region occupy the remaining height, then position the complete group. Review its internal rhythm too; moving only the button does not center the group.
|
||||
|
||||
**Acceptance.** Mark the remaining content rectangle and the bounds of the complete stack. Compare tall, short, and narrow windows with the same content. Distinguish a deliberate, documented optical offset from accidental fixed spacing; do not demand window centering when a sidebar or page toolbar changes the usable area.
|
||||
|
||||
<a id="case-composer-controls"></a>
|
||||
### Example A5: Visible size and center of peer controls
|
||||
|
||||
**Scene and relationship.** Attachment and send actions anchor the same bottom toolbar. DSH's intended treatment uses equal circular bases and a shared vertical center; their different importance is expressed through fill and state.
|
||||
|
||||
**Failure and correction.** Different base diameters plus an extra vertical translation create two unrelated anchors. Normalize the peer controls' outer geometry and remove layout compensation, then balance the plus and arrow within their slots. A thin plus and a solid arrow need not have equal painted area.
|
||||
|
||||
**Acceptance.** Compare both bases and centers in empty, enabled, busy/stop, and narrow layouts. Review the toolbar alongside the composer outline, not as two isolated button screenshots. A reference product's unequal controls do not override an explicit DSH requirement for equal bases.
|
||||
|
||||
<a id="case-trigger-menu-text"></a>
|
||||
### Example A6: Text starts across triggers and menus
|
||||
|
||||
**Scene and relationship.** A permission trigger uses a larger, heavier icon than its menu rows, but their corresponding labels should start on one axis. A search prompt, result heading and option label can share a similar reading edge.
|
||||
|
||||
**Correction and acceptance.** Sum each surface’s border, padding, leading slot and gap before adjusting it. Matching only icon sizes or outer card edges leaves different text starts. Check the assembled open menu, longer labels and narrow layouts; distinguish device-pixel rounding of portal coordinates from a whole inset mismatch.
|
||||
|
||||
<a id="case-close-control-edge"></a>
|
||||
### Example A7: Choosing the close-control edge
|
||||
|
||||
**Scene and relationship.** A modal has a close glyph inside a larger button and a footer action at the content edge. An icon-box edge and a button edge imply different header insets.
|
||||
|
||||
**Correction and acceptance.** Name the intended edge before changing the shared header. If the full button aligns with the footer action, retain the icon’s internal centering instead of pulling it to that edge. An explicitly icon-aligned composition remains a distinct variant. Check short and wrapping titles, description/body combinations and footer actions, and preserve the target size.
|
||||
|
||||
<a id="grouping"></a>
|
||||
## 2. Spatial grouping
|
||||
|
||||
The [spacing reference](../.agents/skills/dsh-client-ui-ux/references/spacing.md) owns semantic groups, internal/external gap relationships and visible whitespace. The cases below explain how to apply it.
|
||||
|
||||

|
||||
|
||||
G is the separation from the previous section to the new heading; g connects the heading to its content. Repeated member cards retain the same internal arrangement and gap. Its gap markers describe relationships in one construction, not new global spacing tokens. The crucial relation survives when the page becomes denser: keep the hierarchy between levels rather than subtracting the same amount from every gap.
|
||||
|
||||
<a id="case-title-actions"></a>
|
||||
### Example G1: Separating content from its action group
|
||||
|
||||
**Scene and relationship.** The title is one group and the three actions are another. Inside the action cluster, the icons are peers. A title-to-action gap smaller than the icon-to-icon gaps visually attaches the title to the first icon while separating that icon from its peers.
|
||||
|
||||
**Correction and acceptance.** Reserve the cluster as a unit, use compact regular internal gaps, and leave a distinctly larger title-to-cluster gap. Inspect the visible title ending, not only its flex box. Repeat with a long title, hover, focus, and an open menu; truncation or marquee behavior must not consume the separation.
|
||||
|
||||
<a id="case-team-sections"></a>
|
||||
### Example G2: Spacing between complex sections
|
||||
|
||||
**Scene and relationship.** Member cards form a section; the following “Shared tasks” heading belongs to the task cards below it. The transition must be more evident than the separation within either section, especially when the last member row is only partly filled.
|
||||
|
||||
**Correction and acceptance.** Treat each heading and its content as one section and give the two sections explicit separation. Compare the last member card → task heading gap with the heading → first task gap, using the actual text and card edges. A 2:1 CSS margin ratio is not a substitute for looking. Check one and several member rows, wrapping task text, and the empty-task variant.
|
||||
|
||||
<a id="case-plugin-groups"></a>
|
||||
### Example G3: Nested groups along the reading order
|
||||
|
||||
**Scene and relationship.** The page heading and introduction form one group; search serves the catalog; a subsection heading and its description introduce that subsection's cards. Reusing the same vertical gap at every transition flattens three levels into one sequence.
|
||||
|
||||
**Correction and acceptance.** Keep each heading close to its own explanation, separate page introduction from catalog tools, and keep each subsection closer to its cards than to its neighbor. Repeated subsections reuse this rhythm. Inspect the full stack with a wrapped introduction and expanded cards; do not solve the problem by placing a border around every paragraph.
|
||||
|
||||
#### Primer: equal control widths do not fix a broken reading order
|
||||
|
||||
The [Forms structure comparison](https://primer.style/product/ui-patterns/forms/#structure) contrasts one vertical sequence with a shorter two-column arrangement. In the latter, a long option-group label wraps while unrelated fields start a second reading path. Inspect the sequence and ownership of groups, not just matching input edges.
|
||||
|
||||
| Primer's positive example | Primer's counterexample |
|
||||
|---|---|
|
||||
| [](https://primer.style/product/ui-patterns/forms/#structure) | [](https://primer.style/product/ui-patterns/forms/#structure) |
|
||||
|
||||
Images: GitHub Primer, linked from their original sources. Compare the two reading paths and the wrapping option-group label. Apply that grouping judgment to forms throughout the page or modal, including long labels and errors. Related values can share a row when the relation is clear and the pair can stack. These are external examples, not DSH implementation results.
|
||||
|
||||
#### Carbon: different distances carry different meanings
|
||||
|
||||
The [Form specifications](https://www.carbondesignsystem.com/building-blocks/core/components/form/specifications) include an [annotated complete form](https://s3.us-south.cloud-object-storage.appdomain.cloud/cxp-prod-payload-cms/form-style-1.png). Read its title/description, field rows, option group and submit area in order. The example separates the introductory area, repeated fields and final action with different spacing levels while maintaining a common content edge; the annotations make the hierarchy inspectable.
|
||||
|
||||
Use that comparison to review DSH's plugin introduction, search, section description and cards, or a model editor's final action. Compare which elements belong together after adding a line of text. Carbon's numerical scale is evidence for its own example, not a new DSH spacing catalog. A DSH result must also satisfy its existing density, font and responsive rules.
|
||||
|
||||
[](https://www.carbondesignsystem.com/building-blocks/core/components/form/specifications)
|
||||
|
||||
Image: IBM Carbon Design System. Follow the annotations between the introduction, repeated fields and final action. The image demonstrates its own spacing system; it does not prescribe DSH pixel values.
|
||||
|
||||
<a id="case-sidebar-groups"></a>
|
||||
### Example G4: Separating groups with different purposes
|
||||
|
||||
**Scene and relationship.** “New conversation” is the main action; plugins/automations/more are a global navigation group; the workspace heading introduces the project list. Equal row heights do not express these different relationships.
|
||||
|
||||
**Correction and acceptance.** Keep the navigation rows together, separate that group from the workspace section, and bring the workspace heading closer to the projects it labels. The main action has its own separation. Review the full sidebar with the account area visible; compare the role-specific relationships from A1. A larger gap before every project would falsely turn each item into a separate section.
|
||||
|
||||
### Composed whitespace and responsive grouping
|
||||
|
||||
Identify the layer responsible for group separation, then account for the assembled result: parent gap, child margins, native paragraph margins, heading padding, line-box whitespace, and conditional nodes can accumulate. Compare visible internal and external distances at the same hierarchy level; do not mechanically compare every large internal space against an unrelated outer gap. Initial-content spacing may differ when a heading has a description, but both structures need clear ownership.
|
||||
|
||||
Shared containers should provide stable heading, description, body, and action arrangements that consumers reuse. Do not spread negative margins through consumers to cancel container defaults. Express a concrete role or variant when a different distance is needed, then recheck other shared-container combinations; a tighter local arrangement does not imply compressing every dialog.
|
||||
|
||||
The comparison below shows an actual voice setup dialog. On the left, body and native paragraph margins accumulate and detach guidance from the heading; on the right, the shared description region keeps heading and guidance together while separating actions. This illustrates judging composed relationships, not adopting this dialog's distances everywhere.
|
||||
|
||||

|
||||
|
||||
Reflow complete semantic groups first in narrow containers. If an input squeezes its guidance into a very narrow text column, keep label and guidance together and move that field's input below them; do not shrink every font or move guidance into a neighboring group. Verify actual component width and both sides of the breakpoint, not only browser width. Container queries must work in the assembled application; selectors, CSS module names, or ancestor structure can prevent a plausible declaration from applying.
|
||||
|
||||
<a id="density"></a>
|
||||
## 3. Whole-page density
|
||||
|
||||
Allocate available space to the task's content and controls instead of reducing every gap or font size.
|
||||
|
||||
Density concerns how much useful work fits in the available space and how clearly it is organized. Compact repeatable rows can coexist with generous section gaps. Start with the reading and operating task, not an empty-space percentage. A long list benefits from stable row rhythm; a decision form needs its explanations and validation; an empty page needs a coherent central group rather than the same padding as a populated list.
|
||||
|
||||
When width decreases, preserve semantic groups before shortening labels or moving actions. Wrap complete groups and keep their internal spacing patterns. When height increases, decide which region should expand; fixed gaps alone do not place an empty state. Inspect both excessive compression and excessive separation. A dense page is acceptable when comparison remains easy; a sparse page can still have poor grouping.
|
||||
|
||||
<a id="hierarchy"></a>
|
||||
## 4. Information hierarchy
|
||||
|
||||
Make the next reading or decision step visible without turning every text element into a heading.
|
||||
|
||||
Use emphasis to express a task-relevant difference. A current selection, an actionable error, and the data needed for a decision deserve stable recognition. Labels and supporting metadata can be quieter, but making useful text difficult to read is not hierarchy. Inspect size, weight, color, position, and surrounding whitespace together; piling all five onto every heading removes meaningful contrast.
|
||||
|
||||
Use the [icon owner](../.agents/skills/dsh-client-ui-ux/references/icons.md) for icon/label foreground roles and their semantic exceptions; example C1 applies that rule to search fields.
|
||||
|
||||
Typography participates in geometry: compare text starts, baselines and line boxes with the intended font, long translations and relevant font preferences. Pair size with line height and use the weight limits from [visual foundation](../.agents/skills/dsh-client-ui-ux/SKILL.md#visual-foundation). Content, code, terminal and fixed interface roles need not scale identically; check the [font-setting owner](../packages/client/ui-theme/README.md) before treating a deliberate difference as a defect. Correct the container allocation before shrinking useful text to force alignment.
|
||||
|
||||
<a id="case-search-color"></a>
|
||||
### Example C1: Color emphasis within one information unit
|
||||
|
||||
**Scene and relationship.** Compare the empty automation search with the expanded sidebar search. Both pair a magnifier with a prompt, but one makes the icon darker and the other makes the text darker.
|
||||
|
||||
**Correction and acceptance.** Give the empty-field prompt one foreground role, including the leading icon. Compare computed color, opacity, disabled styling, and the background together; two identical color tokens can still differ after an ancestor fades one child. Test empty, typed, focused, disabled, light, and dark variants. Typed query text and an independently actionable clear button need not use the placeholder's weaker role. A submenu value and its trailing chevron also form one value: a muted model name with a darker chevron fails that relationship even when their geometry is correct.
|
||||
|
||||
<a id="states"></a>
|
||||
## 5. Control states
|
||||
|
||||
Persistent state and transient feedback must remain distinguishable when they occur together.
|
||||
|
||||
State design assigns stable meanings, not just different colors. Selection says “this is the current item”; hover says “the pointer is here”; focus says “keyboard input goes here”; disabled says “this action is unavailable.” Compare simultaneous states and adjacent items: the selected row beside a hovered unselected row matters as much as selected-plus-hover on one row.
|
||||
|
||||
| Combination | What must remain legible | Failure to look for |
|
||||
|---|---|---|
|
||||
| Selected + another row hovered | Current location remains recognizable | The temporary row looks more selected than the current row |
|
||||
| Selected + hover on the same item | Selection persists while interaction feedback appears | A generic hover fill erases the current state |
|
||||
| Selected + keyboard focus | Location and keyboard target remain distinguishable | Focus disappears into the selection treatment |
|
||||
| Selected + disabled | Retained choice and temporary unavailability both remain visible | Fading the item makes it look deselected |
|
||||
| Expanded + menu open | The active operation has a clear owner | A panel highlight and row highlight compete without a stable relationship |
|
||||
|
||||
<a id="case-sidebar-state"></a>
|
||||
### Example S1: Persistent selection beside temporary hover
|
||||
|
||||
**Scene and relationship.** Compare the selected conversation, a neighboring hovered row and an open row menu. Background paint is one cue alongside the current-row semantics and revealed actions; selection and hover do not require different colors.
|
||||
|
||||
**Correction and acceptance.** Verify that selection persists when hover ends or a menu closes. Move between neighboring rows and traverse by keyboard; inspect focus, action visibility and current-row semantics together. Compare the same theme and platform material; an opaque fixture cannot establish translucent Desktop contrast.
|
||||
|
||||
<a id="component-choice"></a>
|
||||
## 6. Component choice
|
||||
|
||||
Choose the component from the interaction it represents, then fit its layout to the available space.
|
||||
|
||||
Choose the element by what users can do, then make peers visually coherent. Navigation opens a destination, a toggle changes a setting, a checkbox adds an independent choice, and an action button starts an operation. Similar geometry does not make these interchangeable. A card adds a containing group and more space; it is justified when that group needs an independent summary, state, or action, not merely because a row looks plain.
|
||||
|
||||
Keep the control's label, visible bounds, focus behavior, and click target consistent with the chosen interaction. If a visual correction changes those semantics, it needs behavior review as well. Geometry and radius are judged separately in [section 8](#geometry); “native control” is neither a guarantee of visual fit nor a reason to discard keyboard and accessibility behavior.
|
||||
|
||||
<a id="icon-optics"></a>
|
||||
## 7. Icon optics
|
||||
|
||||
The [icon reference](../.agents/skills/dsh-client-ui-ux/references/icons.md) owns interface icon roles, slots, color and optical balance. [Official artwork](../.agents/skills/dsh-official-plugin-artwork/SKILL.md) retains its separate role.
|
||||
|
||||
<a id="case-file-optics"></a>
|
||||
### Example O1: Optical alignment of different icon types
|
||||
|
||||
**Scene and relationship.** At one tree depth, compare closed/open folders, native file-type icons, generic files, and names without icons. Then compare the same pattern one level deeper.
|
||||
|
||||
**Correction and acceptance.** Preserve the depth indent and common text start. Normalize the placement slots, then compare visible centers and perceived weight within them. Do not move the label to compensate for one glyph, or align all depths to one edge. Inspect at normal size and magnification, with selected backgrounds and long names; the magnified view diagnoses the drawing, while normal size determines whether the result balances.
|
||||
|
||||
<a id="case-chevron-consumers"></a>
|
||||
### Example O2: Shared artwork at different consumer sizes
|
||||
|
||||
**Scene and relationship.** A chevron appears beside a menu label and inside a larger floating button. Enlarging its drawing within the same view box changes both appearances.
|
||||
|
||||
**Correction and acceptance.** Compare the directional family, both weights and actual consumer sizes. Keep a modest supporting glyph beside text; reduce the floating control’s icon size if needed without shrinking its button. A large glyph sheet alone does not establish balance in either composition.
|
||||
|
||||
<a id="geometry"></a>
|
||||
## 8. Control geometry and corner relationships
|
||||
|
||||
Consistency means that equal roles and size variants follow one rule, and different roles have an understandable relationship. Review outer height, width, radius, curve, inset, and visible base together. Legal tokens can produce an incoherent combination; one radius on every element can be equally incoherent. The [radius standard](ui-radius.md) owns values and nested geometry, so this section does not create another scale.
|
||||
|
||||
Distinguish a tightly inset surface that follows its container from an independent card inside a panel. The first needs coordinated outer/inner curves and inset; the second follows its own component role. Check the background, border, highlight, pseudo-elements, and clipping together. A hover fill that changes the visible silhouette is a state defect even if the resting border is correct.
|
||||
|
||||
### Decide geometry in this order
|
||||
|
||||
Classify the semantic role, select its existing size variant, identify the parent/child relationship, then inspect curve, material and state. The [radius scale](ui-radius.md#radius-scale) and its named exceptions supply values; a larger bounding box after expansion does not select a new tier. Equal radius numbers can still look different when height or curve differs.
|
||||
|
||||
| Relationship | Decision from the radius owner | Visual failure to look for |
|
||||
|---|---|---|
|
||||
| Circular concentric inset | Inner radius is outer radius minus inset, clamped at zero; arcs share a center | Same-radius inner/outer corners pinch or widen the visible band |
|
||||
| Nested superellipses | Use the shared nominal size relation, then inspect diagonal spacing; they are not exact offset curves | Claiming mathematically equal spacing from subtraction alone |
|
||||
| Flush fill or adjoining segments | The enclosing silhouette owns exposed corners; internal joins stay square | Notches, crescents, double contours or focus clipped with the fill |
|
||||
| Independent child card/group | Choose its role tier instead of subtracting all surrounding space | Collapsing a legitimate child radius to zero from large parent padding |
|
||||
| Explicit circle or pill | Preserve round arcs for the container and its painted contents | A rounded avatar container holding a noncircular image; squircle-like capsule ends |
|
||||
|
||||
Compare fill, stroke, hover overlay, focus and clipping together throughout the page or modal. Flat card borders and elevated materials have different paint ownership under [Web styling](web-styling.md#component-rules); applying both can double the edge. When a contour clips its fill, keep popups and keyboard focus fully visible. Inspect the geometry and continued use together.
|
||||
|
||||
#### Fluent: the split-button notch is a relationship error
|
||||
|
||||
The [Shapes example](https://fluent2.microsoft.design/shapes/#when-to-avoid-rounded-corners) marks the gap where two separately rounded button halves meet. Inspect the internal seam rather than the outer corners: treating each half as a complete independent silhouette breaks the shared enclosure. This is a concrete negative example, not a recommendation to make every control rounder.
|
||||
|
||||
[](https://fluent2.microsoft.design/shapes/)
|
||||
|
||||
Image: Microsoft Fluent 2, linked from its original source. In DSH, apply the flush-fill arrangement from the radius standard, then check independent hover regions, focus and the fully visible popup together. Fluent's radius and stroke scale are not DSH defaults. Look at the notch at the internal seam; this source image is a negative example, not a DSH before/after comparison.
|
||||
|
||||
<a id="case-model-radius"></a>
|
||||
### Example R1: Radii across container and control roles
|
||||
|
||||
**Scene and relationship.** A model editor contains grouped model rows, two inputs per row, an “Add model” button, and cancel/save actions. Every corner appears on the same screen, so role and size relationships must be readable together.
|
||||
|
||||
**Correction and acceptance.** Classify the outer editor, model group, field, and button before selecting their existing variants. Same-role controls at the same size match. Coordinate tightly nested curves with actual inset, while independent children retain their own role. Check collapsed/expanded rows, field focus, hover fills, and long model names. Do not justify a random radius by pointing to the total height after expansion.
|
||||
|
||||
<a id="case-checkbox-radius"></a>
|
||||
### Example R2: Small selection controls within a form
|
||||
|
||||
**Scene and relationship.** Input-type choices use dark native checkboxes beside rounded form controls. The mismatch concerns the small control's silhouette and visual weight, not a requirement to give a checkbox the radius of a large input.
|
||||
|
||||
**Correction and acceptance.** Use a coherent small-selection-control treatment while preserving the recognizable checkbox silhouette and label association. If native appearance is replaced, retain checked/unchecked, disabled, keyboard focus, and assistive semantics. Compare these states at actual size in both themes; an unchecked outline and its checked fill must belong to the same control family.
|
||||
|
||||
<a id="case-automation-radius"></a>
|
||||
### Example R3: Justified geometry differences between roles
|
||||
|
||||
**Scene and relationship.** Nearby automation controls combine rounded filter chips, a search field, and a primary create action. Similar control heights can still produce a conspicuous jump in roundness when each feature chooses its own radius.
|
||||
|
||||
**Correction and acceptance.** Reuse the established control variants. A true pill is an explicit component type; it does not authorize every compact button to use half its height as radius. Compare filters, search, and primary/secondary buttons in one frame. Check hover and focus contours too. Keep a meaningful pill/field distinction where intended rather than forcing equal radii solely because the controls share a row.
|
||||
|
||||
<a id="reproduce"></a>
|
||||
## Reproduce and compare
|
||||
|
||||
Locally maintained production-component fixtures can supply isolated examples for repeatable content and state. Derive actual application captures from the whole page or modal under review, including its relevant regions and states. A fixture establishes only what it renders; it does not determine or complete the review scope.
|
||||
|
||||
The dimensions found in these components are evidence about those examples, not universal design constants. The [style reference](web-styling.md), [radius standard](ui-radius.md), and component owners retain authority over shared values. Extend the evidence for new consumers or a different task rather than copying an example's number without its conditions.
|
||||
|
||||
<a id="references"></a>
|
||||
## External references and what DSH adopts
|
||||
|
||||
These are research inputs, not automatically invoked skills or replacements for DSH rules. The selection below was read on 2026-10-26. Public guidance supplies comparison methods; the worked DSH cases and explanatory diagrams are local synthesis. Links point to the original authors. No external skill installation or execution is required to use this reference.
|
||||
|
||||
| Reference | Useful contribution | Scope and deliberate limit |
|
||||
|---|---|---|
|
||||
| [Anthropic frontend-design](https://github.com/anthropics/skills/blob/main/skills/frontend-design/SKILL.md) | Explicit composition, content-specific choices, and rendered self-critique | Its expressive visual direction is not a mandate to restyle a workbench or change DSH typography |
|
||||
| [Vercel web-design-guidelines skill](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md) and [rules](https://github.com/vercel-labs/web-interface-guidelines/blob/main/command.md) | Locate findings in code and cover focus, labels, long content, and interaction | Code compliance alone does not establish alignment or grouping; its title-case advice does not override DSH sentence case |
|
||||
| [Impeccable](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/SKILL.md), [critique](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/reference/critique.md), [polish](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/reference/polish.md), [operate](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/reference/operate.md) | Separate a task-oriented interface from promotional design; distinguish visual judgment from detector output | Adopt the complete-path inspection and existing-system comparison, not its execution, delegation, or scoring machinery |
|
||||
| [Interface Design](https://github.com/Dammyjay93/interface-design/blob/main/.claude/skills/interface-design/SKILL.md) | Explicit product intent, repeated patterns, and comparison with established neighboring interfaces | Preserve DSH's existing system; do not import its example font weights, create another token catalog, or chase novelty for its own sake |
|
||||
| [OpenAI figma-implement-design](https://github.com/openai/skills/blob/main/skills/.curated/figma-implement-design/SKILL.md) | Compare rendered output against an actual visual reference and integrate existing components | A screenshot is evidence with a defined scope, not permission to infer unseen states or adopt every number |
|
||||
| [Fluent Shapes](https://fluent2.microsoft.design/shapes/) and [Primer Forms](https://primer.style/product/ui-patterns/forms/) | Explicit component counterexamples with inspectable relationships | The case analyses above transfer a decision method, not their token or interaction system |
|
||||
| [Atlassian spacing](https://atlassian.design/foundations/spacing/) | Positive/negative examples of proximity, repeated rhythm, hierarchy, and optical adjustment | Borrow the relationship tests, not Atlassian's token names or an automatic pixel formula |
|
||||
| [Carbon spacing](https://www.carbondesignsystem.com/building-blocks/foundations/spacing/overview) | Spacing patterns express equal weight and section boundaries; fluid centering differs from fixed spacing | Use DSH's spacing ownership; a scale alone cannot select the right group relationship |
|
||||
| [Codex official product introduction](https://openai.com/index/introducing-the-codex-app/) and [desktop documentation](https://learn.chatgpt.com/docs/app) | Task-oriented composition and real public examples to compare | Public examples are versioned observations, not the current local app or an unpublished Codex design specification |
|
||||
|
||||
### Codex: a limited reference for composition
|
||||
|
||||
The official [welcome/composer poster](https://learn.chatgpt.com/images/codex/video-posters/proactive-teammate-v2.webp) and [long-input poster](https://learn.chatgpt.com/images/codex/video-posters/draft-prds-from-slack-linear-docs.webp) show centered introductory content, left-aligned input text, and separate bottom control groups. They support that composition comparison. They do not reveal full-sidebar alignment, nested-corner behavior, neighboring selected/hovered states, or resizing. Use the more specific examples above for those decisions rather than treating a product poster as broad design approval.
|
||||
|
||||
External images remain at their credited sources. If a source image is unavailable, the adjacent analysis still identifies the comparison; obtain a usable reference before using that image as visual evidence in a new review.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## Dev Note
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,445 @@
|
||||
---
|
||||
description: "DSH 设计判断参考:对齐、空间分组、信息层级、状态与几何,以及完整的审查案例。"
|
||||
---
|
||||
|
||||
# UI 设计原则与审查案例
|
||||
|
||||
[English](ui-design-review-cases.md) | 中文
|
||||
|
||||
## 概览
|
||||
|
||||
设计关系不明确时查阅相关案例。普通修改以页面/弹窗为上下文检查受影响关系,不要求遍历全部案例、建立清单或完整审查整页。下文整界面审查章节用于明确要求的完整审查。案例解释可迁移的原则,不定义覆盖范围。详细方法按需查阅[审查手册](cookbook/reviewing-ui-design.zh.md),执行深度以所属流程为准。
|
||||
|
||||
## 目录
|
||||
|
||||
- [审查究竟要判断什么](#judgment)
|
||||
- [以整页或整个弹窗作为审查单位](#whole-interface-review)
|
||||
- [先综合适用规范](#review-baseline)
|
||||
- [如何用例子理解原则](#case-selection)
|
||||
- [原则示例索引](#case-index)
|
||||
- [1. 对齐](#alignment)
|
||||
- [2. 空间分组](#grouping)
|
||||
- [3. 整屏密度](#density)
|
||||
- [4. 信息层级](#hierarchy)
|
||||
- [5. 控件状态](#states)
|
||||
- [6. 组件选型](#component-choice)
|
||||
- [7. 图标光学](#icon-optics)
|
||||
- [8. 控件几何与圆角关系](#geometry)
|
||||
- [复现与比较](#reproduce)
|
||||
- [外部参考与 DSH 的取舍](#references)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
<a id="judgment"></a>
|
||||
## 审查究竟要判断什么
|
||||
|
||||
好的界面会在读者逐字阅读前先表达元素之间的关系。对齐让人比较并列内容时不用反复寻找起点;间距告诉人哪些图标、标签、说明、控件和操作属于同一组。这些是基础设计要求,即使功能完全可用也要满足,不能等 token 检查通过后才当作可选打磨。
|
||||
|
||||
DSH 的视觉审查优先关注对齐与空间分组。先识别任务和语义组,再检查整个区域的跨组对齐轴,然后检查每组内部关系;随后检查信息层级、状态、几何、颜色、密度和光学细节。操作阻塞或误导性状态仍先于纯外观问题。不能为了统一而让所有元素一样大、一样疏密、一样突出。
|
||||
|
||||
页面或弹窗提供构图参照;一次改动需要检查哪些关系,由[影响范围](cookbook/reviewing-ui-design.zh.md#review-scope)决定。导航、内容、表单和操作即使由不同组件实现,也可能共用文字轴和间距规则。例外应由角色、层级或状态解释,组件归属本身不是设计理由。
|
||||
|
||||
本文原理图解释元素关系,不是实现截图或已验收的修改前后对照。下文例子用于讲清具体原则,不代表已覆盖当前要审查的页面或弹窗。
|
||||
|
||||
<a id="whole-interface-review"></a>
|
||||
## 以整页或整个弹窗作为审查单位
|
||||
|
||||
选择参照和判断关系时,将整个页面或弹窗视为一个视觉整体。日常改动在这个上下文中审查受影响的分组和跨组关系,不自动检查每个区域或整个产品。明确的完整界面审查必须检查其全部区域。侧栏、表单、工具栏即使由独立组件实现,也属于更大的整体构图。
|
||||
|
||||
对弹窗,标题、关闭按钮、全部内容分区、滚动区域和底部操作一起纳入判断;对页面,导航、页头、工具栏、内容区和固定操作一起纳入判断。滚动后才能看到的内容仍属于同一个整体。选择对齐和居中参照时,依据页面或弹窗自己的边缘;打开的弹窗在底层页面之上形成自己的构图。
|
||||
|
||||
### 从整体、组间关系看到组内元素
|
||||
|
||||
先看整体构图:内容边缘、视觉中心、阅读顺序、主操作和总体疏密。再按内容描述什么、操作什么,判断哪些元素组成一组、组内有哪些子组。先比较相邻组,再看组内元素。同一种关系应在整页或整个弹窗内保持一致,包括相距较远的分区。
|
||||
|
||||
| 审查方面 | 在整页或整个弹窗内统一比较 | 判断依据 |
|
||||
|---|---|---|
|
||||
| 对齐 | 文字起点、图标中心、控件边缘、重复的操作列 | 按角色选择居左、居中或两侧对齐;共用轴线跨组延续;图标与文字先作为一个单元判断 |
|
||||
| 空间分组 | 标签到内容、元素之间、组之间、分区之间的距离 | 组内间距小于组间间距;组越复杂,组间距应越大;相似构成的组复用相同组内间距 |
|
||||
| 尺寸与比例 | 同角色控件、图标底板、图标及相邻文字 | 使用一致的尺寸变体和视觉重量;必要时通过调整大小实现共同对齐 |
|
||||
| 圆角与轮廓 | 外层容器、卡片、字段、按钮和选择控件 | 全部遵循同一套按角色划分的圆角规则;依据已有规范区分内缩、贴边和独立容器关系 |
|
||||
| 颜色与强调 | 文字、关联图标、底色、描边和辅助信息 | 共同表达同一信息的图标与文字保持一致的强调;差异应能由角色或状态解释 |
|
||||
| 字体与疏密 | 标题、标签、说明、重复行和整体留白 | 同层级内容保持一致,层级清楚;局部间距与整体疏密协调 |
|
||||
| 状态与行为 | 选中、悬停、焦点、禁用、展开、加载和失败内容 | 状态变化后仍能认出分组、对齐关系和操作入口;同时出现的状态放在一起比较 |
|
||||
|
||||
统一看待,意味着在这个范围内使用同一套判断依据。同角色、同尺寸变体使用一致处理,差异来自角色、层级或状态。“由不同组件实现”不能解释文字起点、间距模式或圆角为什么不一致。同样,居中的标题与居左的阅读列可以共存,前提是各自角色支持这种构图。
|
||||
|
||||
### 把每个发现放回整体判断
|
||||
|
||||
记录哪些元素发生冲突、它们应共享什么关系,以及改法如何与页面或弹窗的其他部分协调。在本次范围内比较这种关系的所有出现位置。局部调整后,回到整体构图,一起复查受到影响的对齐、分组、几何和层级。
|
||||
|
||||
完整审查覆盖范围内所有区域,包括相关滚动位置、展开内容、标签页,以及会改变这些关系的状态。按适用情况检查支持的宽度、主题和输入方式。保留草稿、焦点、关闭方式和主操作可达性,失败时提供相应恢复。没有检查的区域和状态明确列出。一张合格裁图、一个组件 fixture 或几个选定例子,都不能证明整页或整个弹窗已经协调一致。
|
||||
|
||||
### 用关系清单判断覆盖是否完整
|
||||
|
||||
把完整界面中的语义组列出来,再列出需要比较的关系:父子缩进、跨分区文字列、重复操作槽、标题与说明、字段与校验、正文与行内控件、滚动内容与固定操作。不只检查每组内部;每条跨组关系都必须连接到实际比较过的两端。修改共用原语时,补查它在有/无说明、长短内容和不同操作组合中的使用方。
|
||||
|
||||
关系通过至少满足三项:结构使用同一个参照;实际画面表达该关系;改变相关内容或状态后仍成立。测试断言存在、同一个 token、截图成功或单张裁图都不能单独替代这三项。优先级按误导程度、使用频率及传播范围决定,不能用“只差几像素”排除高频错列。
|
||||
|
||||
“已检查无新增问题”“已修复并复查”“发现但未修复”“未检查”分别记录。若无法穷尽平台或异常状态,交付明确的覆盖边界;不能将例子数量、组件测试数量或页面入口数量当成全部设计通过。
|
||||
|
||||
<a id="review-baseline"></a>
|
||||
## 先综合适用规范
|
||||
|
||||
用[规则归属表](../.agents/skills/dsh-client-ui-ux/SKILL.md#select-the-rule-owners)选择当前实现要求,包括对齐、间距和图标专项。
|
||||
|
||||
审查有两个独立要求:符合适用的 DSH 规则,同时解释组装后的界面是否清楚表达预期关系。轮廓好看不能抵消交互语义错误,token 合法也不能抵消对齐错误。局部审查应明确不涉及哪些方面;整体审查则在受审整页或整个弹窗的所有区域中检查下表相关项。
|
||||
|
||||
| 规则所有方 | 对审查的贡献 | 在整页或整个弹窗中比较什么 |
|
||||
|---|---|---|
|
||||
| [圆角规范](ui-radius.zh.md#radius-scale)、[嵌套几何](ui-radius.zh.md#nested-hover) | 先角色后尺寸;内缩、贴边填色、独立子容器三种关系;曲线与明确例外 | 外层容器、字段、hover 填色、指示块及裁切边缘一起比较 |
|
||||
| [Web 样式](web-styling.zh.md#component-rules) | 语义色、字号与行高配对、发丝线、层级阴影、菜单材质、动效 | 跨功能的同角色表面、明暗主题与 Desktop 原生背景 |
|
||||
| [客户端 UI/UX skill](../.agents/skills/dsh-client-ui-ux/SKILL.md) | 组件复用、层级、加载、反馈归属、窗口避让 | 加载后/空/错误状态及实际周边页面 |
|
||||
| [响应式 skill](../.agents/skills/dsh-responsive-ui/SKILL.md) | 先分配空间再截断、输入能力、焦点、主操作可达性 | 同一任务的宽屏、受限宽度、键盘及触屏使用 |
|
||||
| [官方图案 skill](../.agents/skills/dsh-official-plugin-artwork/SKILL.md) | 插件图案语义、光学平衡、透明区域及邻近参照 | 插件图案在列表与详情中的呈现;操作图标属于另一类 |
|
||||
| [错误处理 skill](../.agents/skills/dsh-error-handling/SKILL.md#localized-actionable-visible-reports) | 失败可见、保留有效状态、提供安全下一步 | 区分字段纠错、操作失败与查询失败 |
|
||||
| [基础组件目录](../packages/client/ui-primitives/README.zh.md#component-catalog)与相关包 README | 已有变体、焦点与交互行为、功能特有要求 | 实际组件角色与外观相似的替代品是否相符 |
|
||||
|
||||
当前仓库所有方及其明确记录的例外,优先于旧版个人 skill 或外部案例。用户的具体选择适用于指定组件族,不会默默替换全局体系。实现与所属规范不一致时,报告差异,不能把既有代码自动当成新规则。选圆角和材质时重新核对所有方,不在 review 文档里复制另一套尺度。
|
||||
|
||||
<a id="case-selection"></a>
|
||||
## 如何用例子理解原则
|
||||
|
||||
例子通过具体任务、相关元素、周边上下文,以及可能推翻改法的变化条件,让原则变得可观察。应提炼其中的关系,并用于当前整页或整个弹窗的判断。侧栏或模型编辑器的例子,不会把原则限定在这些组件上。例子是帮助理解的证据,不是审查菜单或覆盖清单。
|
||||
|
||||
区分三种证据:DSH 实际场景可以确认所展示条件下的缺陷;官方设计系统案例解释特定关系,其尺寸与交互策略仍属于原系统;原创示意图解释拟采用的关系,不能标成已实现的改善。fixture 仅覆盖它渲染的条件。来源、比较对象、适用的 DSH 规则和复查条件应当紧邻案例。
|
||||
|
||||
每个案例按“场景与问题 → 设计判断 → 紧接下方的截图或成对对照”呈现。图注说明看哪里、两侧分别表示什么及来源。不要把无关案例集中成脱离正文的截图墙。差异只有勉强可辨、或图片没有包含所讨论关系时,应选择更有解释力的证据,不能将其当作设计范例。
|
||||
|
||||
<a id="case-index"></a>
|
||||
## 原则示例索引
|
||||
|
||||
对某种关系不确定时,可借助这些例子理解。在当前页面或弹窗内,所有相关区域都适用相同原则,包括这里没有举到的区域。审查范围来自用户要求的界面,不由这份示例清单决定。
|
||||
|
||||
| 案例 | 要检查的关系 |
|
||||
|---|---|
|
||||
| [A1](#case-sidebar-axes) | 按角色判断侧栏对齐 |
|
||||
| [A2](#case-action-columns) | 重复操作组的对应列 |
|
||||
| [A3](#case-preview-toolbar) | 多排控件与复合控件的对齐 |
|
||||
| [A4](#case-empty-center) | 内容组相对所属容器居中 |
|
||||
| [A5](#case-composer-controls) | 并列控件的可见尺寸与中心 |
|
||||
| [A6](#case-trigger-menu-text) | 入口与菜单之间的文本起点 |
|
||||
| [A7](#case-close-control-edge) | 明确关闭控件的对齐边缘 |
|
||||
| [G1](#case-title-actions) | 内容与操作组的分隔 |
|
||||
| [G2](#case-team-sections) | 复杂分区之间的间距 |
|
||||
| [G3](#case-plugin-groups) | 阅读顺序中的多层分组 |
|
||||
| [G4](#case-sidebar-groups) | 不同用途的组之间的分隔 |
|
||||
| [C1](#case-search-color) | 同一信息单元的图文强调 |
|
||||
| [S1](#case-sidebar-state) | 持续选中与临时悬停共存 |
|
||||
| [O1](#case-file-optics) | 不同类型图标的光学对齐 |
|
||||
| [O2](#case-chevron-consumers) | 共享图形在不同使用尺寸下的表现 |
|
||||
| [R1](#case-model-radius) | 不同层级容器与控件的圆角 |
|
||||
| [R2](#case-checkbox-radius) | 表单中小型选择控件的轮廓 |
|
||||
| [R3](#case-automation-radius) | 不同角色之间有依据的几何差异 |
|
||||
|
||||
<a id="alignment"></a>
|
||||
## 1. 对齐
|
||||
|
||||
[对齐参考](../.agents/skills/dsh-client-ui-ux/references/alignment.md)负责共享轴线、完整布局框、层级缩进和文字度量;以下案例用于说明这些判断。
|
||||
|
||||

|
||||
|
||||
示意图区分同类行的轴线与账号、标题区域的几何关系,不是像素规范。分别比较内容、背景绘制区域和点击区域;不同角色不必共用所有轴线。
|
||||
|
||||
### 文字度量与不等长内容
|
||||
|
||||
行内图标的参照是相邻文字,不是整段的行框中心。`cap-height` 可以用于具体字体组合的校准,但不是所有字体与图标的统一公式;不能把某次有效的位移复制到所有控件。多行说明旁的状态图标通常附着首行,而单行按钮的 icon 与 label 作为整体居中。改变字号、字体回退或语言后重新检查这种关系。
|
||||
|
||||
下图使用真实 Markdown 渲染,左前右后,标注只在右图。中文、英文和代码链接保持原图标尺寸,通过字体度量修正垂直位置;外部留白与组内留白分别负责链接归属。检查长链接时还需看完整段落中的换行,裁图不能证明长链接可用。
|
||||
|
||||

|
||||
|
||||
并列卡片应共同确定标题起点、说明首行和操作区边缘。短说明不因相邻说明更长就独自垂直居中;剩余高度放在内容组之后,底部操作可以另行对齐。统一卡片高度不等于统一内部阅读顺序,也不要求裁短真实内容来制造等高。
|
||||
|
||||
下图为实际预设卡片的长短内容组合。左侧短说明因自动外距离开标题,右侧说明共同从标题下方开始,操作区仍对齐。该关系也适用于结果卡片、选项卡片和并列摘要;它不要求所有页面都采用卡片。
|
||||
|
||||

|
||||
|
||||
<a id="case-sidebar-axes"></a>
|
||||
### 示例 A1:按角色判断侧栏对齐
|
||||
|
||||
**场景与关系。**比较展开侧栏中的导航、工作区行、会话和账号区域。检查居中的新会话操作与行背景边缘,而不只比较文字起点。
|
||||
|
||||
**错误与修正。**为所有角色套用同一前导槽宽度,会移动标题、拉大间距或改变已登录账号行。保留所属组件的共享变量、紧凑图标槽和绘制内缩。保留有意设计的账号几何关系与层级,不强造贯穿所有角色的文字轴。
|
||||
|
||||
**验收。**用辅助线比较同角色的行。检查长短标题、可选图标、项目展开和账号登录/未登录状态。行操作浮现不能移动标题起点。列表裁图不能证明完整侧栏对齐。
|
||||
|
||||
<a id="case-action-columns"></a>
|
||||
### 示例 A2:区分标题操作与行操作
|
||||
|
||||
**场景与关系。**工作区的搜索/筛选/添加与会话的更多/归档/置顶都处于侧栏尾部,但角色和间距不同。重复行之间互相比较;标题区域则比较自身搜索收起和展开的状态。
|
||||
|
||||
**错误与修正。**按各组角色选择控件尺寸、间距与尾部内缩,并检查搜索展开时的布局。标题到操作组的分隔独立于组内间距。
|
||||
|
||||
**验收。**同时显示标题与行操作,再展开搜索并折叠侧栏。检查预期边缘关系、未裁切的点击区域和长标题避让。不能为了让无关控件形成垂直列而补空操作或偏移。
|
||||
|
||||
<a id="case-preview-toolbar"></a>
|
||||
### 示例 A3:多排控件与复合控件的对齐
|
||||
|
||||
**场景与关系。**面板控制位于上排,文件显示控制与“在应用中打开”分段按钮位于下排。两排属于同一个预览页头,每排又包含作用范围不同的操作。
|
||||
|
||||
**错误与修正。**各组件内部可能已经居中,组装后仍出现无关的右边距、高度和分组间距。先按上排图标框与下排完整控件建立尾端关系,保留图标内部留白;不能直接对齐额外热区,也不能追齐单个 glyph 的像素边缘。再对齐并列控件中心,把显示控制收成一组,与外部打开操作分开。分段按钮按完整单元处理。缩小其外部宽度可以在保留前方图标列轴的同时,使可见间距相等;应检查两个分段的点击区域,以及边框对布局尺寸的贡献。不强迫其箭头与无关的面板操作共用列轴。
|
||||
|
||||
**验收。**保留路径、标签页与内容边缘,一并观察两排;再检查长路径和窄面板。用一排独立图标代替实际分段控件,不能验证这个关系。
|
||||
|
||||
<a id="case-empty-center"></a>
|
||||
### 示例 A4:内容组相对所属容器居中
|
||||
|
||||
**场景与关系。**自动化页面在标题、筛选栏下面放置插画、说明和创建按钮。空状态组属于剩余内容区,应具有共同的水平中心和明确的纵向位置。
|
||||
|
||||
**错误与修正。**各子项水平居中,但父级只设置固定顶部 padding,会让整组停在高视口上方。让内容区占据剩余高度,再定位完整内容组;同时检查内部节奏,不能只移动按钮来声称整组居中。
|
||||
|
||||
**验收。**标出剩余内容矩形与整组的外接范围,以同样内容比较高、矮、窄窗口。区分有设计依据的光学偏移与意外固定间距;侧栏或页头改变可用区域时,不要求相对于整个窗口居中。
|
||||
|
||||
<a id="case-composer-controls"></a>
|
||||
### 示例 A5:并列控件的可见尺寸与中心
|
||||
|
||||
**场景与关系。**附件和发送操作分别位于底栏两端。DSH 的预期处理是圆形底板等大、垂直中心一致,主次通过填色与状态表达。
|
||||
|
||||
**错误与修正。**底板直径不同,再加额外的垂直平移,会形成两个不一致的端点。先统一并列控件外部几何并去掉布局补偿,再平衡槽内的加号和箭头。细线加号与实心箭头不需要具有相同的墨迹面积。
|
||||
|
||||
**验收。**检查空输入、可发送、忙碌/停止和窄布局中的底板与中心。保留输入框外轮廓一起观察,不能只看两张独立按钮截图。参考产品里不同大小的操作控件,不能覆盖 DSH 已明确的等大底板要求。
|
||||
|
||||
<a id="case-trigger-menu-text"></a>
|
||||
### 示例 A6:入口与菜单之间的文本起点
|
||||
|
||||
**场景与关系。**权限入口使用比菜单行更大、更重的图标,但对应文字应从同一轴线开始。搜索提示、结果分组标题和选项文字也可能共享类似的阅读边缘。
|
||||
|
||||
**修正与验收。**先合计各表面的 border、padding、前导槽和 gap,再调整实现。只统一图标大小或卡片外缘,仍可能留下不同的文字起点。检查组装后的展开菜单、长标签和窄布局;区分浮层坐标按设备像素取整的微差与整段内缩错误。
|
||||
|
||||
<a id="case-close-control-edge"></a>
|
||||
### 示例 A7:明确关闭控件的对齐边缘
|
||||
|
||||
**场景与关系。**弹窗的关闭图形位于较大的按钮内,底部操作靠内容边缘。图标框边缘与按钮边缘对应不同的标题栏内缩。
|
||||
|
||||
**修正与验收。**修改共享标题栏前先明确目标边缘。如果完整按钮与底部操作对齐,就保留图标在按钮内居中,不再把图标拉到该边缘。明确采用图标对齐的构图仍是独立变体。检查短标题、换行标题、说明/正文组合与底部操作,并保留点击区域大小。
|
||||
|
||||
<a id="grouping"></a>
|
||||
## 2. 空间分组
|
||||
|
||||
[间距分组参考](../.agents/skills/dsh-client-ui-ux/references/spacing.md)负责语义组、组内外间距关系和可见留白;以下案例说明如何应用。
|
||||
|
||||

|
||||
|
||||
图中 G 是前一个分区到新标题的间距,g 是新标题到所属内容的间距;重复成员卡片保持相同的内部排列与间隔。间距标记只描述这张构造图中的关系,不增加全局间距 token。页面变密时仍须保留层级关系,不能对每种间距都减去相同数值。
|
||||
|
||||
<a id="case-title-actions"></a>
|
||||
### 示例 G1:内容与操作组的分隔
|
||||
|
||||
**场景与关系。**标题是一组,三个操作图标是另一组,图标之间是并列关系。标题到操作的距离比图标之间还小,会把标题与第一个图标视觉上粘在一起,反而把该图标与其他操作分开。
|
||||
|
||||
**修正与验收。**将操作组作为整体预留空间,组内采用紧凑且一致的间距,标题到操作组之间明显更宽。观察可见标题的末端,而非只看 flex 框。覆盖长标题、悬停、焦点和菜单打开状态;截断或滚动文字不能侵占分组留白。
|
||||
|
||||
<a id="case-team-sections"></a>
|
||||
### 示例 G2:复杂分区之间的间距
|
||||
|
||||
**场景与关系。**成员卡片组成一个分区,随后“共享任务”标题属于它下方的任务卡片。两个分区之间的过渡应比各区内部的间隔更清楚,尤其是在最后一排成员卡片未填满时。
|
||||
|
||||
**修正与验收。**将每个标题及其内容作为一个完整分区,明确设置分区间距。按实际字面和卡片边界,比较“最后一张成员卡片 → 任务标题”与“任务标题 → 第一张任务卡片”的距离。CSS margin 的 2:1 比例不能替代读图。检查一排和多排成员、任务文案换行、任务为空的变体。
|
||||
|
||||
<a id="case-plugin-groups"></a>
|
||||
### 示例 G3:阅读顺序中的多层分组
|
||||
|
||||
**场景与关系。**页面标题与介绍是一组,搜索服务于目录,子分区标题与说明介绍该分区的卡片。每次过渡都沿用相同纵向间距,会把三个层级摊平成一串元素。
|
||||
|
||||
**修正与验收。**各标题靠近自己的说明,页面介绍与目录工具分开,每个子分区与自己的卡片更近、与相邻分区更远。重复分区复用同一节奏。检查完整纵向序列,包含介绍换行和卡片展开;不能用给每段文字套边框来替代分组。
|
||||
|
||||
#### Primer:控件等宽不能修复阅读顺序
|
||||
|
||||
[Forms 结构对照](https://primer.style/product/ui-patterns/forms/#structure)将纵向连续表单与更矮的两列表单并列。后者的长选项组标签换行,同时无关字段开启另一条阅读路径。应该比较顺序和分组归属,不能只检查输入框边缘是否齐整。
|
||||
|
||||
| Primer 正例 | Primer 反例 |
|
||||
|---|---|
|
||||
| [](https://primer.style/product/ui-patterns/forms/#structure) | [](https://primer.style/product/ui-patterns/forms/#structure) |
|
||||
|
||||
图片来自 GitHub Primer,链接到原始来源。比较两条阅读路径和换行的选项组标签,将这种分组判断用于整页或整个弹窗中的表单,包括长标签和错误。相关字段在归属清楚、整组能换行时可以同行。这些是外部参照,不是 DSH 实施结果。
|
||||
|
||||
#### Carbon:不同距离表达不同关系
|
||||
|
||||
[Form 规范](https://www.carbondesignsystem.com/building-blocks/core/components/form/specifications)提供[完整表单的标注图](https://s3.us-south.cloud-object-storage.appdomain.cloud/cxp-prod-payload-cms/form-style-1.png)。依次读标题/说明、字段行、选项组与提交区。示例为介绍区、重复字段、末尾操作使用不同层级的距离,同时保留共同内容边缘;标注使层级可以直接比较。
|
||||
|
||||
用这个关系审查 DSH 插件页的介绍、搜索、分区说明、卡片,或模型编辑器的末尾操作。增加一行文字后,再比较哪些元素仍然属于同一组。Carbon 数值属于它自己的示例,不是新的 DSH 间距目录;DSH 的结果还要满足既有密度、字号和响应式规则。
|
||||
|
||||
[](https://www.carbondesignsystem.com/building-blocks/core/components/form/specifications)
|
||||
|
||||
图片来自 IBM Carbon Design System。沿标注比较介绍区、重复字段和末尾操作之间的间距;图中展示其自身的间距体系,不规定 DSH 的像素数值。
|
||||
|
||||
<a id="case-sidebar-groups"></a>
|
||||
### 示例 G4:不同用途的组之间的分隔
|
||||
|
||||
**场景与关系。**“新会话”是主操作,插件/自动化/更多组成全局导航,工作区标题介绍项目列表。相同行高不能表达这几种不同关系。
|
||||
|
||||
**修正与验收。**导航行彼此靠近,导航组与工作区分开,工作区标题更靠近它所标注的项目,主操作与导航组保留独立间隔。连同账号区域检查完整侧栏;比较 A1 按角色区分的关系。在每个项目前都增加大间距,会错误地把每个项目变成独立分区。
|
||||
|
||||
### 组合后的留白与响应式分组
|
||||
|
||||
先确认哪一层负责组间距离,再核算实际组合:父级 gap、子级 margin、原生段落外距、标题 padding、行框空白和条件节点都可能叠加。组内可见距离与组间可见距离应在同一层级比较;不能把一组内部最大的所有空白与任意外部 gap 机械比较。标题带/不带说明时,首次内容的距离可以不同,但两种结构均需明确归属。
|
||||
|
||||
公共容器应提供稳定的标题、说明、正文和操作组织方式,使用方优先沿用。不要在使用方普遍增加负 margin 去抵消容器默认值。确需不同间距时,表达具体角色或变体,再复查共享容器的其他组合;局部更紧凑不等于所有弹窗都应压缩。
|
||||
|
||||
下图是实际语音提示弹窗。左侧普通段落叠加正文与原生外距,使说明脱离标题;右侧使用公共说明区,标题与解释更紧密,独立操作保留分隔。它说明间距应由组合后的关系决定,不规定全部弹窗采用这张图的距离。
|
||||
|
||||

|
||||
|
||||
窄容器首先重排完整语义组。若输入框挤压说明成极窄文字列,将标签与说明留在一起,并把该字段的输入放到下一行;不要缩小所有字体或让说明跨到邻组。验证组件实际容器宽度和断点两侧,而不只改变浏览器宽度。容器查询须在真实组装中生效;选择器、CSS module 名称或祖先结构都可能使看似正确的声明不起作用。
|
||||
|
||||
<a id="density"></a>
|
||||
## 3. 整屏密度
|
||||
|
||||
按任务内容与控件分配可用空间,不是缩小所有间距或字号。
|
||||
|
||||
密度关注可用空间能容纳多少有效工作,以及这些内容是否组织清楚。紧凑的重复行可以与宽松的分区间距共存。先确定阅读和操作任务,而非先追求某个留白百分比:长列表需要稳定行节奏,决策表单需要保留说明和校验,空页面需要连贯的中心内容组,不能沿用满列表的 padding 就算完成。
|
||||
|
||||
宽度变小时,先保留语义组,再考虑缩短文案或移动操作。以完整组为单位换行,组内间距模式保持稳定。高度增加时,明确哪个区域应该伸展,单靠固定 gap 无法定位空状态。同时检查过密和过疏:内容密集但便于比较可以成立,页面稀疏也可能分组混乱。
|
||||
|
||||
<a id="hierarchy"></a>
|
||||
## 4. 信息层级
|
||||
|
||||
让下一步需要阅读或决定的信息可见,不必把每段文字都变成标题。
|
||||
|
||||
强调必须表达与任务有关的差异。当前选择、可处理的错误和决策依据,需要稳定可辨;标签和补充信息可以更弱,但让有用的文字难以阅读不是层级设计。联合观察字号、字重、颜色、位置和周围留白;每个标题都把五种强调用满,反而失去有意义的区别。
|
||||
|
||||
图标与文字的前景角色及语义例外由[图标参考](../.agents/skills/dsh-client-ui-ux/references/icons.md)负责;案例 C1 将该规则用于搜索框。
|
||||
|
||||
字体也是几何的一部分:结合预期字体、长翻译与相关字体设置,比较文字起点、基线和行框。字号配套行高,并遵循[视觉基础](../.agents/skills/dsh-client-ui-ux/SKILL.md#visual-foundation)的字重限制。正文、代码、终端与固定界面角色不必同幅缩放;把差异判为缺陷前,核对[字体设置所有方](../packages/client/ui-theme/README.zh.md)。先修容器分配,不用缩小有用文字来强行对齐。
|
||||
|
||||
<a id="case-search-color"></a>
|
||||
### 示例 C1:同一信息单元的图文强调
|
||||
|
||||
**场景与关系。**比较空的自动化搜索框与展开的侧栏搜索框。二者都是放大镜配合提示文字,但一处图标更深,另一处文字更深。
|
||||
|
||||
**修正与验收。**为空输入提示指定一个前景色角色,包含前导图标。联合检查计算后的颜色、透明度、禁用样式与背景;祖先单独淡化某个子元素时,相同 token 仍可能产生不同结果。检查空值、输入文字、焦点、禁用及明暗主题。已输入的查询文字和具有独立操作的清除按钮,不必使用较弱的占位提示色。子菜单的值与尾部 chevron 也构成同一个值;即使几何正确,弱化的模型名称配更深的箭头仍不符合该关系。
|
||||
|
||||
<a id="states"></a>
|
||||
## 5. 控件状态
|
||||
|
||||
持续状态与临时反馈同时出现时,仍须能够区分。
|
||||
|
||||
状态设计要赋予稳定含义,而不只是换几种颜色。选中表示“当前所在项”,悬停表示“指针在这里”,焦点表示“键盘输入作用于这里”,禁用表示“这个操作暂不可用”。同时比较叠加状态与相邻条目:选中行旁边出现另一条悬停行,与同一行的选中+悬停同样重要。
|
||||
|
||||
| 组合 | 必须继续可辨的含义 | 应寻找的错误 |
|
||||
|---|---|---|
|
||||
| 已选中+另一行悬停 | 当前所在位置仍可识别 | 临时悬停行比当前行更像选中 |
|
||||
| 同一项已选中+悬停 | 选中保留,同时有交互反馈 | 通用 hover 底色抹掉当前状态 |
|
||||
| 已选中+键盘焦点 | 当前位置与键盘目标均可区分 | 焦点提示融进选中样式 |
|
||||
| 已选中+禁用 | 保留的选择与暂不可用同时可辨 | 淡化后看起来像取消了选择 |
|
||||
| 展开+菜单打开 | 当前操作有明确归属 | 面板高亮与行高亮无稳定关系地竞争 |
|
||||
|
||||
<a id="case-sidebar-state"></a>
|
||||
### 示例 S1:持续选中与临时悬停共存
|
||||
|
||||
**场景与关系。**比较当前会话、相邻悬停行和已打开的行菜单。背景绘制只是线索之一,还需结合当前行语义和浮现的操作;选中与悬停不必使用不同颜色。
|
||||
|
||||
**修正与验收。**确认悬停结束或菜单关闭后选中仍然保持。在相邻行之间移动指针并使用键盘遍历,同时检查焦点、操作可见性和当前行语义。比较相同主题与平台材质;不透明的隔离场景不能证明 Desktop 半透明背景的对比关系。
|
||||
|
||||
<a id="component-choice"></a>
|
||||
## 6. 组件选型
|
||||
|
||||
先根据交互含义选择组件,再让布局适应可用空间。
|
||||
|
||||
先按用户能做什么选择元素,再让并列元素保持视觉一致。导航打开目的地,开关改变设置,复选框添加独立选项,操作按钮启动动作;几何相似不代表可以互换。卡片增加了容纳内容的分组与空间,只有当该组需要独立摘要、状态或操作时才成立,不能仅因一行看起来朴素就改成卡片。
|
||||
|
||||
标签、可见边界、焦点行为和点击区域应与交互含义一致。视觉修正若改变这些语义,还需检查行为。几何和圆角另见[第 8 节](#geometry);“原生控件”既不保证视觉适配,也不是丢掉键盘与可访问行为的理由。
|
||||
|
||||
<a id="icon-optics"></a>
|
||||
## 7. 图标光学
|
||||
|
||||
[图标参考](../.agents/skills/dsh-client-ui-ux/references/icons.md)负责界面图标的角色、槽位、颜色和视觉平衡;[官方 artwork](../.agents/skills/dsh-official-plugin-artwork/SKILL.md)保留独立职责。
|
||||
|
||||
<a id="case-file-optics"></a>
|
||||
### 示例 O1:不同类型图标的光学对齐
|
||||
|
||||
**场景与关系。**在同一树形深度比较关闭/打开的文件夹、原生文件类型图标、通用文件及无图标名称,再比较下一层的同样组合。
|
||||
|
||||
**修正与验收。**保留层级缩进和共同文字起点,先统一放置槽,再比较槽内可见中心与视觉重量。不能移动文字去补偿单个 glyph,也不能把所有深度压到同一边缘。检查正常尺寸和放大图,包含选中底色与长文件名;放大图用于诊断绘制,正常尺寸决定结果是否平衡。
|
||||
|
||||
<a id="case-chevron-consumers"></a>
|
||||
### 示例 O2:共享图形在不同使用尺寸下的表现
|
||||
|
||||
**场景与关系。**同一个 chevron 既出现在菜单文字旁,也出现在较大的悬浮按钮中。在同一 viewBox 内放大绘制范围,会同时改变两处表现。
|
||||
|
||||
**修正与验收。**对比各方向、两种字重及实际使用尺寸。文字旁保留适度的辅助图形;必要时缩小悬浮控件里的图标,但不缩小按钮。只看放大的图标总表,不能证明这两种构图都平衡。
|
||||
|
||||
<a id="geometry"></a>
|
||||
## 8. 控件几何与圆角关系
|
||||
|
||||
一致意味着相同角色、相同尺寸变体遵循同一规则,不同角色之间具有可理解的关系。联合检查外部高度、宽度、圆角、曲线、内缩与可见底板。合法 token 也能组合出不协调的外观,所有元素套用同一个圆角同样可能不协调。数值与嵌套几何由[圆角规范](ui-radius.zh.md)拥有,本文不另建尺度。
|
||||
|
||||
区分沿容器内缩的贴合表面与面板里的独立卡片:前者要协调内外曲线和内缩距离,后者按自身组件角色选择。一起检查底色、描边、高亮、伪元素与裁切。即使静止描边正确,hover 底色改变了可见轮廓,也属于状态缺陷。
|
||||
|
||||
### 按这个顺序判断几何
|
||||
|
||||
先确定语义角色,选择已有尺寸变体,识别父子关系,再观察曲线、材质和状态。[圆角尺度](ui-radius.zh.md#radius-scale)及其明确例外提供数值;展开后的外接框变大,不会自动选择新档位。相同半径数值也可能因高度或曲线不同而产生不同观感。
|
||||
|
||||
| 关系 | 从圆角所有方得到的判断 | 要寻找的视觉错误 |
|
||||
|---|---|---|
|
||||
| 圆弧同心内缩 | 内半径为外半径减内缩,最小为零;圆弧共用圆心 | 内外同半径使可见间隔在角落挤压或扩大 |
|
||||
| 嵌套超椭圆 | 采用共享名义尺寸关系,再观察斜向留白;它们不是严格偏移曲线 | 仅凭半径相减就声称数学上等距 |
|
||||
| 贴边填色或相邻分段 | 外框拥有暴露角落,内部接缝保持方角 | 缺口、月牙、双轮廓,或焦点被随底色一起裁掉 |
|
||||
| 独立子卡片/分组 | 按自身角色选档,不机械减去全部周围空间 | 因父级 padding 很大而把合理子圆角减成零 |
|
||||
| 明确的圆形/胶囊 | 容器与实际绘制内容保持圆弧 | 圆形头像容器里嵌着非圆形图片;胶囊端部变方圆 |
|
||||
|
||||
在整页或整个弹窗中一起比较底色、描边、hover 覆盖层、焦点和裁切。平面卡片边框与有层级阴影的材质,在 [Web 样式](web-styling.zh.md#component-rules)中具有不同绘制归属,同时应用可能让边缘加倍。轮廓裁切底色时,弹出菜单和键盘焦点仍须完整可见。几何与持续使用应一起判断。
|
||||
|
||||
#### Fluent:分段按钮的缺口是关系错误
|
||||
|
||||
[Shapes 案例](https://fluent2.microsoft.design/shapes/#when-to-avoid-rounded-corners)标出了两个各自圆角的按钮半区相接时形成的空隙。应观察内部接缝而非外部四角:把每半区都当成独立完整轮廓,会破坏共同外框。这是具体的反例,不是在建议所有控件都更圆。
|
||||
|
||||
[](https://fluent2.microsoft.design/shapes/)
|
||||
|
||||
图片来自 Microsoft Fluent 2,链接到原始来源。在 DSH 中采用圆角规范的贴边填色关系,再一起检查左右两半的悬停、焦点和弹出菜单是否完整显示。Fluent 的圆角与描边尺度不是 DSH 默认值。观察内部接缝的凹口;原图是反例,不是 DSH 的修改前后对照。
|
||||
|
||||
<a id="case-model-radius"></a>
|
||||
### 示例 R1:不同层级容器与控件的圆角
|
||||
|
||||
**场景与关系。**模型编辑器包含模型分组行、每行两个输入框、“添加模型”按钮及取消/保存操作。这些圆角同时出现在一屏,角色和尺寸关系必须能够一起读懂。
|
||||
|
||||
**修正与验收。**先区分外层编辑器、模型组、字段、按钮,再选择现有变体。同角色同尺寸控件保持一致,紧密嵌套曲线与实际内缩距离协调,独立子控件保留自己的角色。检查行的折叠/展开、字段焦点、hover 底色及长模型名,不能拿展开后的总高度解释任意圆角。
|
||||
|
||||
<a id="case-checkbox-radius"></a>
|
||||
### 示例 R2:表单中小型选择控件的轮廓
|
||||
|
||||
**场景与关系。**输入类型选项使用深色原生复选框,旁边是圆角表单控件。这里检查小型选择控件的外轮廓和重量,不是要求复选框具有大输入框的圆角。
|
||||
|
||||
**修正与验收。**采用协调的小型选择控件样式,保留复选框易辨识的轮廓及标签关联。替换原生外观时,继续保留选中/未选中、禁用、键盘焦点和辅助技术语义。明暗主题中按实际尺寸比较这些状态;未选中的线框与选中的底色必须属于同一控件族。
|
||||
|
||||
<a id="case-automation-radius"></a>
|
||||
### 示例 R3:不同角色之间有依据的几何差异
|
||||
|
||||
**场景与关系。**自动化页相邻的筛选项、搜索框和主创建操作采用不同圆角。即使高度接近,各功能自己选择半径,仍可能产生明显的弯曲感跳变。
|
||||
|
||||
**修正与验收。**复用已有控件变体,真正的胶囊是明确的组件类型,不代表所有紧凑按钮都应使用高度一半作为半径。将筛选、搜索和主次按钮放在同一画面比较,同时检查 hover 与焦点轮廓。预期的胶囊/字段差异可以保留,不能仅因同行就强制圆角相等。
|
||||
|
||||
<a id="reproduce"></a>
|
||||
## 复现与比较
|
||||
|
||||
本地维护的生产组件 fixture 可提供固定内容与状态的隔离示例。实际应用截图应来自本次要审查的整页或整个弹窗,包含相关区域与状态。fixture 只能证明它所渲染的条件,不能决定或完成审查范围。
|
||||
|
||||
这些组件中的尺寸是对应案例的证据,不是通用设计常数。共享数值仍由[样式参考](web-styling.zh.md)、[圆角标准](ui-radius.zh.md)及组件归属方定义。新增使用位置或任务不同时,应补充证据,不能脱离条件照抄案例数值。
|
||||
|
||||
<a id="references"></a>
|
||||
## 外部参考与 DSH 的取舍
|
||||
|
||||
这些是研究输入,不是自动启用的 skill,也不替代 DSH 规范。下列材料阅读于 2026-10-26。公开资料提供比较方法,本文 DSH 案例和原理图是结合本地场景的归纳;链接指向原作者。使用本文不需要安装或执行外部 skill。
|
||||
|
||||
| 参考 | 可采用的贡献 | 范围与明确取舍 |
|
||||
|---|---|---|
|
||||
| [Anthropic frontend-design](https://github.com/anthropics/skills/blob/main/skills/frontend-design/SKILL.md) | 明确构图、围绕实际内容做选择、观察渲染结果自评 | 表现性视觉方向不意味着应重设工作界面风格或更换 DSH 字体 |
|
||||
| [Vercel web-design-guidelines skill](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md) 与[规则](https://github.com/vercel-labs/web-interface-guidelines/blob/main/command.md) | 定位问题源码,覆盖焦点、标签、长内容与交互 | 代码合规不能证明对齐和分组;其 title case 建议不覆盖 DSH 的 sentence case |
|
||||
| [Impeccable](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/SKILL.md)、[critique](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/reference/critique.md)、[polish](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/reference/polish.md)、[operate](https://github.com/pbakaus/impeccable/blob/main/.agents/skills/impeccable/reference/operate.md) | 区分任务型界面与宣传设计,区分视觉判断与检测器输出 | 采用完整路径检查与现有系统比较,不引入其执行、委派或评分机制 |
|
||||
| [Interface Design](https://github.com/Dammyjay93/interface-design/blob/main/.claude/skills/interface-design/SKILL.md) | 明确产品意图、复用重复模式、与已有相邻界面比较 | 保留 DSH 现有系统,不照搬示例字重、不另建 token 目录、不为独特而独特 |
|
||||
| [OpenAI figma-implement-design](https://github.com/openai/skills/blob/main/skills/.curated/figma-implement-design/SKILL.md) | 用真实视觉参考比较渲染结果,并整合已有组件 | 截图是有范围的证据,不代表可以推断未展示状态或照搬每个数值 |
|
||||
| [Fluent Shapes](https://fluent2.microsoft.design/shapes/)与 [Primer Forms](https://primer.style/product/ui-patterns/forms/) | 能直接检查关系的组件反例 | 上文案例分析迁移判断方法,不迁移其 token 或交互体系 |
|
||||
| [Atlassian spacing](https://atlassian.design/foundations/spacing/) | 邻近关系、重复节奏、层级与光学调整的正反例 | 借鉴关系判断,不移植 Atlassian token 名称或自动像素公式 |
|
||||
| [Carbon spacing](https://www.carbondesignsystem.com/building-blocks/foundations/spacing/overview) | 间距模式表达等权与分区边界,流式居中不同于固定距离 | 沿用 DSH 间距所有方,只有一套尺度仍不能选择正确分组关系 |
|
||||
| [Codex 官方产品介绍](https://openai.com/index/introducing-the-codex-app/)与[桌面文档](https://learn.chatgpt.com/docs/app) | 任务导向的构图和可比较的真实公开示例 | 公开示例是特定版本的观察,不是当前本机应用或未公开的 Codex 设计规范 |
|
||||
|
||||
### Codex:限定用途的构图参照
|
||||
|
||||
官方[欢迎区/输入框封面](https://learn.chatgpt.com/images/codex/video-posters/proactive-teammate-v2.webp)与[长输入封面](https://learn.chatgpt.com/images/codex/video-posters/draft-prds-from-slack-linear-docs.webp)展示居中的介绍内容、居左的输入文字,以及底部独立控件组,可以支持这项构图比较。它们没有展示完整侧栏对齐、嵌套圆角、相邻选中/悬停或缩放变化。这些判断应使用上文更具体的案例,不能把产品封面当作广泛设计背书。
|
||||
|
||||
外部图片保留在署名来源中。源图不可用时,邻近分析仍指出比较对象;新审查要使用该图作视觉证据前,应先取得可用参照。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## 开发备注
|
||||
|
||||
无。
|
||||
@@ -12,8 +12,8 @@
|
||||
en: aad65e4a8399adbb
|
||||
zh: e76b80d421b17992
|
||||
/dsh-unified-corner-radius-standard/scope-and-authority:
|
||||
en: cd8120cba5566e19
|
||||
zh: 65359436088c8bc5
|
||||
en: 909c1c11ccf420dc
|
||||
zh: 23bec3e3dfa856b6
|
||||
/dsh-unified-corner-radius-standard/select-the-radius:
|
||||
en: e736416ec85d6e16
|
||||
zh: 0edf60fccc867295
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 7fe4eb7707e6a9f3
|
||||
zh: be7530a05c205e27
|
||||
/dsh-unified-corner-radius-standard/settings-card-materials:
|
||||
en: 85e7e63183df06d1
|
||||
zh: 1da2b44083d7ebe2
|
||||
en: 126066f9c638433c
|
||||
zh: a7416d255f838b92
|
||||
/dsh-unified-corner-radius-standard/apply-and-verify:
|
||||
en: 2f6f10e45b27c19b
|
||||
zh: 9eb5fd964c643be6
|
||||
|
||||
+3
-1
@@ -18,7 +18,7 @@ Choose consistent corner radii by component role and size, and align nested regi
|
||||
<a id="scope"></a>
|
||||
## Scope and authority
|
||||
|
||||
This reference defines the DSH Web and Desktop radius scale and its component rules. A local visual adjustment stays scoped to the named component or family and does not automatically change the global scale.
|
||||
This reference defines the DSH Web and Desktop radius scale, curve rules and component exceptions. Use the [UI design skill's geometry decisions](../.agents/skills/dsh-client-ui-ux/SKILL.md#component-geometry) to select and review their application. A local visual adjustment stays scoped to the named component or family and does not automatically change the global scale.
|
||||
|
||||
The [Web UI style reference](web-styling.md) defines general styling ownership. Shared values live in [`base.css`](../packages/client/ui-theme/src/styles/base.css), and curve behavior lives in [`corner-shape.css`](../packages/client/ui-theme/src/styles/corner-shape.css). Update the theme implementation and both language versions of this standard together when changing a rule.
|
||||
|
||||
@@ -107,6 +107,8 @@ The theme resolves the card fill to `--dsw-alias-bg-layer-2` and the stroke to `
|
||||
|
||||
Flat neutral borders use the shared 0.5px hairline. Elevated menus, popovers, dialogs, and panels use `border: 0` with the existing `--dsw-elevation-*` material, whose shadow includes the hairline. Do not add a second neutral border over that stroke or add a shadow to an ordinary settings card merely to distinguish its tab. Preserve semantic state-colored borders and the existing translucent-menu backdrop behavior.
|
||||
|
||||
A page-level radius review compares roles and silhouettes together, including native small selection controls. Token compliance only establishes membership in the scale; it does not establish that adjacent roles or nested contours look coherent. Use the [geometry cases](ui-design-review-cases.md#geometry) for the comparison method, while this document remains the owner of values and curve rules.
|
||||
|
||||
<a id="verification"></a>
|
||||
## Apply and verify
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<a id="scope"></a>
|
||||
## 范围与依据
|
||||
|
||||
本参考文档规定 DSH Web 和 Desktop 的圆角尺度及组件应用规则。局部视觉调整限定在指定组件或组件族内,不自动改变全局尺度。
|
||||
本参考文档规定 DSH Web 和 Desktop 的圆角尺度、曲线规则及组件例外。应用时按 [UI 设计 skill 的几何判断](../.agents/skills/dsh-client-ui-ux/SKILL.md#component-geometry)选择并审查。局部视觉调整限定在指定组件或组件族内,不自动改变全局尺度。
|
||||
|
||||
通用样式职责见 [Web UI 样式参考](web-styling.zh.md)。共享数值位于 [`base.css`](../packages/client/ui-theme/src/styles/base.css),曲线行为位于 [`corner-shape.css`](../packages/client/ui-theme/src/styles/corner-shape.css)。修改规则时同步更新主题实现及本规范的中英文版本。
|
||||
|
||||
@@ -107,6 +107,8 @@ H 表示以 CSS 像素计、包含边框的设计外部高度。R 表示浏览
|
||||
|
||||
平面中性边框使用共享的 0.5px 发丝线。具有层级阴影的菜单、浮层、对话框和面板使用 `border: 0` 与既有 `--dsw-elevation-*` 材质,其阴影已包含发丝描边。不要在该描边上再叠一层中性边框,也不要仅为区分设置 tab 就给普通设置卡片加阴影。保留语义状态色边框与既有的半透明菜单背景效果。
|
||||
|
||||
整页圆角审查要同时比较角色与轮廓,包含原生的小型选择控件。符合 token 只能证明数值属于尺度,不能证明相邻角色或嵌套轮廓协调。比较方法见[几何案例](ui-design-review-cases.zh.md#geometry),本规范继续拥有数值与曲线规则。
|
||||
|
||||
<a id="verification"></a>
|
||||
## 应用与验证
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.2-alpha.2/retained-ptc-timeout/guide.md
|
||||
/:
|
||||
en: 81282ebd9fb3522f
|
||||
zh: a10b0dd429c1e527
|
||||
/retained-ptc-timeout-recovery:
|
||||
en: 077a655f2f967e61
|
||||
zh: 2c3458dc17023b75
|
||||
/retained-ptc-timeout-recovery/change:
|
||||
en: f3ebfde587b16d65
|
||||
zh: b48f8fdc91d84ccb
|
||||
/retained-ptc-timeout-recovery/migration:
|
||||
en: 969be7aa004c1c83
|
||||
zh: a8dfde8f75efaa9a
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Retained Node PTC uses cooperative stop on timeout or cancellation, with process termination when acknowledgment fails."
|
||||
---
|
||||
|
||||
# Retained PTC timeout recovery
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
Timeouts and cancellation in `persistent-in-memory` mode and `vm-script` with `scriptPersistence: true` request cooperative stop. Previously, they always cleared the environment. Canceled tool calls throw, allowing `catch` and `finally` to run. Stop acknowledgment waits for the evaluation to settle and preserves variables and earlier mutations. The host waits up to `stopGraceMs` (default `3000`) for acknowledgment, then terminates the managed process range if acknowledgment is missing or fails. A blocked loop therefore still loses state. Temporary execution continues to terminate its process.
|
||||
|
||||
The runtime returns after acknowledgment or fallback cleanup, with the original `timeout` or `abort` kind and a diagnostic stating whether variables survived. Confirmed termination allows the next call to create an empty environment. Failed cleanup returns `worker-exit` and blocks reuse until host release succeeds. Timers, arbitrary user Promises, I/O callbacks, Workers and subprocesses have no per-evaluation cancellation guarantee; acknowledgment does not mean all Node activity has stopped.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Check the reported state outcome instead of assuming that timeout or cancellation clears variables. Reuse retained bindings; recreate them after fallback termination. Earlier effects are not rolled back.
|
||||
2. Use the host's `PtcExecutor.reset(agent)` or runtime `release(owner)` if cleanup failed or retained resources cannot be trusted. No reset tool is exposed to the model. Await reset before running more code.
|
||||
3. Set `stopGraceMs` in the `@deepseek-ai/dsh-ptc-runtime-node` configuration if needed. It bounds acknowledgment separately from the existing process-cleanup `graceMs`. Verify that an asynchronous wait preserves an earlier variable and a blocked loop clears it. See the [runtime reference](../../../../packages/ptc-runtime/ptc-runtime-node/README.md).
|
||||
4. When `launch.bootstrapPath` points to a separately installed Node bootstrap, upgrade it with the host. Readiness checks the protocol revision before dispatch; an older bootstrap reports `bootstrap protocol mismatch` instead of running code.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "保留状态的 Node PTC 在超时或取消时协作式停止,确认失败时终止进程兜底。"
|
||||
---
|
||||
|
||||
# 保留状态的 PTC 超时恢复
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
`persistent-in-memory` 模式及启用 `scriptPersistence: true` 的 `vm-script` 在超时或取消时请求协作式停止。此前这两种情况总会清空环境;现在取消的工具调用正常抛错,允许 `catch` 和 `finally` 运行。停止确认等待本次执行结束,并保留变量及此前修改。宿主最多等待 `stopGraceMs`(默认 `3000`)取得确认,未收到确认或确认失败时终止受管进程范围。因此阻塞的死循环仍会丢失状态。临时执行仍终止进程。
|
||||
|
||||
运行时在收到确认或兜底清理完成后返回,保留原来的 `timeout` 或 `abort` 类型,并在诊断中说明变量是否保留。确认终止后,下一次调用可创建空环境。清理失败返回 `worker-exit`,并阻止复用,直到宿主释放成功。定时器、任意用户 Promise、I/O 回调、Worker 和子进程不承诺按执行取消;收到确认不表示所有 Node 活动均已停止。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 检查报告的状态结果,不再假定超时或取消会清空变量。复用保留的绑定;兜底终止后重新创建。此前的效果不会回滚。
|
||||
2. 清理失败或保留资源不可信时,使用宿主的 `PtcExecutor.reset(agent)` 或运行时 `release(owner)`。模型不会获得重置工具。等待重置完成后再执行代码。
|
||||
3. 按需在 `@deepseek-ai/dsh-ptc-runtime-node` 配置中设置 `stopGraceMs`。它限制确认等待时间,与现有进程清理的 `graceMs` 独立。验证异步等待会保留此前变量,阻塞的死循环会清空变量。参阅[运行时参考](../../../../packages/ptc-runtime/ptc-runtime-node/README.zh.md)。
|
||||
4. 如果 `launch.bootstrapPath` 指向单独安装的 Node bootstrap,随宿主一起升级它。派发前的就绪握手会检查协议版本;旧 bootstrap 报告 `bootstrap protocol mismatch`,不会执行代码。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.2-alpha.3/chat-process-activity-context/guide.md
|
||||
/:
|
||||
en: cee1fd1a4a871edb
|
||||
zh: d02fdd6e917aacc1
|
||||
/chat-processactivity-adds-context:
|
||||
en: eb074a142ff239d1
|
||||
zh: a73ee547890e9ec9
|
||||
/chat-processactivity-adds-context/change:
|
||||
en: 27273494c758b014
|
||||
zh: aa34a345a74a39c8
|
||||
/chat-processactivity-adds-context/migration:
|
||||
en: 220c40bf5382ac52
|
||||
zh: aae0d1731127571a
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Chat ProcessActivity gains a context category, and the running activity uses RunningProcessActivity."
|
||||
---
|
||||
|
||||
# Chat `ProcessActivity` adds `context`
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
`ProcessActivity`, exported from `@deepseek-ai/dsh-client-ui-chat/client`, gains the member `context`. A closed process group whose members are all Context injections reports it as its only category. `ProcessActivitySummary.running` is now typed as the new exported `RunningProcessActivity`, which excludes `context`. This affects extensions that switch exhaustively over `ProcessActivity` or map every member to a label or icon.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Add a `context` branch wherever code handles every `ProcessActivity` member, for example in a `Record<ProcessActivity, …>` or a `switch` ending in `assertNever`.
|
||||
2. Type values read from `summary.running` as `RunningProcessActivity` when they need not cover `context`.
|
||||
3. Run the extension's typecheck and confirm it compiles without a missing-member error.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Chat 的 ProcessActivity 新增 context 类别,运行中活动改用 RunningProcessActivity。"
|
||||
---
|
||||
|
||||
# Chat `ProcessActivity` 新增 `context`
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
`@deepseek-ai/dsh-client-ui-chat/client` 导出的 `ProcessActivity` 新增成员 `context`。成员全部为上下文注入的已结束过程组以它作为唯一类别。`ProcessActivitySummary.running` 的类型改为新导出的 `RunningProcessActivity`,该类型不含 `context`。对 `ProcessActivity` 做穷尽 switch 或为每个成员映射文案、图标的扩展受影响。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 在处理全部 `ProcessActivity` 成员的位置补上 `context` 分支,例如 `Record<ProcessActivity, …>` 或以 `assertNever` 结尾的 `switch`。
|
||||
2. 读取 `summary.running` 且无需覆盖 `context` 的值,类型改为 `RunningProcessActivity`。
|
||||
3. 运行扩展的类型检查,确认没有缺少成员的编译错误。
|
||||
@@ -9,14 +9,17 @@
|
||||
en: dc8c41fbf9869179
|
||||
zh: e45976a585cb9c86
|
||||
/web-ui-style-reference/component-rules:
|
||||
en: 4ee4a6fd2917306a
|
||||
zh: d7d68c140a698ab0
|
||||
en: 8b301ae4862ee8e5
|
||||
zh: eb65019fb1e26f30
|
||||
/web-ui-style-reference/input-capabilities:
|
||||
en: bf2e98bce68743e2
|
||||
zh: fc58dcbb60bb944a
|
||||
/web-ui-style-reference/corner-radii-and-settings-cards:
|
||||
en: 118972f6c2bd6ce4
|
||||
zh: 85f8416b03e4dd8b
|
||||
/web-ui-style-reference/review-the-assembled-design:
|
||||
en: 21bed565fa27b823
|
||||
zh: 84b5a96127c842c0
|
||||
/web-ui-style-reference/changing-the-system:
|
||||
en: 71501119492a3ad4
|
||||
zh: 87bf6c9406d0d9ed
|
||||
|
||||
@@ -26,6 +26,8 @@ Global style sheets belong in `ui-theme/src/styles/`. Component styles live besi
|
||||
- Elevated surfaces (menus, popovers, modals, panels, floating buttons, the composer) set `border: 0` and take `box-shadow: var(--dsw-elevation-panel)`, `var(--dsw-elevation-prominent)`, or the composer's `var(--dsw-elevation-soft)` (larger blur at lower alpha): the 0.5px hairline stroke is the first shadow layer, and `--dsw-elevation-stroke-color` rebinds or suppresses it per surface or state. Never pair a `--dsw-alias-border-*` border with an lv/elevation shadow — the ui-theme elevation spec rejects the pairing; state-colored borders (warn panels) stay real borders.
|
||||
- Existing and new dropdown, context, submenu, and selection menus use `Menu` or wrap custom content in `MenuSurface`. The material is the theme-owned `--dsw-menu-surface-fill` plus `--dsw-menu-backdrop-filter`; feature and platform CSS must not override either. The ui-theme menu spec rejects unwrapped menu/listbox renderers, material overrides, and token redefinitions. The schedule-owned `TaskMenu` and `ClockPicker` retain their existing containers and material as explicit exceptions ([owner](../packages/client/ui-schedule/README.md)).
|
||||
- Modal backdrops, including settings and confirmation dialogs, retain their translucent dark mask without blur. The theme sets `--dsw-mask-blur` to `none`; Desktop-owned modal overlays also leave the parent page unfiltered.
|
||||
- Floating panels preserve horizontal anchor alignment when neither vertical side fits. Try vertical window bounds before a fallback that also clears horizontal anchoring; every candidate reserves the window edge and frame margins.
|
||||
|
||||
- Menu background filtering stays on an isolated layer so nested menus and fixed overlays retain their positioning and backdrop. On macOS, `MenuSurface` adds an opaque backing behind page content only within each menu’s bounds, allowing Chromium to blur content over native vibrancy; CSS anchors follow placement and size, and unmounting removes the backing. Overlays without this backing use `--dsw-specific-menu`, whose theme-owned macOS fill is 94% opaque to keep underlying text from bleeding through. Other elevated surfaces using that fill pair it with the same filter on their own isolated background when they contain fixed overlays.
|
||||
- Flat borders and separators that use a neutral `--dsw-alias-border-*` token draw at `0.5px` — buttons, inputs, cards, row dividers, and separators drawn as filled boxes (menu separators, the conversation header seam, markdown `hr`, vertical rails) share the hairline weight, which Chromium paints as one device pixel. Dashed affordances and state-colored borders keep 1px; spinner ring tracks keep their width through the spec's explicit allowlist. The ui-theme elevation spec rejects wider neutral solid borders.
|
||||
- Clickable artifact links (markdown anchors, prose file mentions, web source and fetch links, produced-file chips, workflow member links) color through `--dsw-alias-link` at `font-weight: 500`, with no underline at rest and a dotted 3px-offset underline on hover/focus. Compact Thinking Markdown keeps tertiary text color and a resting dotted underline ([compact presentation](../packages/client/ui-primitives/README.md#rendering-agent-output)). Text-leading anchors also lead with the ui-primitives `LinkIconMedium` category glyph riding `currentColor`, which for a well-known external host is that site's own mark instead of the globe; workflow member links and image-only anchors carry no glyph, and tool-row file links keep their grey dotted affordance.
|
||||
@@ -46,6 +48,10 @@ On a mixed-input device, a mouse preview can appear while the primary-pointer CS
|
||||
|
||||
The [DSH unified corner-radius standard](ui-radius.md) defines the radius scale, component size mapping, circle and capsule exceptions, nested hover geometry, and settings-card fills and strokes.
|
||||
|
||||
## Review the assembled design
|
||||
|
||||
Use the [UI review baseline](ui-design-review-cases.md#review-baseline) to combine these material and typography rules with radius, responsive behavior, feedback and artwork guidance across the [entire page or modal](ui-design-review-cases.md#whole-interface-review). Compare all regions and same-role elements within that unit; individually compliant components can still form doubled edges, inconsistent grouping or clipped focus indicators. This document and the theme remain the styling owners.
|
||||
|
||||
## Changing the system
|
||||
|
||||
Add or change a shared token in the owning `ui-theme` sheet, then consume its semantic alias from feature packages. Update the owning package reference when a public styling contract changes. Visual behavior follows the [testing policy](testing.md); the [archived styling-system Agent Note](../.agents/notes/archived/process/2026-07-19-web-styling-system.md) records framework rationale.
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
- 高层级表面(菜单、浮层、对话框、面板、悬浮按钮、输入框)设 `border: 0` 并使用 `box-shadow: var(--dsw-elevation-panel)`、`var(--dsw-elevation-prominent)` 或输入框专用的 `var(--dsw-elevation-soft)`(更大模糊、更低透明度):0.5px 发丝描边是第一层投影,`--dsw-elevation-stroke-color` 可按表面或状态重绑或抑制描边。不得将 `--dsw-alias-border-*` border 与 lv/elevation 投影配对——ui-theme 的 elevation spec 会拒绝;状态色 border(warn 面板)保持真 border。
|
||||
- 存量和新增的下拉菜单、上下文菜单、子菜单及选择菜单使用 `Menu`,自定义内容用 `MenuSurface` 包裹。材质由主题的 `--dsw-menu-surface-fill` 和 `--dsw-menu-backdrop-filter` 组成;功能及平台 CSS 不得覆盖两者。ui-theme 菜单检查拒绝未使用共享容器的 menu/listbox 渲染、材质覆盖和 token 重定义。计划任务自有的 `TaskMenu` 与 `ClockPicker` 是明确例外,保留现有容器和材质([所属包](../packages/client/ui-schedule/README.zh.md))。
|
||||
- 设置、确认框等模态弹窗保留黑色半透明遮罩,不模糊背景。主题将 `--dsw-mask-blur` 设为 `none`;Desktop 自有模态浮层也不对父页面施加模糊。
|
||||
- 浮层在上下两侧均放不下时,保留与锚点的横向对齐。先尝试窗口内的纵向范围,再尝试同时清除横向锚定的回退位置;每个候选位置均须保留窗口边距与顶栏间距。
|
||||
|
||||
- 菜单背景模糊绘制在独立图层上,保证嵌套菜单和 fixed 浮层的定位及背景采样。macOS 上,`MenuSurface` 仅在各菜单范围内、页面内容后方添加实色底层,使 Chromium 能在原生透明材质上模糊页面内容;CSS 锚点跟随位置及尺寸,卸载时移除底层。没有该底层的浮层使用 `--dsw-specific-menu`,主题在 macOS 上将其设为 94% 不透明度,防止下方文字透出。其他使用该填充的浮起表面也须配套同一模糊;包含 fixed 浮层时,将两者绘制在独立背景层上。
|
||||
- 使用中性 `--dsw-alias-border-*` token 的平面边框与分割线一律 `0.5px`——按钮、输入框、卡片、行分割线,以及以填充盒绘制的分隔线(菜单分隔、对话标题栏接缝、markdown `hr`、竖向轨道线)共用发丝线粗细,Chromium 将其绘制为一个设备像素。dashed 记号与状态色 border 保持 1px;spinner 圆环经 spec 的显式豁免保留原宽度。更宽的中性 solid border 会被 ui-theme elevation spec 拒绝。
|
||||
- 可点击产物链接(Markdown 锚点、正文文件引用、网页来源与抓取链接、产物 chips、workflow 成员链接)经 `--dsw-alias-link` 着色、`font-weight: 500`,默认无下划线,hover/focus 时为 3px offset 的点状下划线。紧凑 Thinking Markdown 保持 tertiary 文字色和默认点状下划线([紧凑展示](../packages/client/ui-primitives/README.zh.md#rendering-agent-output))。带文字的锚点另以 ui-primitives 的 `LinkIconMedium` 分类图形(随 `currentColor`)作前置;目的地是已知外部站点时改用该站点自己的标记而非地球;workflow 成员链接与只包图片的锚点不带图形,工具行文件链接保持其灰色点线示能。
|
||||
@@ -46,6 +48,10 @@
|
||||
|
||||
圆角尺度、组件尺寸映射、正圆与胶囊例外、嵌套 hover,以及设置卡片的底色与描边统一由 [DSH 统一圆角规范](ui-radius.zh.md)定义。
|
||||
|
||||
## 审查组装后的设计
|
||||
|
||||
按 [UI review 基准](ui-design-review-cases.zh.md#review-baseline),在[整页或整个弹窗](ui-design-review-cases.zh.md#whole-interface-review)中综合判断材质、字体、圆角、响应式行为、反馈和图案。统一比较这个范围内的所有区域与同角色元素;组件分别合规,组合后仍可能出现双描边、分组不一致或焦点被裁切。本规范与主题继续拥有样式规则。
|
||||
|
||||
## 变更系统
|
||||
|
||||
在所属 `ui-theme` 样式表中添加或修改共享 token,然后在功能包中使用其语义别名。公共样式约定发生变化时,更新所属包的参考文档。视觉行为遵循[测试策略](testing.zh.md);[已归档的样式系统 Agent Note](../.agents/notes/archived/process/2026-07-19-web-styling-system.md) 记录框架依据。
|
||||
|
||||
@@ -115,6 +115,8 @@ One UI feature = one plugin package (`src/client/` browser half). A multi-domain
|
||||
|
||||
## Styling and localization
|
||||
|
||||
Use [dsh-client-ui-ux](../../.agents/skills/dsh-client-ui-ux/SKILL.md) for UI design rules and review requirements. The client test ladder below still applies.
|
||||
|
||||
[docs/web-styling.md](../../docs/web-styling.md) is authoritative. Shared `--dsw-*` tokens and global sheets live in `ui-theme/src/styles/`; feature components consume semantic aliases through CSS Modules and `clsx`, with no literal colors, component library, or Tailwind. Code comments are English. Design judgment beyond those rules — feedback surfaces, loading states, overlay safety, platform window adaptation — follows [dsh-client-ui-ux](../../.agents/skills/dsh-client-ui-ux/SKILL.md); responsive layout and telling a mobile or touch client from a desktop one follow [dsh-responsive-ui](../../.agents/skills/dsh-responsive-ui/SKILL.md).
|
||||
|
||||
Menus use `Menu` or `MenuSurface`, including custom listboxes, except the explicitly listed schedule containers. Preserve the shared transparency and blur ([rules](../../docs/web-styling.md#component-rules)).
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
en: 8d6c7c6e63f849f6
|
||||
zh: d89fa273e30b99d3
|
||||
/deepseek-ai-dsh-client-ui-approval/table-of-contents:
|
||||
en: 7b5184a35d7607f9
|
||||
zh: 55ebfea4d5ad26ed
|
||||
en: 4dba952a4e1f1d6b
|
||||
zh: a33ee85e0b0cb212
|
||||
/deepseek-ai-dsh-client-ui-approval/model-experience:
|
||||
en: 8161dce49e9f899c
|
||||
zh: a17401e1e78ba6be
|
||||
|
||||
@@ -22,6 +22,8 @@ Focus the approval detail region to approve with Enter or reject with Escape. Th
|
||||
|
||||
When offered, **Always allow** is the highlighted primary button. Otherwise, the current-request allow action is primary. Enter in the detail region always grants the limited scope: once for a tool approval, or the current session for CUA. Persistent access requires explicitly activating Always allow. The shortcut reference uses Allow because the caller supplies that scope.
|
||||
|
||||
Callers can supply localized warning copy through the shared card's optional `warning` field, with paragraphs separated by blank lines (`\n\n`). Hovering or keyboard-focusing the tag opens a formatted nonmodal card; clicking or tapping also opens it. The pointer can cross into the card to read or select its text. Leaving both surfaces closes it after a short grace period unless keyboard focus remains inside. Moving focus outside, clicking outside, and Escape also dismiss it without rejecting the request. Hover does not move focus. Moving focus elsewhere also closes a hover-open card, including when a foreground modal takes focus. Switching away from the window or hiding the browser tab closes it without restoring focus. Foreground modals and input-method composition retain their keyboard handling. CSS anchors position the card above the tag with window-edge fallbacks and internal scrolling when space is limited.
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ kind: "package-reference"
|
||||
|
||||
提供“始终允许”时,将它显示为高亮主按钮;否则高亮当前请求的允许按钮。在详情区域按 Enter 始终授予有限范围的许可:工具审批为单次,CUA 为当前会话。持久授权需要显式操作“始终允许”按钮。快捷键说明使用“允许”,具体范围由调用方提供。
|
||||
|
||||
调用方可以通过共享卡片的可选 `warning` 字段提供本地化风险说明,段落以空行(`\n\n`)分隔。悬停或通过键盘聚焦标签会打开保留格式的非模态卡片;点击或轻触也可打开。指针可以移入卡片阅读或选择文字。离开标签与卡片后,卡片会在短暂宽限期后关闭,但键盘焦点仍在其中时保持打开。焦点移出、点击外部或按 Escape 也可关闭卡片,不会拒绝请求。悬停不会移动焦点。悬停打开后,焦点转移到其他位置也会关闭卡片,包括前景模态框取得焦点时。切换到其他窗口或隐藏浏览器标签页会关闭卡片,不会将焦点拉回。前景模态框和输入法组合保留各自的键盘处理。 卡片通过 CSS 锚点优先显示在标签上方;空间不足时会调整位置以避开窗口边缘,并在卡片内部滚动。
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## 模型体验
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-typert-protocol": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-shortcuts": "workspace:*"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { FactoryComponentPropsOf } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type {} from './contract/slots.ts'
|
||||
import css from './ApprovalPanel.module.css'
|
||||
import { ApprovalWarning } from './ApprovalWarning.tsx'
|
||||
|
||||
/**
|
||||
* Render one decision with caller-owned actions and an optional persistent choice.
|
||||
@@ -15,7 +16,7 @@ export function ApprovalCard(props: FactoryComponentPropsOf<'conversation.approv
|
||||
}
|
||||
|
||||
function ApprovalFlow(props: FactoryComponentPropsOf<'conversation.approval.card'>) {
|
||||
const { requestKey, title, description, subject, allowLabel, canAnswer, onAllow, onReject, onAlways, t } = props
|
||||
const { requestKey, title, description, subject, warning, allowLabel, canAnswer, onAllow, onReject, onAlways, t } = props
|
||||
const detail = props.callId === undefined ? null : props.renderSlot('conversation.approval.detail', { callId: props.callId })
|
||||
const [answered, setAnswered] = useState(false)
|
||||
const waiting = useRef(false)
|
||||
@@ -68,7 +69,10 @@ function ApprovalFlow(props: FactoryComponentPropsOf<'conversation.approval.card
|
||||
{subject.icon !== undefined && <img src={subject.icon} alt="" width={40} height={40} draggable={false} />}
|
||||
<strong>{subject.name}</strong>
|
||||
</div>}
|
||||
<div className={css.headline}>{title}</div>
|
||||
<div className={css.heading}>
|
||||
<div className={css.headline}>{title}</div>
|
||||
{warning !== undefined && <ApprovalWarning warning={warning} />}
|
||||
</div>
|
||||
{description !== undefined && <p className={css.description}>{description}</p>}
|
||||
{detail !== null && <div className={css.command}>{detail}</div>}
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,113 @@
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
}
|
||||
|
||||
.warningTrigger {
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.warningTag { gap: 4px; }
|
||||
|
||||
/* The tag supplies the fill; keep its larger button hit target transparent. */
|
||||
.heading .warningTrigger:active:not(:disabled) { background: transparent; }
|
||||
|
||||
@media (hover: hover) {
|
||||
.heading .warningTrigger:hover:not(:disabled) { background: transparent; }
|
||||
.warningTrigger:hover:not(:disabled) .warningTag {
|
||||
background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 18%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.warningTrigger:active:not(:disabled) .warningTag {
|
||||
background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 22%, transparent);
|
||||
}
|
||||
|
||||
.warningPopover {
|
||||
--dsh-warning-window-top: max(12px, var(--dsh-frame-overlay-top, 12px));
|
||||
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
||||
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
||||
border: 0;
|
||||
box-shadow: var(--dsw-elevation-panel);
|
||||
position: fixed;
|
||||
position-area: none;
|
||||
top: var(--dsh-warning-window-top);
|
||||
bottom: max(12px, calc(anchor(top) + 8px));
|
||||
left: 12px;
|
||||
right: max(12px, anchor(right));
|
||||
align-self: end;
|
||||
justify-self: end;
|
||||
margin: 0;
|
||||
position-try-fallbacks: --dsh-warning-wide-above, --dsh-warning-below, --dsh-warning-wide-below, --dsh-warning-window, --dsh-warning-wide-window;
|
||||
z-index: 1100;
|
||||
box-sizing: border-box;
|
||||
width: 420px;
|
||||
max-width: calc(100vw - 24px);
|
||||
height: fit-content;
|
||||
max-height: calc(100dvh - 12px - var(--dsh-warning-window-top));
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
@position-try --dsh-warning-wide-above {
|
||||
right: 12px;
|
||||
justify-self: safe end;
|
||||
}
|
||||
|
||||
@position-try --dsh-warning-below {
|
||||
top: max(var(--dsh-warning-window-top), calc(anchor(bottom) + 8px));
|
||||
bottom: 12px;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
@position-try --dsh-warning-wide-below {
|
||||
top: max(var(--dsh-warning-window-top), calc(anchor(bottom) + 8px));
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
align-self: start;
|
||||
justify-self: safe end;
|
||||
}
|
||||
|
||||
@position-try --dsh-warning-window {
|
||||
top: var(--dsh-warning-window-top);
|
||||
bottom: 12px;
|
||||
align-self: safe start;
|
||||
}
|
||||
|
||||
@position-try --dsh-warning-wide-window {
|
||||
top: var(--dsh-warning-window-top);
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
align-self: safe start;
|
||||
justify-self: safe end;
|
||||
}
|
||||
|
||||
.warningPopover p { margin: 16px 0 0; }
|
||||
|
||||
.warningPopover p + p:last-child {
|
||||
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
||||
padding-top: 16px;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.warningTitle {
|
||||
margin: 0;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.command {
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
font-family: var(--ds-font-family-code);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/** Formatted hover notice that keeps the pending approval unanswered. */
|
||||
import { useEffect, useId, useRef, useState, type FocusEvent, type PointerEvent } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import {
|
||||
Button, IconWarningOutlineRegular, MenuSurface, Tag,
|
||||
isBehindModal, observeComposition, useDismissOnOutsidePointer, usePointerGrace,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { ApprovalCardInput } from './contract/slots.ts'
|
||||
import css from './ApprovalPanel.module.css'
|
||||
|
||||
/**
|
||||
* Present caller-owned risks on hover, keyboard focus, or touch activation.
|
||||
* @param props Localized warning content.
|
||||
* @returns The warning tag and its readable, nonmodal hover card.
|
||||
*/
|
||||
export function ApprovalWarning({ warning }: {
|
||||
warning: NonNullable<ApprovalCardInput['warning']>
|
||||
}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const anchor = useRef<HTMLButtonElement>(null)
|
||||
const panel = useRef<HTMLDivElement>(null)
|
||||
const id = useId()
|
||||
const titleId = useId()
|
||||
const anchorName = `--dsh-approval-warning-${id.replaceAll(':', '')}`
|
||||
useDismissOnOutsidePointer(anchor, open, setOpen, panel)
|
||||
const { arm: armClose, cancel: cancelClose } = usePointerGrace(() => {
|
||||
if (anchor.current?.matches(':focus-visible')
|
||||
|| (panel.current?.contains(document.activeElement) && document.activeElement?.matches(':focus-visible'))) return
|
||||
setOpen(false)
|
||||
})
|
||||
const show = () => {
|
||||
if (isBehindModal(anchor.current)) return
|
||||
cancelClose()
|
||||
setOpen(true)
|
||||
}
|
||||
const enter = (event: PointerEvent<HTMLElement>) => {
|
||||
if (event.pointerType === 'touch' || !window.matchMedia('(any-hover: hover)').matches) return
|
||||
show()
|
||||
}
|
||||
const leave = (event: PointerEvent<HTMLElement>) => {
|
||||
if (event.pointerType !== 'touch') armClose()
|
||||
}
|
||||
const blur = (event: FocusEvent<HTMLElement>) => {
|
||||
const next = event.relatedTarget
|
||||
if (next instanceof Node && (anchor.current?.contains(next) || panel.current?.contains(next))) return
|
||||
cancelClose()
|
||||
setOpen(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
const composition = observeComposition(document)
|
||||
const close = () => { cancelClose(); setOpen(false) }
|
||||
const focusOutside = (event: Event) => {
|
||||
if (event.target instanceof Node && !anchor.current?.contains(event.target) && !panel.current?.contains(event.target)) close()
|
||||
}
|
||||
const visibilityChanged = () => { if (document.visibilityState === 'hidden') close() }
|
||||
const dismiss = (event: KeyboardEvent) => {
|
||||
if (composition.guards(event) || isBehindModal(anchor.current) || event.defaultPrevented
|
||||
|| event.ctrlKey || event.altKey || event.metaKey || event.shiftKey || event.key !== 'Escape') return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (event.repeat) return
|
||||
cancelClose()
|
||||
if (panel.current?.contains(document.activeElement)) anchor.current?.focus()
|
||||
setOpen(false)
|
||||
}
|
||||
document.addEventListener('keydown', dismiss, true)
|
||||
document.addEventListener('focusin', focusOutside)
|
||||
document.addEventListener('visibilitychange', visibilityChanged)
|
||||
window.addEventListener('blur', close)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', dismiss, true)
|
||||
document.removeEventListener('focusin', focusOutside)
|
||||
document.removeEventListener('visibilitychange', visibilityChanged)
|
||||
window.removeEventListener('blur', close)
|
||||
composition.dispose()
|
||||
}
|
||||
}, [open, cancelClose])
|
||||
return <>
|
||||
<Button ref={anchor} size="sm" className={css.warningTrigger} style={{ anchorName }} aria-haspopup="dialog"
|
||||
aria-expanded={open} aria-controls={open ? id : undefined} onClick={show}
|
||||
onPointerEnter={enter} onPointerLeave={leave}
|
||||
onFocus={(event) => { if (event.currentTarget.matches(':focus-visible')) show() }} onBlur={blur}>
|
||||
<Tag tone="warning" className={css.warningTag}>
|
||||
<IconWarningOutlineRegular size={12} />{warning.label}
|
||||
</Tag>
|
||||
</Button>
|
||||
{open && createPortal(
|
||||
<MenuSurface ref={panel} id={id} role="dialog" aria-labelledby={titleId} tabIndex={-1}
|
||||
className={css.warningPopover} style={{ positionAnchor: anchorName }}
|
||||
onPointerEnter={enter} onPointerLeave={leave} onFocus={cancelClose} onBlur={blur}>
|
||||
<h3 id={titleId} className={css.warningTitle}>{warning.title}</h3>
|
||||
{warning.detail.split('\n\n').map(paragraph => <p key={paragraph}>{paragraph}</p>)}
|
||||
</MenuSurface>, document.body,
|
||||
)}
|
||||
</>
|
||||
}
|
||||
@@ -211,6 +211,8 @@ export interface ApprovalCardInput {
|
||||
title: string
|
||||
/** Localized scope or consequence of the decision. */
|
||||
description?: string
|
||||
/** Caller-localized warning label and explanation, with paragraphs separated by blank lines (`\n\n`). */
|
||||
warning?: { label: string; title: string; detail: string }
|
||||
/** Subject metadata for human recognition; the caller admits icon URLs. Never an authorization identity. */
|
||||
subject?: { name: string; icon?: string }
|
||||
/** Optional tool-owned detail rendered through the existing child slot. */
|
||||
|
||||
@@ -6,6 +6,8 @@ import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ApprovalCard } from '../src/client/ApprovalCard.tsx'
|
||||
import { ApprovalWarning } from '../src/client/ApprovalWarning.tsx'
|
||||
import { Modal } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { FactoryComponentPropsOf } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { ApprovalPanel } from '../src/client/ApprovalPanel.tsx'
|
||||
import type { ApprovalCardInput, ApprovalComposerProps, ApprovalInjected } from '../src/client/contract/slots.ts'
|
||||
@@ -123,7 +125,9 @@ const id = (value: string): SessionId => value as SessionId
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('PendingApproval', () => {
|
||||
@@ -541,3 +545,181 @@ it.each(['allow', 'reject', 'always', 'keyboard'] as const)('settles a fixed app
|
||||
await waitFor(() => { expect(actions[action]).toHaveBeenCalledOnce() })
|
||||
expect(Object.values(actions).filter(callback => callback.mock.calls.length > 0)).toEqual([actions[action]])
|
||||
})
|
||||
|
||||
it('opens and dismisses risk details without answering the approval', () => {
|
||||
const onAllow = vi.fn(async () => {}), onReject = vi.fn(async () => {})
|
||||
const props = {
|
||||
requestKey: 'risk-approval', title: 'Control this app?',
|
||||
warning: { label: 'Risks', title: 'Computer use risks', detail: 'Background control.\n\nAccount restrictions.\n\nStop in DSH.' },
|
||||
canAnswer: () => true, onAllow, onReject, renderSlot: () => null, t: (key: string) => key,
|
||||
} as FactoryComponentPropsOf<'conversation.approval.card'>
|
||||
const view = render(<ApprovalCard {...props} />)
|
||||
const trigger = screen.getByRole('button', { name: 'Risks' })
|
||||
vi.spyOn(trigger, 'matches').mockImplementation(selector => selector === ':focus-visible')
|
||||
act(() => { trigger.focus() })
|
||||
const dialog = screen.getByRole('dialog', { name: 'Computer use risks' })
|
||||
expect([...dialog.querySelectorAll('p')].map(p => p.textContent).join('\n\n')).toBe(props.warning!.detail)
|
||||
expect(document.activeElement).toBe(trigger)
|
||||
expect(trigger.getAttribute('aria-expanded')).toBe('true')
|
||||
act(() => { trigger.focus() })
|
||||
fireEvent.keyDown(trigger, { key: 'Enter' })
|
||||
expect(onAllow).not.toHaveBeenCalled()
|
||||
fireEvent.keyDown(trigger, { key: 'Escape' })
|
||||
expect(screen.queryByRole('dialog', { name: 'Computer use risks' })).toBeNull()
|
||||
expect(onReject).not.toHaveBeenCalled()
|
||||
expect(document.activeElement).toBe(trigger)
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.pointerDown(document.body)
|
||||
expect(screen.queryByRole('dialog', { name: 'Computer use risks' })).toBeNull()
|
||||
fireEvent.click(trigger)
|
||||
view.rerender(<ApprovalCard {...props} requestKey="replacement" />)
|
||||
expect(screen.queryByRole('dialog', { name: 'Computer use risks' })).toBeNull()
|
||||
expect(onAllow).not.toHaveBeenCalled()
|
||||
expect(onReject).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps risk details readable across the pointer gap and after a touch tap', () => {
|
||||
vi.useFakeTimers()
|
||||
vi.stubGlobal('matchMedia', vi.fn(() => ({ matches: true, addEventListener() {}, removeEventListener() {} })))
|
||||
render(<ApprovalWarning warning={{ label: 'Risks', title: 'Computer use risks', detail: 'Background control.' }} />)
|
||||
const trigger = screen.getByRole('button', { name: 'Risks' })
|
||||
const pointer = (target: Element, type: string, pointerType = 'mouse') => {
|
||||
const event = new Event(type, { bubbles: true })
|
||||
Object.defineProperty(event, 'pointerType', { value: pointerType })
|
||||
fireEvent(target, event)
|
||||
}
|
||||
pointer(trigger, 'pointerover')
|
||||
const dialog = screen.getByRole('dialog', { name: 'Computer use risks' })
|
||||
expect(document.activeElement).toBe(document.body)
|
||||
pointer(trigger, 'pointerout')
|
||||
act(() => { vi.advanceTimersByTime(100) })
|
||||
pointer(dialog, 'pointerover')
|
||||
act(() => { vi.advanceTimersByTime(200) })
|
||||
expect(screen.getByRole('dialog', { name: 'Computer use risks' })).toBe(dialog)
|
||||
pointer(dialog, 'pointerout')
|
||||
act(() => { vi.advanceTimersByTime(200) })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
pointer(trigger, 'pointerover', 'touch')
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
fireEvent.click(trigger)
|
||||
pointer(trigger, 'pointerout', 'touch')
|
||||
act(() => { vi.advanceTimersByTime(200) })
|
||||
expect(screen.getByRole('dialog', { name: 'Computer use risks' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('leaves Escape to composition and dismisses risk details when a modal takes focus', () => {
|
||||
const onClose = vi.fn()
|
||||
const warning = <ApprovalWarning warning={{ label: 'Risks', title: 'Computer use risks', detail: 'Background control.' }} />
|
||||
const view = render(warning)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Risks' }))
|
||||
fireEvent.compositionStart(document)
|
||||
fireEvent.keyDown(document, { key: 'Escape' })
|
||||
expect(screen.getByRole('dialog', { name: 'Computer use risks' })).toBeTruthy()
|
||||
fireEvent.compositionEnd(document)
|
||||
fireEvent.keyUp(document, { key: 'Escape' })
|
||||
fireEvent.keyDown(document, { key: 'Escape', shiftKey: true })
|
||||
expect(screen.getByRole('dialog', { name: 'Computer use risks' })).toBeTruthy()
|
||||
view.rerender(<>{warning}<Modal open title="Foreground" closeLabel="Close modal" onClose={onClose} /></>)
|
||||
const modal = screen.getByRole('dialog', { name: 'Foreground' })
|
||||
expect(screen.queryByRole('dialog', { name: 'Computer use risks' })).toBeNull()
|
||||
act(() => { modal.focus() })
|
||||
fireEvent.keyDown(modal, { key: 'Escape' })
|
||||
expect(onClose).toHaveBeenCalledOnce()
|
||||
expect(document.activeElement).toBe(modal)
|
||||
expect(screen.queryByRole('dialog', { name: 'Computer use risks' })).toBeNull()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Risks' }))
|
||||
expect(screen.queryByRole('dialog', { name: 'Computer use risks' })).toBeNull()
|
||||
expect(document.activeElement).toBe(modal)
|
||||
})
|
||||
|
||||
it('keeps risk details open for keyboard focus and closes when focus leaves', () => {
|
||||
vi.useFakeTimers()
|
||||
render(<>
|
||||
<ApprovalWarning warning={{ label: 'Risks', title: 'Computer use risks', detail: 'Background control.' }} />
|
||||
<button>Outside</button>
|
||||
</>)
|
||||
const trigger = screen.getByRole('button', { name: 'Risks' })
|
||||
const focusVisible = vi.spyOn(trigger, 'matches').mockReturnValue(false)
|
||||
act(() => { trigger.focus() })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
fireEvent.click(trigger)
|
||||
const dialog = screen.getByRole('dialog', { name: 'Computer use risks' })
|
||||
vi.spyOn(dialog, 'matches').mockImplementation(selector => selector === ':focus-visible')
|
||||
act(() => { dialog.focus() })
|
||||
fireEvent.pointerLeave(dialog)
|
||||
act(() => { vi.advanceTimersByTime(200) })
|
||||
expect(screen.getByRole('dialog')).toBe(dialog)
|
||||
|
||||
focusVisible.mockImplementation(selector => selector === ':focus-visible')
|
||||
act(() => { trigger.focus() })
|
||||
fireEvent.pointerLeave(trigger)
|
||||
act(() => { vi.advanceTimersByTime(200) })
|
||||
expect(screen.getByRole('dialog')).toBe(dialog)
|
||||
act(() => { screen.getByRole('button', { name: 'Outside' }).focus() })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
|
||||
fireEvent.click(trigger)
|
||||
act(() => { screen.getByRole('dialog').focus() })
|
||||
act(() => { screen.getByRole('dialog').blur() })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
})
|
||||
|
||||
it('dismisses pointer-focused risk details when the pointer leaves', () => {
|
||||
vi.useFakeTimers()
|
||||
render(<ApprovalWarning warning={{ label: 'Risks', title: 'Computer use risks', detail: 'Background control.' }} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Risks' }))
|
||||
const dialog = screen.getByRole('dialog', { name: 'Computer use risks' })
|
||||
vi.spyOn(dialog, 'matches').mockReturnValue(false)
|
||||
act(() => { dialog.focus() })
|
||||
fireEvent.pointerLeave(dialog)
|
||||
act(() => { vi.advanceTimersByTime(200) })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
})
|
||||
|
||||
it('ignores repeated Escape and restores focus when dismissing focused risk details', () => {
|
||||
render(<ApprovalWarning warning={{ label: 'Risks', title: 'Computer use risks', detail: 'Background control.' }} />)
|
||||
const trigger = screen.getByRole('button', { name: 'Risks' })
|
||||
fireEvent.click(trigger)
|
||||
const dialog = screen.getByRole('dialog', { name: 'Computer use risks' })
|
||||
act(() => { dialog.focus() })
|
||||
fireEvent.keyDown(dialog, { key: 'Escape', repeat: true })
|
||||
expect(screen.getByRole('dialog')).toBe(dialog)
|
||||
expect(document.activeElement).toBe(dialog)
|
||||
fireEvent.keyDown(dialog, { key: 'Escape' })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
expect(document.activeElement).toBe(trigger)
|
||||
})
|
||||
|
||||
it('dismisses hover-open risks when focus moves elsewhere without taking focus on open', () => {
|
||||
render(<>
|
||||
<button>Before</button>
|
||||
<ApprovalWarning warning={{ label: 'Risks', title: 'Computer use risks', detail: 'Background control.' }} />
|
||||
<button>After</button>
|
||||
</>)
|
||||
const before = screen.getByRole('button', { name: 'Before' })
|
||||
const after = screen.getByRole('button', { name: 'After' })
|
||||
act(() => { before.focus() })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Risks' }))
|
||||
expect(document.activeElement).toBe(before)
|
||||
expect(screen.getByRole('dialog', { name: 'Computer use risks' })).toBeTruthy()
|
||||
act(() => { after.focus() })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
expect(document.activeElement).toBe(after)
|
||||
})
|
||||
|
||||
it.each(['window blur', 'hidden tab'] as const)('dismisses risk details on %s without restoring focus', (reason) => {
|
||||
render(<ApprovalWarning warning={{ label: 'Risks', title: 'Computer use risks', detail: 'Background control.' }} />)
|
||||
const trigger = screen.getByRole('button', { name: 'Risks' })
|
||||
fireEvent.click(trigger)
|
||||
fireEvent(document, new Event('visibilitychange'))
|
||||
expect(screen.getByRole('dialog')).toBeTruthy()
|
||||
if (reason === 'window blur') fireEvent(window, new Event('blur'))
|
||||
else {
|
||||
vi.spyOn(document, 'visibilityState', 'get').mockReturnValue('hidden')
|
||||
fireEvent(document, new Event('visibilitychange'))
|
||||
}
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
expect(document.activeElement).toBe(document.body)
|
||||
fireEvent(window, new Event('focus'))
|
||||
expect(trigger.getAttribute('aria-expanded')).toBe('false')
|
||||
})
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
en: 9386791a9e5efa00
|
||||
zh: 7cb9f3bbc42319d6
|
||||
/deepseek-ai-dsh-client-ui-chat/hidden-chat-rows:
|
||||
en: 91b7c7a5263eda35
|
||||
zh: 16d39b45edee038f
|
||||
en: 8ca277dc7fed699f
|
||||
zh: 167478dba50f3904
|
||||
/deepseek-ai-dsh-client-ui-chat/command-and-failure-rows:
|
||||
en: c5b23d0ed29a0425
|
||||
zh: 689bab2c8253294a
|
||||
|
||||
@@ -54,7 +54,7 @@ Settings → General → Open chat links in selects the destination for ordinary
|
||||
<a id="system-prompt-row"></a>
|
||||
## Hidden Chat rows
|
||||
|
||||
Chat omits system-prompt, ordinary Context injection, and `permission` command rows in every work-details mode. It also hides pending and successful `/team on` and `/team off` receipts, identified by their recorded name and trimmed arguments; failures and commands with missing or unrecognized arguments remain visible. Context containing tool additions or removals remains visible. The filter changes neither recorded Session events nor Trajectory inspection and excludes hidden receipts from flow spacing and process summaries. Non-human Turn triggers remain independent, bordered notices with a contrasting neutral background; other command rows remain in Chat.
|
||||
Chat omits system-prompt and `permission` command rows in every work-details mode. It also hides pending and successful `/team on` and `/team off` receipts, identified by their recorded name and trimmed arguments; failures and commands with missing or unrecognized arguments remain visible. Context injections remain visible as collapsed rows that use the tool-call disclosure row; within a Turn they join its process group like tool calls. The filter changes neither recorded Session events nor Trajectory inspection and excludes hidden receipts from flow spacing and process summaries. Non-human Turn triggers remain independent, bordered notices with a contrasting neutral background; other command rows remain in Chat.
|
||||
|
||||
When an Assistant attempt retires without a visible message, Chat hides its already-published Node instead of removing its key. A retry in the same Step reuses that key when visible content returns. This also applies when the loaded window lacks the Step start.
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ Session 作用域的 `conversation.chat.user.content` Factory 只渲染用户内
|
||||
<a id="system-prompt-row"></a>
|
||||
## Chat 隐藏的行
|
||||
|
||||
Chat 在所有工作步骤展示模式下都不显示系统提示词行、普通上下文注入和 `permission` 命令行。记录中的命令名和去除首尾空白后的参数确定为 `/team on` 或 `/team off` 时,也隐藏执行中与成功回执;失败以及参数缺失或无法识别的命令仍然可见。包含工具添加或移除记录的上下文仍然可见。该过滤不改变已记录的 Session 事件或 Trajectory 查看能力,隐藏回执也不计入行间距和过程摘要。非人工轮次触发仍作为独立通知显示,其他命令行仍保留在 Chat 中。
|
||||
Chat 在所有工作步骤展示模式下都不显示系统提示词行和 `permission` 命令行。记录中的命令名和去除首尾空白后的参数确定为 `/team on` 或 `/team off` 时,也隐藏执行中与成功回执;失败以及参数缺失或无法识别的命令仍然可见。上下文注入以默认收起、与工具调用相同的展开行显示;在 Turn 内与工具调用一样加入过程组。该过滤不改变已记录的 Session 事件或 Trajectory 查看能力,隐藏回执也不计入行间距和过程摘要。非人工轮次触发仍作为独立通知显示,其他命令行仍保留在 Chat 中。
|
||||
|
||||
Assistant 尝试结束且没有可见消息时,Chat 隐藏已发布的 Node,不移除其 key。同一 Step 的重试再次产生可见内容时,复用该 key。已加载窗口缺少 Step 起点时也遵循此规则。
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { memo, useCallback, useEffect, useId, useRef, useState, type ComponentProps, type ReactNode } from 'react'
|
||||
import {
|
||||
IconAgentPresetOutlineRegular, IconApiOutlineRegular, IconBrowseOutlineRegular, IconChevronDownOutlineRegular,
|
||||
IconChevronUpOutlineRegular, IconCodeOutlineRegular, IconEditOutlineRegular, IconGlobeOutlineRegular,
|
||||
IconPlanOutlineRegular, IconQuestionOutlineRegular, IconSearchOutlineRegular, IconSparkleRegular,
|
||||
IconChevronUpOutlineRegular, IconCodeOutlineRegular, IconContextInjectionOutlineRegular, IconEditOutlineRegular,
|
||||
IconGlobeOutlineRegular, IconPlanOutlineRegular, IconQuestionOutlineRegular, IconSearchOutlineRegular, IconSparkleRegular,
|
||||
IconThinkOutlineRegular, TextShimmer,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { GroupKey, NodeReference } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import type { ProcessActivity } from '../contract/process-groups.ts'
|
||||
import type { ProcessActivity, RunningProcessActivity } from '../contract/process-groups.ts'
|
||||
import type { ChatStoreState } from '../contract/store.ts'
|
||||
import type { ChatFlowSlotProps } from '../contract/slots.ts'
|
||||
import { turnProcessOpen } from '../stores.ts'
|
||||
@@ -30,7 +30,7 @@ const PROCESS_TITLE_MINIMUM_MS = 150
|
||||
type ProcessTitleActivity = ProcessActivity | 'thinking'
|
||||
|
||||
interface LiveProcessTitle {
|
||||
readonly activity: ProcessTitleActivity
|
||||
readonly activity: RunningProcessActivity | 'thinking'
|
||||
readonly detail: string
|
||||
readonly preparing: boolean
|
||||
}
|
||||
@@ -50,6 +50,7 @@ const PROCESS_ICONS: Record<ProcessTitleActivity, ReactNode> = {
|
||||
plan: <IconPlanOutlineRegular />,
|
||||
questions: <IconQuestionOutlineRegular />,
|
||||
tools: <IconSparkleRegular size={14} />,
|
||||
context: <IconContextInjectionOutlineRegular size={14} />,
|
||||
}
|
||||
|
||||
function sameLiveProcessTitle(left: LiveProcessTitle, right: LiveProcessTitle): boolean {
|
||||
|
||||
@@ -26,13 +26,17 @@
|
||||
overflow-y: auto;
|
||||
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
||||
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
||||
position-area: top span-right;
|
||||
position-try-fallbacks: --dsh-stat-window, flip-block, --dsh-stat-viewport;
|
||||
position-area: none;
|
||||
top: max(12px, var(--dsh-frame-overlay-top, 12px));
|
||||
bottom: max(12px, calc(anchor(top) + 8px));
|
||||
left: max(12px, anchor(left));
|
||||
right: 12px;
|
||||
position-try-fallbacks: --dsh-stat-window, --dsh-stat-opposite, --dsh-stat-wide-opposite, --dsh-stat-tall, --dsh-stat-viewport;
|
||||
align-self: end;
|
||||
justify-self: start;
|
||||
height: fit-content;
|
||||
min-height: 0;
|
||||
margin: 8px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Dialog heading row: the section name left, the section's headline value
|
||||
@@ -110,10 +114,36 @@
|
||||
justify-self: safe end;
|
||||
}
|
||||
|
||||
@position-try --dsh-stat-opposite {
|
||||
top: max(12px, calc(anchor(bottom) + 8px));
|
||||
bottom: 12px;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
@position-try --dsh-stat-wide-opposite {
|
||||
top: max(12px, calc(anchor(bottom) + 8px));
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
align-self: start;
|
||||
justify-self: safe end;
|
||||
}
|
||||
|
||||
@position-try --dsh-stat-tall {
|
||||
position-area: none;
|
||||
top: max(12px, var(--dsh-frame-overlay-top, 12px));
|
||||
bottom: 12px;
|
||||
left: max(12px, anchor(left));
|
||||
right: 12px;
|
||||
margin: 0;
|
||||
align-self: safe start;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
@position-try --dsh-stat-viewport {
|
||||
position-area: none;
|
||||
inset: max(12px, var(--dsh-frame-overlay-top, 12px)) 12px 12px;
|
||||
margin: 0;
|
||||
align-self: safe start;
|
||||
justify-self: safe start;
|
||||
justify-self: safe end;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import type { ChatNode } from './chat-nodes.ts'
|
||||
|
||||
/**
|
||||
* Exclude system prompts, ordinary Context, permission commands, and normal Team toggle receipts.
|
||||
* Failed Team toggles and Context containing tool changes remain visible.
|
||||
* Exclude system prompts, permission commands, and normal Team toggle receipts.
|
||||
* Failed Team toggles remain visible.
|
||||
* @param node - projected Chat node.
|
||||
* @returns whether the node contributes a visible Chat row.
|
||||
*/
|
||||
@@ -17,6 +17,4 @@ export function isVisibleChatNode(node: ChatNode): boolean {
|
||||
}
|
||||
return node.visibility === 'visible'
|
||||
&& node.kind !== 'system-prompt'
|
||||
&& (node.kind !== 'context'
|
||||
|| node.data.content.some(block => block.type === 'tool-addition' || block.type === 'tool-removal'))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
/** Business data for Chat process groups; source Nodes remain in the Chat store. */
|
||||
export type ProcessActivity = 'read' | 'readImage' | 'search' | 'write' | 'edit' | 'commands' | 'code'
|
||||
| 'webSearch' | 'webFetch' | 'subagents' | 'plan' | 'questions' | 'tools'
|
||||
|
||||
/** Distinct-call category ranking and the current live task detail. */
|
||||
/** Process-group category ranked by distinct calls; `context` names a group whose members are all Context. */
|
||||
export type ProcessActivity = 'read' | 'readImage' | 'search' | 'write' | 'edit' | 'commands' | 'code'
|
||||
| 'webSearch' | 'webFetch' | 'subagents' | 'plan' | 'questions' | 'tools' | 'context'
|
||||
|
||||
/** Categories a live tool call can report. */
|
||||
export type RunningProcessActivity = Exclude<ProcessActivity, 'context'>
|
||||
|
||||
/** Category ranking by distinct calls, or by member count for an all-Context group, and the current live task detail. */
|
||||
export interface ProcessActivitySummary {
|
||||
readonly counts: readonly { readonly kind: ProcessActivity; readonly count: number }[]
|
||||
readonly running: ProcessActivity | undefined
|
||||
readonly running: RunningProcessActivity | undefined
|
||||
readonly runningDetail: string
|
||||
/** Present only while the selected live activity has not reached tool/call. */
|
||||
readonly preparing?: true
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 75564701ecd3d1bd
|
||||
zh: 9c042288a15d441d
|
||||
/chat-conversation-node-rules/whole-turn-folding:
|
||||
en: 5e008ac2ab8de86a
|
||||
zh: d782e34c7cfcb0f2
|
||||
en: d5eac08a4bc94c10
|
||||
zh: ee2167d1fc823f21
|
||||
/chat-conversation-node-rules/display-modes:
|
||||
en: ada5a60d2c46f6a8
|
||||
zh: f3eaeb5cb4d9b3de
|
||||
@@ -39,11 +39,11 @@
|
||||
en: 8e775c43878ae31a
|
||||
zh: 4c1063c0071ebdbd
|
||||
/chat-conversation-node-rules/display-modes/group-title-rules:
|
||||
en: f78fb52d4733e6ce
|
||||
zh: 119e5319b23762ba
|
||||
en: eefdd59673e8c0fb
|
||||
zh: 309384c5c995b80a
|
||||
/chat-conversation-node-rules/process-grouping:
|
||||
en: 3d28b3bb8757bf55
|
||||
zh: 3b0f451b48db6048
|
||||
en: 4c581ba07e633619
|
||||
zh: d3ea678ba42a496a
|
||||
/chat-conversation-node-rules/process-grouping/examples:
|
||||
en: 2a550f7e04bce4b0
|
||||
zh: b8f95b687448cd77
|
||||
|
||||
@@ -80,7 +80,7 @@ The Turn control follows all its opening inputs, including human steering and no
|
||||
| Human message claimed from `next-step` | Steering message, outside process folding. When Chat orders the claim's first human message after the Turn control, that message keeps the Turn tail slot above it; the claim's later messages follow at the ordinary input gap. |
|
||||
| Non-human message in the current `next-turn` claim | Independent, initially collapsed Turn-trigger notice. |
|
||||
| Non-human message in an idle `next-step` claim | Turn-trigger notice only in Step 1, with a loaded Turn start, a claim after that start, no `next-turn` claim in this Turn, and no human in the same `next-step` batch. |
|
||||
| Other non-human input | Ordinary Context, retained in the Node Store but omitted from Chat. Unclaimed, canceled, or requeued messages do not establish a waking claim. |
|
||||
| Other non-human input | Ordinary Context, shown as a collapsed row that joins the process group like a tool call. Unclaimed, canceled, or requeued messages do not establish a waking claim. |
|
||||
|
||||
Trigger titles and icons use the recorded `source.kind`: `schedule`, `tool-jobs`, and `cordis-host-runner` identify scheduled work, background work, and plugin updates. Schedule notices are labeled Automation task and use the same plain clock icon as the Automation tasks entry. Goal, agent, team, subagent, and webhook sources have their own titles; a webhook with `provider: github` uses the GitHub title. Unknown sources use the generic execution-request title. Expanding the notice shows its recorded body; it does not imply successful execution.
|
||||
|
||||
@@ -174,6 +174,7 @@ The labels below describe recorded activity, not successful outcomes. For exampl
|
||||
| `plan` | Updating the plan | Updated the plan |
|
||||
| `questions` | Waiting for your action | Asked questions |
|
||||
| `tools` | Calling tools | Called tools |
|
||||
| `context` (every member is Context) | Analyzing the request | Injected context |
|
||||
|
||||
| Group state | Title composition |
|
||||
|---|---|
|
||||
@@ -213,7 +214,7 @@ Grouping reads each Node's [group-tree position](../contract/group-tree.ts), whi
|
||||
| Node at `process` or an outer position: `model-retry`, Turn inputs, `turn-error`, `turn-max-tokens`, `turn-tail`, and the `turn-process` control | End the preceding group and retain the Node as an independent root. |
|
||||
| Other visible Nodes owned by a Turn without a position, including tools | Append the whole Node to the current group, creating a group if necessary. |
|
||||
| Node from another Turn or without a Turn | Break the same-Turn sequence. A Node without a Turn remains independent, including an unsplit Assistant. |
|
||||
| Hidden Nodes, system prompts, ordinary Context injection, and `permission` commands | Excluded from the grouping input; they neither join nor split a group. |
|
||||
| Hidden Nodes, system prompts, and `permission` commands | Excluded from the grouping input; they neither join nor split a group. |
|
||||
|
||||
A reply contains non-blank text, an image, or another visible block; reasoning and tool-call protocol blocks do not count as a reply. An Assistant with neither non-blank reasoning nor reply content contributes no reference. Group creation requires a member, so metadata-only Turns create no empty group.
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ description: "Chat 过程分组与活动摘要的明细业务规则。"
|
||||
| 从 `next-step` 领取的人工消息 | Steering 消息,不参与过程折叠。若 Chat 将该次领取的第一条人工消息排在整轮控件之后,它上方保留轮次尾部槽位;同一次领取的后续消息按普通输入间距跟随。 |
|
||||
| 当前 `next-turn` 领取批次中的非人工消息 | 独立、默认收起的轮次触发通知。 |
|
||||
| 空闲时从 `next-step` 领取的非人工消息 | 仅在 Step 1、已加载轮次起点、领取发生于起点之后、本轮没有 `next-turn` 领取、同一 `next-step` 批次没有人工消息时,作为轮次触发通知。 |
|
||||
| 其他非人工输入 | 普通 Context,保留在 Node Store,但不显示在 Chat 中。未领取、已取消或重新入队的消息不构成唤醒领取。 |
|
||||
| 其他非人工输入 | 普通 Context,以默认收起的行显示,并像工具调用一样加入过程组。未领取、已取消或重新入队的消息不构成唤醒领取。 |
|
||||
|
||||
触发通知的标题与图标使用记录中的 `source.kind`:`schedule`、`tool-jobs`、`cordis-host-runner` 分别表示定时任务、后台任务和插件更新。定时任务通知显示为「自动化任务」,与「自动化任务」入口使用同一个普通时钟图标。目标、Agent、团队、子任务和 webhook 来源有各自的标题;`provider: github` 的 webhook 使用 GitHub 标题。未知来源显示通用执行请求标题。展开通知显示记录的正文,不代表执行成功。
|
||||
|
||||
@@ -174,6 +174,7 @@ description: "Chat 过程分组与活动摘要的明细业务规则。"
|
||||
| `plan` | 正在更新计划 | 更新了计划 |
|
||||
| `questions` | 等待你的操作 | 向用户提出了问题 |
|
||||
| `tools` | 正在调用工具 | 已调用工具 |
|
||||
| `context`(所有成员都是 Context) | 正在分析请求 | 已注入上下文 |
|
||||
|
||||
| 组状态 | 标题组合规则 |
|
||||
|---|---|
|
||||
@@ -213,7 +214,7 @@ description: "Chat 过程分组与活动摘要的明细业务规则。"
|
||||
| 位于 `process` 或更外层位置的 Node:`model-retry`、轮次输入、`turn-error`、`turn-max-tokens`、`turn-tail` 及 `turn-process` 控件 | 结束前面的组,Node 作为独立根引用保留。 |
|
||||
| 轮次内没有位置的其他可见 Node,包括工具 | 将整个 Node 加入当前组,必要时创建组。 |
|
||||
| 另一轮次或没有轮次归属的 Node | 截断同轮次序列。无轮次 Node 保持独立,Assistant 也不拆分。 |
|
||||
| 隐藏 Node、系统提示词、普通 Context 注入及 `permission` 命令 | 不进入分组输入,既不入组,也不截断组。 |
|
||||
| 隐藏 Node、系统提示词及 `permission` 命令 | 不进入分组输入,既不入组,也不截断组。 |
|
||||
|
||||
回复须包含非空白文本、图片或其他可见块;推理和工具调用协议块不算回复。既无非空白推理、也无回复内容的 Assistant 不产生引用。只有存在成员才创建组,因此仅含元数据的轮次不会产生空组。
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/** Tool-category and live-detail interpretation owned by Chat grouping. */
|
||||
import { reasoningText } from '../reasoning-text.ts'
|
||||
import type { ToolArgs } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import type { ProcessActivity, ProcessActivitySummary } from '../contract/process-groups.ts'
|
||||
import type { ProcessActivity, ProcessActivitySummary, RunningProcessActivity } from '../contract/process-groups.ts'
|
||||
import type { ChatNode } from '../contract/chat-nodes.ts'
|
||||
import { isRunningTool } from '../contract/chat-nodes.ts'
|
||||
import type { ToolCallBlock } from '../contract/snapshot.ts'
|
||||
|
||||
function activity(name: string): ProcessActivity {
|
||||
function activity(name: string): RunningProcessActivity {
|
||||
if (name === 'read') return 'read'
|
||||
if (name === 'read_image') return 'readImage'
|
||||
if (name === 'grep' || name === 'glob' || name.endsWith('_inspect')) return 'search'
|
||||
@@ -129,13 +129,14 @@ function liveToolDetail(name: string, args: ToolArgs): string {
|
||||
|
||||
/**
|
||||
* Rank categories by distinct call count, breaking ties by first appearance.
|
||||
* A group whose members are all Context reports one `context` category counting those members; Context beside other members adds no count.
|
||||
* @param nodes - process members, including recursive tools.
|
||||
* @returns all ranked categories and the latest running tool category and bounded task detail.
|
||||
*/
|
||||
export function processActivity(nodes: readonly ChatNode[]): ProcessActivitySummary {
|
||||
const counts = new Map<ProcessActivity, number>()
|
||||
const seen = new Set<string>()
|
||||
let running: ProcessActivity | undefined
|
||||
let running: RunningProcessActivity | undefined
|
||||
let runningDetail = ''
|
||||
let runningTime = -Infinity
|
||||
let preparing: boolean | undefined
|
||||
@@ -159,6 +160,7 @@ export function processActivity(nodes: readonly ChatNode[]): ProcessActivitySumm
|
||||
if (node.kind === 'tool-call') visit(node.data.root)
|
||||
}
|
||||
if (running === undefined) runningDetail = liveReasoningDetail(nodes)
|
||||
if (nodes.length > 0 && nodes.every(node => node.kind === 'context')) counts.set('context', nodes.length)
|
||||
return {
|
||||
counts: [...counts].map(([kind, count]) => ({ kind, count })).sort((a, b) => b.count - a.count),
|
||||
running,
|
||||
|
||||
@@ -66,4 +66,4 @@ type PublicChatNodeDataMap = ChatNodeDataMap
|
||||
declare module './contract/chat-nodes.ts' {
|
||||
interface ChatNodeDataMap extends PublicChatNodeDataMap {}
|
||||
}
|
||||
export type { ProcessActivity, ProcessActivitySummary, ProcessGroupData } from './contract/process-groups.ts'
|
||||
export type { ProcessActivity, ProcessActivitySummary, ProcessGroupData, RunningProcessActivity } from './contract/process-groups.ts'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user