fix(client): address tool presentation review
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md
|
||||
2026-07-19-gui-web-client-architecture.md: 4dc4558ea245baa17646f92b9b5e4c9a45b6a419
|
||||
2026-07-19-gui-web-client-architecture.zh.md: a306b5c82891840b96340f5464267cc9d861ef7e
|
||||
2026-07-19-gui-web-client-architecture.md: cba029ea74e89b277f9079ebb3765deeeb105b47
|
||||
2026-07-19-gui-web-client-architecture.zh.md: f700cc04fe1479d43957456c14b3abb4014d99c5
|
||||
@@ -44,7 +44,7 @@ Implementation homes: registry core and the props-share types in `packages/clien
|
||||
|
||||
A service is a plugin's only API surface toward other plugins (UI components and injection faces are not APIs; a plugin nobody calls mounts no service — ui-trajectory is the minimal-plugin exemplar: no ctx service, only view-slot registrations). The roster: `ctx.connection` (api client + stream handles), `ctx.slots` (registry wrapper emitting `slots/changed`, render entry, renderer install seam), `ctx.sessions` (list store, current-session state, scope tree), `ctx.loader`, `ctx.theme`, `ctx.i18n`, `ctx.layout` (cross-plugin view navigation), `ctx.conversation` (send/cancel/startSession). Viewing state that used to live in service stores (panel widths, selection, drafts) now lives in entry-declared stores per the [slot system standard](2026-07-22-slot-type-chain-implementation.md).
|
||||
|
||||
There is no registration model besides slots — the former view and tool rings both dissolved into it. Conversation views are entries of the `'conversation.view'` list slot ui-conversation declares, tab metadata rides the registration options (`id`/`order`/`label`), and per-view chrome lives inside the view components themselves. Tool presentation crosses one explicit package boundary: ui-conversation places each ordered root call into the single `'conversation.chat.tool'` seat and passes the Runtime-projected Code Dispatch children without interpreting their Tool names; ui-tool renders that root/child shape and declares the keyed/session `'tool.call.toolview'` child slot. The key space stays runtime-open (SlotMap declares slots, never keys), and both roots and children dispatch by `entryKey: toolName` with `GenericToolCard` as the fallback. Business packages register atomic views through `ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({ name: 'tool.call.toolview', key: '<tool>', inject? }, Row))`; the declaration is the load and reload dependency ([decision](2026-08-05-slot-declaration-injection.md)). ui-conversation separately delegates the selected call's details body through `'conversation.details.tool'`, so ui-tool's card models remain the single presentation owner without making conversation import Tool components.
|
||||
There is no registration model besides slots — the former view and tool rings both dissolved into it. Conversation views are entries of the `'conversation.view'` list slot ui-conversation declares, tab metadata rides the registration options (`id`/`order`/`label`), and per-view chrome lives inside the view components themselves. Tool presentation crosses one explicit package boundary: ui-conversation places each ordered root call into the single `'conversation.chat.tool'` seat without interpreting Tool names or Code Dispatch topology; ui-tool selects `codeDispatches[rootCallId]` from the Runtime snapshot, renders that root/child shape, and declares the keyed/session `'tool.call.toolview'` child slot. The key space stays runtime-open (SlotMap declares slots, never keys), and both roots and children dispatch by `entryKey: toolName` with `GenericToolCard` as the fallback. Business packages register atomic views through `ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({ name: 'tool.call.toolview', key: '<tool>', inject? }, Row))`; the declaration is the load and reload dependency ([decision](2026-08-05-slot-declaration-injection.md)). ui-conversation separately delegates the selected call's details body through `'conversation.details.tool'`, so ui-tool's card models remain the single presentation owner without making conversation import Tool components.
|
||||
|
||||
**Scope addressing** mirrors the host's agent-scope idiom: services are root singletons whose methods take no sessionId — they read the caller's scope mark (`scopeOf(ctx)`). Inside a session scope, `ctx.conversation.send('hi', 'queue')` targets that session; cross-session calls re-target by switching ctx (`ctx.sessions.scope(id)!.conversation.send(...)`); calling a scoped method from root ctx throws. Client session scopes are minted like host agent scopes (a no-op plugin fiber + a scope-key extend), built lazily on first viewing and torn down only when the session is removed and unwatched — host-session death alone does not tear a scope (it freezes into a read-only viewport).
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ slot 体系有自己的 RFC——[slot 体系标准](2026-07-22-slot-type-chain-
|
||||
|
||||
服务是插件对其他插件的唯一 API 面(UI 组件与注入面都不是 API;无人调用的插件不挂服务——ui-trajectory 即最小插件样板:无 ctx 服务,只做视图坑注册)。名册:`ctx.connection`(api client + 流句柄)、`ctx.slots`(注册表包装层,发 `slots/changed`,渲染入口,渲染器安装缝)、`ctx.sessions`(列表 store、当前会话状态、scope 树)、`ctx.loader`、`ctx.theme`、`ctx.i18n`、`ctx.layout`(跨插件视图导航)、`ctx.conversation`(send/cancel/startSession)。过去住在服务 store 里的观看态(面板宽、选中、草稿)现按 [slot 体系标准](2026-07-22-slot-type-chain-implementation.md) 住 entry 声明的 store。
|
||||
|
||||
slot 之外不存在第二种注册模型——原视图环与工具环都已溶解进来。会话视图即 ui-conversation 声明的 `'conversation.view'` list 坑的 entry,tab 元数据随注册 options(`id`/`order`/`label`)走,per-view chrome 住视图组件自身。Tool 展示跨越一条显式包边界:ui-conversation 把每个已排序 root call 放进 single `'conversation.chat.tool'` seat,并透传 Runtime 已投影的 Code Dispatch child,不解释其 Tool 名称;ui-tool 渲染该 root/child 形状,并声明 keyed/session 的 `'tool.call.toolview'` 子 slot。key 空间仍在运行时开放(SlotMap 声明 slot、从不声明 key),root 与 child 都按 `entryKey: toolName` 分发,以 `GenericToolCard` 兜底。业务包通过 `ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({ name: 'tool.call.toolview', key: '<tool>', inject? }, Row))` 注册原子视图;声明本身就是加载与重载依赖([决策](2026-08-05-slot-declaration-injection.md))。ui-conversation 还通过 `'conversation.details.tool'` 委托选中调用的详情正文,使 ui-tool 的 card model 保持为唯一展示所有者,同时避免 conversation 导入 Tool 组件。
|
||||
slot 之外不存在第二种注册模型——原视图环与工具环都已溶解进来。会话视图即 ui-conversation 声明的 `'conversation.view'` list 坑的 entry,tab 元数据随注册 options(`id`/`order`/`label`)走,per-view chrome 住视图组件自身。Tool 展示跨越一条显式包边界:ui-conversation 把每个已排序 root call 放进 single `'conversation.chat.tool'` seat,不解释 Tool 名称或 Code Dispatch 拓扑;ui-tool 从 Runtime snapshot 选择 `codeDispatches[rootCallId]`、渲染 root/child 形状,并声明 keyed/session 的 `'tool.call.toolview'` 子 slot。key 空间仍在运行时开放(SlotMap 声明 slot、从不声明 key),root 与 child 都按 `entryKey: toolName` 分发,以 `GenericToolCard` 兜底。业务包通过 `ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({ name: 'tool.call.toolview', key: '<tool>', inject? }, Row))` 注册原子视图;声明本身就是加载与重载依赖([决策](2026-08-05-slot-declaration-injection.md))。ui-conversation 还通过 `'conversation.details.tool'` 委托选中调用的详情正文,使 ui-tool 的 card model 保持为唯一展示所有者,同时避免 conversation 导入 Tool 组件。
|
||||
|
||||
**scope 寻址**与 host 侧 agent scope 惯例同构:服务是 root 单例,方法不收 sessionId——它们读调用方 ctx 上的 scope 标(`scopeOf(ctx)`)。在会话 scope 内,`ctx.conversation.send('hi', 'queue')` 自动打到该会话;跨会话调用换 ctx 定向(`ctx.sessions.scope(id)!.conversation.send(...)`);从 root ctx 直接调 scoped 方法即 throw。client 会话 scope 的铸造方式与 host agent scope 相同(no-op 插件 fiber + scope 键 extend),首次观看时惰性建,只有会话被移除且无人观看才拆——仅 host 会话死亡不拆 scope(冻结为只读视窗)。
|
||||
|
||||
|
||||
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-08-client-tool-presentation-ownership.md
|
||||
2026-08-08-client-tool-presentation-ownership.md: 4d06450a10c4198f20d7139aef815def9e7cb362
|
||||
2026-08-08-client-tool-presentation-ownership.zh.md: 3d3bf3bc3204aca6871a82c7b7ae330b6381a3e4
|
||||
2026-08-08-client-tool-presentation-ownership.md: e61c2030457cc9f0fda214e896b76afb37d0d2bc
|
||||
2026-08-08-client-tool-presentation-ownership.zh.md: 5c56b8c17ef5ca6695f3b28f6b93218dade356c7
|
||||
+2
@@ -66,6 +66,8 @@ A slot declaration also constrains render ownership. The conversation chat entry
|
||||
|
||||
The whole seat's `ToolTreeOwnerProps` carries the root `callId`, `toolName`, `ToolCallBlock`, `selectedCallId`, session `cwd`, `openFile(path)`, and `inspectCall(callId)`. `ToolCallTree` converts either a root or child into the same `ToolCallOwnerProps` and narrows inspect to a callback for that call. The atomic owner carries no `ReactNode`, Cordis `Context`, Session service, or projector; a business view consumes only one standard call block and host actions.
|
||||
|
||||
The seat filler also preserves the conversation DOM contract on every root and child wrapper: `data-chat-anchor-key="call:<callId>"`, `data-chat-call-id`, and `data-selected="true"` on the selected call. `ChatView` consumes the anchor key to restore prepend/paging position; the Tool owner emits it because it alone composes child wrappers.
|
||||
|
||||
Business plugins use one registration shape:
|
||||
|
||||
```text
|
||||
|
||||
+2
@@ -66,6 +66,8 @@ slot 声明同时限定渲染所有权。conversation chat entry 通过 `childre
|
||||
|
||||
整体席位的 `ToolTreeOwnerProps` 携带 root `callId`、`toolName`、`ToolCallBlock`、`selectedCallId`、session `cwd`、`openFile(path)` 与 `inspectCall(callId)`。`ToolCallTree` 把 root 或 child 转成相同的 `ToolCallOwnerProps`,并把 inspect 收窄成当前 call 的回调。原子 owner 不携带 `ReactNode`、Cordis `Context`、Session service 或 projector;业务 view 只消费一个标准调用块和宿主动作。
|
||||
|
||||
席位填充方还要在每个 root 和 child wrapper 上保留 conversation DOM 契约:`data-chat-anchor-key="call:<callId>"`、`data-chat-call-id`,以及 selected call 上的 `data-selected="true"`。`ChatView` 用 anchor key 恢复 prepend/paging 位置;child wrapper 由 Tool owner 独自编排,因此这些属性也由它输出。
|
||||
|
||||
业务插件遵循同一个注册形态:
|
||||
|
||||
```text
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-web-diff-card.md
|
||||
2026-07-30-web-diff-card.md: c8aed2aa59d82520a2a52523edb9b66d0bb34bf0
|
||||
2026-07-30-web-diff-card.zh.md: c2127844165e5f5c162eb3707fa5c86aa3a536c0
|
||||
2026-07-30-web-diff-card.md: b078da8d0e688d705683599467bd98b5c6a0be48
|
||||
2026-07-30-web-diff-card.zh.md: a9c710df99c1060bbf50b591b2f62120dc7dcbef
|
||||
@@ -14,7 +14,7 @@ This is the [terminal card](2026-07-28-web-terminal-card.md) done for the `diff`
|
||||
|
||||
## Decision
|
||||
|
||||
`DiffBlock` is a `ui-primitives` component that renders a file mutation as an inline diff surface, and both Web render sites for a write/edit call consume the diff render intent through it: the chat tool row's body and the details panel's Output section. `ui-tool/src/client/models/diff-card-model.ts` is the single place that turns the snapshot's `callView`/`resultView` pair into the component's props, so the two sites cannot disagree about a change. It returns null — the generic path — whenever neither side declares `card: 'diff'`, including a `card` value this client version does not know, and whenever a settled call's result view is generic, which is how write/edit keep their execution errors on the generic path. The result side is authoritative once the call settles: the applied hunks replace the call-time diff derived from the arguments alone. A paging window that drops the call head still renders, because the result view carries the whole change.
|
||||
`DiffBlock` is a `ui-primitives` component that renders a file mutation as an inline diff surface, and both Web render sites for a write/edit call consume the diff render intent through it: the chat tool row's body and the details panel's Output section. `ui-tool/src/client/tool/models/diff-card-model.ts` is the single place that turns the snapshot's `callView`/`resultView` pair into the component's props, so the two sites cannot disagree about a change. It returns null — the generic path — whenever neither side declares `card: 'diff'`, including a `card` value this client version does not know, and whenever a settled call's result view is generic, which is how write/edit keep their execution errors on the generic path. The result side is authoritative once the call settles: the applied hunks replace the call-time diff derived from the arguments alone. A paging window that drops the call head still renders, because the result view carries the whole change.
|
||||
|
||||
The component shares the TUI's single-column framing, line-terminator rule, and distinct-path file count. Line classification differs: Web renders the complete old and new sides, while the TUI derives neutral context and exact changed rows when its bounded comparison completes and labels its whole-side fallback approximate.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Web 客户端忽略了它。write/edit 调用落到 `GenericToolCard`,其行
|
||||
|
||||
## Decision
|
||||
|
||||
`DiffBlock` 是一个 `ui-primitives` 组件,把文件改动渲染为内联 diff 表面,write/edit 调用的两个 Web 渲染点都通过它消费 diff 渲染意图:chat 工具行的行体和详情面板的 Output 区。`ui-tool/src/client/models/diff-card-model.ts` 是唯一把快照的 `callView`/`resultView` 对转成组件 props 的地方,因此两个渲染点不会对一次改动产生分歧。当两侧都未声明 `card: 'diff'` 时它返回 null —— 走通用路径 —— 包括本客户端版本不认识的 `card` 值,以及已结算调用的 result view 是 generic 的情况(write/edit 的执行错误正是这样留在通用路径上的)。调用结算后 result 侧是权威:已应用的 hunk 替换仅从参数推导的 call 时 diff。分页窗口丢弃了 call 头也仍能渲染,因为 result view 携带完整改动。
|
||||
`DiffBlock` 是一个 `ui-primitives` 组件,把文件改动渲染为内联 diff 表面,write/edit 调用的两个 Web 渲染点都通过它消费 diff 渲染意图:chat 工具行的行体和详情面板的 Output 区。`ui-tool/src/client/tool/models/diff-card-model.ts` 是唯一把快照的 `callView`/`resultView` 对转成组件 props 的地方,因此两个渲染点不会对一次改动产生分歧。当两侧都未声明 `card: 'diff'` 时它返回 null —— 走通用路径 —— 包括本客户端版本不认识的 `card` 值,以及已结算调用的 result view 是 generic 的情况(write/edit 的执行错误正是这样留在通用路径上的)。调用结算后 result 侧是权威:已应用的 hunk 替换仅从参数推导的 call 时 diff。分页窗口丢弃了 call 头也仍能渲染,因为 result view 携带完整改动。
|
||||
|
||||
该组件与 TUI 共用单栏框架、行终止符规则和去重路径计数。两者的行分类不同:Web 渲染完整的变更前后两侧,而 TUI 会在有界比较完成时派生中性上下文和精确变更行,并把整侧回退标记为近似结果。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md
|
||||
2026-07-30-web-read-card-frontend.md: 10ed9d3eaa54c2440cf3fbd00a7e44b76c1acfe9
|
||||
2026-07-30-web-read-card-frontend.zh.md: 931316135470b7e7257f7f9d016b24fe741fdbf5
|
||||
2026-07-30-web-read-card-frontend.md: e659645066b706f35709fead4c11023eb4fe9554
|
||||
2026-07-30-web-read-card-frontend.zh.md: 4fc02e23b45399c01b5c99d388f5ae1726b026c0
|
||||
@@ -10,7 +10,7 @@ The [read backend](2026-07-30-web-read-card.md) added a fourth render-intent car
|
||||
|
||||
## Decision
|
||||
|
||||
`ReadBlock` is a `ui-primitives` component that renders a read result as a line-numbered, optionally syntax-highlighted file view, and both Web render sites for a read consume the read render intent through it: the chat tool row (resident under the summary line) and the details panel's Output section. `ui-tool/src/client/models/read-card-model.ts` is the single place that turns the snapshot's `resultView` into the component's props, so the two sites cannot disagree.
|
||||
`ReadBlock` is a `ui-primitives` component that renders a read result as a line-numbered, optionally syntax-highlighted file view, and both Web render sites for a read consume the read render intent through it: the chat tool row (resident under the summary line) and the details panel's Output section. `ui-tool/src/client/tool/models/read-card-model.ts` is the single place that turns the snapshot's `resultView` into the component's props, so the two sites cannot disagree.
|
||||
|
||||
**A new `ReadBlock` primitive, not an extension of `CodeBlock`.** `CodeBlock` already does shiki highlighting with a language banner and a copy control, but a read view needs a per-line gutter carrying each line's own file number, which `CodeBlock` renders as a single `<pre>` tree with no per-line structure. Extending `CodeBlock` with an optional gutter would push a read-specific concern (windowed line numbers, a "showing N of M" note, a height cap) onto every markdown fence and every `run_code` body that shares that component. Instead `ReadBlock` reuses the part that is genuinely shared: the shiki grammar singleton in `markdown/highlight.ts`. A new `highlightLines(code, lang)` there tokenizes into shiki's own per-line token arrays (`codeToTokens`) rather than the single-`<pre>` HTML `highlightToHtml` produces, so the block can place one gutter number per line and still color the content through the same `--shiki-*` custom properties on the same grammar allowlist. The height cap and its head/tail expand arithmetic are copied from `TerminalBlock` (`ceil(max/2)` head plus the remaining tail), so a long read and a long command output collapse at the same place. The copy control writes the window's raw text (the lines joined by newlines), never the gutter numbers or the banner.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## Decision
|
||||
|
||||
`ReadBlock` 是一个 `ui-primitives` 组件,把一次读取结果渲染成带行号、可选语法高亮的文件视图,读取的两个 Web 渲染点都通过它消费读取渲染意图:聊天工具行(常驻在摘要行之下)与详情面板的 Output 区段。`ui-tool/src/client/models/read-card-model.ts` 是把快照的 `resultView` 转成组件 props 的唯一位置,因此两个渲染点不会产生分歧。
|
||||
`ReadBlock` 是一个 `ui-primitives` 组件,把一次读取结果渲染成带行号、可选语法高亮的文件视图,读取的两个 Web 渲染点都通过它消费读取渲染意图:聊天工具行(常驻在摘要行之下)与详情面板的 Output 区段。`ui-tool/src/client/tool/models/read-card-model.ts` 是把快照的 `resultView` 转成组件 props 的唯一位置,因此两个渲染点不会产生分歧。
|
||||
|
||||
**新建一个 `ReadBlock` primitive,而不是扩展 `CodeBlock`。** `CodeBlock` 已经带语言横幅和复制控件做 shiki 高亮,但读取视图需要一个每行带该行自身文件行号的行号栏,而 `CodeBlock` 把内容渲染为单个 `<pre>` 树、没有逐行结构。给 `CodeBlock` 加一个可选行号栏会把读取专属的关切(窗口行号、"显示 N / M"提示、高度上限)强加给共享该组件的每个 markdown 代码围栏和每个 `run_code` 程序体。`ReadBlock` 转而复用真正共享的部分:`markdown/highlight.ts` 里的 shiki 语法单例。那里新增的 `highlightLines(code, lang)` 把代码切成 shiki 自己的逐行 token 数组(`codeToTokens`),而不是 `highlightToHtml` 产出的单 `<pre>` HTML,于是该 block 能每行放一个行号、同时用同一套 `--shiki-*` 自定义属性、同一份语法白名单给内容上色。高度上限及其头/尾展开算法照抄自 `TerminalBlock`(`ceil(max/2)` 行头部加剩余的尾部),因此长读取和长命令输出在同一处折叠。复制控件写入窗口的原始文本(各行以换行拼接),绝不含行号栏或横幅。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-web-result-card-frontend.md
|
||||
2026-07-30-web-result-card-frontend.md: 1d58710dbce3ed1aa9337e1841940195f71db40a
|
||||
2026-07-30-web-result-card-frontend.zh.md: 8705aa05557c1fdf642f498939bc2ceddde91f1f
|
||||
2026-07-30-web-result-card-frontend.md: c7e63220d824cf0bd3ac3536c528707d0db379bc
|
||||
2026-07-30-web-result-card-frontend.zh.md: 4cec11371e8d0848f2f03ded0b093d80cc25a7a5
|
||||
@@ -10,7 +10,7 @@ The `web_search` and `web_fetch` tools declare a `card: 'web'` result view ([web
|
||||
|
||||
## Decision
|
||||
|
||||
`WebBlock` is a `ui-primitives` component that renders a completed web retrieval, and every Web render site for a web call consumes the `web` render intent through it: the keyed chat tool rows (`web_search`/`web_fetch`), the `GenericToolCard` render-site fallback, and the details panel's Output section. `ui-tool/src/client/models/web-card-model.ts` is the single place that turns the snapshot's `resultView` into the component's props, mirroring `terminal-card-model.ts`, so no two sites disagree about what a web call shows. It returns null — the generic path — for a running call (the web card is result-only, since the tools keep a generic pending view), for a settled call whose result view is not a web card including a `card` value this client version does not know (which arrives over the wire and so cannot be trusted to be a compiled variant), for a generic result view (a web tool's error path returns the generic card, whose text the generic path preserves), and for a web card whose `kind` this client version does not know (a newer host's value off the wire, which reading as a fetch would draw as an empty URL and `HTTP undefined`).
|
||||
`WebBlock` is a `ui-primitives` component that renders a completed web retrieval, and every Web render site for a web call consumes the `web` render intent through it: the keyed chat tool rows (`web_search`/`web_fetch`), the `GenericToolCard` render-site fallback, and the details panel's Output section. `ui-tool/src/client/tool/models/web-card-model.ts` is the single place that turns the snapshot's `resultView` into the component's props, mirroring `terminal-card-model.ts`, so no two sites disagree about what a web call shows. It returns null — the generic path — for a running call (the web card is result-only, since the tools keep a generic pending view), for a settled call whose result view is not a web card including a `card` value this client version does not know (which arrives over the wire and so cannot be trusted to be a compiled variant), for a generic result view (a web tool's error path returns the generic card, whose text the generic path preserves), and for a web card whose `kind` this client version does not know (a newer host's value off the wire, which reading as a fetch would draw as an empty URL and `HTTP undefined`).
|
||||
|
||||
One component draws both kinds, discriminated by `kind`. A `search` shows the answer as markdown above a citation list; each source is a safe external link labelled by its title, or its hostname when the provider gave none, with the snippet and publication date below it, and a `来源列表已截断` indicator when the tool capped the list. A `fetch` shows a compact summary: the linked final URL, its HTTP status, and a `内容已截断` indicator. One component rather than two because both are web retrieval rendered as one card family, which is exactly the reason the contract carries them under one `card` tag with a `kind` discriminant.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## Decision
|
||||
|
||||
`WebBlock` 是一个 `ui-primitives` 组件,渲染一次已完成的 web 检索,web 调用的每个 Web 渲染点都通过它消费 `web` 渲染意图:键控的 chat 工具行(`web_search`/`web_fetch`)、`GenericToolCard` 渲染点兜底,以及详情面板的 Output 区。`ui-tool/src/client/models/web-card-model.ts` 是唯一把快照的 `resultView` 转成组件 props 的地方,镜像 `terminal-card-model.ts`,因此没有两个渲染点会对一次 web 调用的显示产生分歧。它返回 null —— 走通用路径 —— 对运行中的调用(web 卡片是 result-only 的,因为工具保留 generic pending 视图)、对 result view 不是 web 卡片的已结算调用(包括本客户端版本不认识的 `card` 值,它经 wire 抵达因而不能被信任为已编译的变体)、对 generic result view(web 工具的错误路径返回 generic 卡片,其文本由通用路径保留)、以及对本客户端版本不认识 `kind` 的 web 卡片(更新的 host 经 wire 发来的值,读作 fetch 会画出空 URL 和 `HTTP undefined`)。
|
||||
`WebBlock` 是一个 `ui-primitives` 组件,渲染一次已完成的 web 检索,web 调用的每个 Web 渲染点都通过它消费 `web` 渲染意图:键控的 chat 工具行(`web_search`/`web_fetch`)、`GenericToolCard` 渲染点兜底,以及详情面板的 Output 区。`ui-tool/src/client/tool/models/web-card-model.ts` 是唯一把快照的 `resultView` 转成组件 props 的地方,镜像 `terminal-card-model.ts`,因此没有两个渲染点会对一次 web 调用的显示产生分歧。它返回 null —— 走通用路径 —— 对运行中的调用(web 卡片是 result-only 的,因为工具保留 generic pending 视图)、对 result view 不是 web 卡片的已结算调用(包括本客户端版本不认识的 `card` 值,它经 wire 抵达因而不能被信任为已编译的变体)、对 generic result view(web 工具的错误路径返回 generic 卡片,其文本由通用路径保留)、以及对本客户端版本不认识 `kind` 的 web 卡片(更新的 host 经 wire 发来的值,读作 fetch 会画出空 URL 和 `HTTP undefined`)。
|
||||
|
||||
一个组件绘制两种 kind,由 `kind` 判别。`search` 把 answer 作为 markdown 显示在引用列表上方;每个 source 是一个安全外链,以其标题为标签,provider 未给标题时以其主机名为标签,下方是 snippet 与发布日期,工具截断列表时显示 `来源列表已截断` 提示。`fetch` 显示一个紧凑摘要:带链接的最终 URL、其 HTTP 状态、以及 `内容已截断` 提示。用一个组件而非两个,因为两者都是渲染为同一卡片族的 web 检索 —— 这正是契约把它们放在一个 `card` 标签下、用 `kind` 判别的原因。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-web-search-card.md
|
||||
2026-07-30-web-search-card.md: a350756a22d2ba5da8a0ff5d3a4cb3f257ac566b
|
||||
2026-07-30-web-search-card.zh.md: d6ec2a08eb02a92a50d4742c3d87c22f42c095d4
|
||||
2026-07-30-web-search-card.md: e0be857df69dfd46b6e936c775c6bae1476c26dc
|
||||
2026-07-30-web-search-card.zh.md: 1704b8f04c519511d5afa32ae6683c1e48ce7992
|
||||
@@ -12,7 +12,7 @@ This is the follow-up the search render card note names: that PR was the backend
|
||||
|
||||
## Decision
|
||||
|
||||
`SearchBlock` is a `ui-primitives` component that renders a completed search as either shape, and the Web render sites for a `grep`/`glob` call consume the search render intent through it. `ui-tool/src/client/models/search-card-model.ts` is the single place that turns the snapshot's `resultView` into the component's props, so no render site re-derives the shape. It returns null — the generic path — whenever the result view is not a search card, including a still-running call (a search card is result-time only, so there is nothing before `execute`), a generic result a `grep`/`glob` failure or a nested `run_code` dispatch produces, a terminal result view, a `card` value this client version does not know, a `card: 'search'` view whose `shape` this version does not compile, and — because `shape` and the grouped/flat contents ride the same untrusted wire frame the host schema only string-checks — a known `shape` whose `files`/`paths` is missing or malformed (which would otherwise crash `SearchBlock` at `.reduce`/`.map`). The result-view discriminant is `shape` (not `kind`, which the backend reserves for the call view's icon-picking tag); `SearchBlock`'s own prop stays `kind`, mapped from `shape` in this derivation.
|
||||
`SearchBlock` is a `ui-primitives` component that renders a completed search as either shape, and the Web render sites for a `grep`/`glob` call consume the search render intent through it. `ui-tool/src/client/tool/models/search-card-model.ts` is the single place that turns the snapshot's `resultView` into the component's props, so no render site re-derives the shape. It returns null — the generic path — whenever the result view is not a search card, including a still-running call (a search card is result-time only, so there is nothing before `execute`), a generic result a `grep`/`glob` failure or a nested `run_code` dispatch produces, a terminal result view, a `card` value this client version does not know, a `card: 'search'` view whose `shape` this version does not compile, and — because `shape` and the grouped/flat contents ride the same untrusted wire frame the host schema only string-checks — a known `shape` whose `files`/`paths` is missing or malformed (which would otherwise crash `SearchBlock` at `.reduce`/`.map`). The result-view discriminant is `shape` (not `kind`, which the backend reserves for the call view's icon-picking tag); `SearchBlock`'s own prop stays `kind`, mapped from `shape` in this derivation.
|
||||
|
||||
The asymmetry with the terminal card is deliberate and inherited from the backend contract: `terminalCardModel` reads both `callView` and `resultView` because a command, cwd, and description exist at call time; `searchCardModel` reads only `resultView` because a search's matches or paths exist only after execution. A running search row therefore shows its summary alone, with no card.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
## Decision
|
||||
|
||||
`SearchBlock` 是一个 `ui-primitives` 组件,把一次已完成的搜索渲染成两种形态之一,`grep`/`glob` 调用的 Web 渲染点都通过它消费搜索 render intent。`ui-tool/src/client/models/search-card-model.ts` 是把 snapshot 的 `resultView` 转成组件 props 的唯一位置,因此没有渲染点重新推导形态。当结果视图不是搜索卡片时它返回 null(走 generic 路径),包括仍在运行的调用(搜索卡片仅在结果阶段存在,`execute` 前无内容)、`grep`/`glob` 失败或嵌套 `run_code` dispatch 产生的 generic 结果、terminal 结果视图、本客户端版本不认识的 `card` 值、`shape` 是本版本无法编译的 `card: 'search'` 视图,以及 —— 因为 `shape` 和分组/扁平内容与 host schema 只做字符串校验的那同一个不可信 wire 帧同行 —— 一个 `shape` 已知但 `files`/`paths` 缺失或格式错误的视图(否则会让 `SearchBlock` 在 `.reduce`/`.map` 处崩溃)。结果视图的判别键是 `shape`(不是 `kind` —— 后端把 `kind` 留给 call view 的选图标签);`SearchBlock` 自身的 prop 仍是 `kind`,由本推导从 `shape` 映射得到。
|
||||
`SearchBlock` 是一个 `ui-primitives` 组件,把一次已完成的搜索渲染成两种形态之一,`grep`/`glob` 调用的 Web 渲染点都通过它消费搜索 render intent。`ui-tool/src/client/tool/models/search-card-model.ts` 是把 snapshot 的 `resultView` 转成组件 props 的唯一位置,因此没有渲染点重新推导形态。当结果视图不是搜索卡片时它返回 null(走 generic 路径),包括仍在运行的调用(搜索卡片仅在结果阶段存在,`execute` 前无内容)、`grep`/`glob` 失败或嵌套 `run_code` dispatch 产生的 generic 结果、terminal 结果视图、本客户端版本不认识的 `card` 值、`shape` 是本版本无法编译的 `card: 'search'` 视图,以及 —— 因为 `shape` 和分组/扁平内容与 host schema 只做字符串校验的那同一个不可信 wire 帧同行 —— 一个 `shape` 已知但 `files`/`paths` 缺失或格式错误的视图(否则会让 `SearchBlock` 在 `.reduce`/`.map` 处崩溃)。结果视图的判别键是 `shape`(不是 `kind` —— 后端把 `kind` 留给 call view 的选图标签);`SearchBlock` 自身的 prop 仍是 `kind`,由本推导从 `shape` 映射得到。
|
||||
|
||||
与终端卡片的不对称是刻意的,继承自后端契约:`terminalCardModel` 同时读 `callView` 和 `resultView`,因为命令、cwd、description 在调用时就存在;`searchCardModel` 只读 `resultView`,因为搜索的匹配或路径只在执行后存在。因此运行中的搜索行只显示摘要,没有卡片。
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ export const AssistantMarkdown = memo(function AssistantMarkdown({
|
||||
case 'text': return (
|
||||
<MarkdownText key={i} text={block.text} streaming={streaming} codeLabels={codeLabels} />
|
||||
)
|
||||
case 'reasoning': return <ReasoningRow key={i} text={block.text} running={streaming && i === last} />
|
||||
case 'reasoning': return <ReasoningRow key={i} text={block.text} running={streaming && i === last} t={t} />
|
||||
// Grouped into tool rows by ChatView; hasVisible above skips an empty shell.
|
||||
case 'tool-call': return null
|
||||
default: return (
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import type { ChatViewSlotProps, CommandRowOwnerProps } from '../contract/slots.ts'
|
||||
import { DisclosureRow, IconApiOutline14, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import a11yCss from './accessibility.module.css'
|
||||
import css from './GenericCommandCard.module.css'
|
||||
|
||||
type CommandRowState = 'running' | 'ok' | 'error'
|
||||
@@ -42,6 +43,7 @@ export function GenericCommandCard({ node, t }: GenericCommandCardProps) {
|
||||
const open = expanded && body !== null
|
||||
return (
|
||||
<div className={css.root} data-state={state}>
|
||||
{state === 'error' && <span className={a11yCss.visuallyHidden}>{t('row.failed')}</span>}
|
||||
<DisclosureRow
|
||||
rowClassName={css.row}
|
||||
leadingClassName={css.leading}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/** Assistant reasoning disclosure, independent of Tool-call presentation. */
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { DisclosureRow, IconThinkOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { ChatViewSlotProps } from '../contract/slots.ts'
|
||||
import { useThrottledVisualUpdate } from './use-throttled-visual-update.ts'
|
||||
import a11yCss from './accessibility.module.css'
|
||||
import css from './ReasoningRow.module.css'
|
||||
|
||||
function firstLine(text: string): string {
|
||||
@@ -19,9 +21,10 @@ function latestLine(text: string): string {
|
||||
* Render one assistant reasoning block as the Think disclosure row.
|
||||
* @param props.text - complete or streaming reasoning text.
|
||||
* @param props.running - whether this block is the streaming tail.
|
||||
* @param props.t - conversation locale seat for the running status.
|
||||
* @returns the reasoning disclosure.
|
||||
*/
|
||||
export function ReasoningRow({ text, running }: { text: string; running: boolean }) {
|
||||
export function ReasoningRow({ text, running, t }: { text: string; running: boolean; t: ChatViewSlotProps['t'] }) {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const summaryRef = useRef<HTMLSpanElement>(null)
|
||||
const summary = running ? latestLine(text) : firstLine(text)
|
||||
@@ -36,6 +39,7 @@ export function ReasoningRow({ text, running }: { text: string; running: boolean
|
||||
|
||||
return (
|
||||
<div className={css.root} data-state={running ? 'running' : 'ok'}>
|
||||
{running && <span className={a11yCss.visuallyHidden}>{t('row.running')}</span>}
|
||||
<DisclosureRow
|
||||
rowClassName={css.row}
|
||||
leadingClassName={css.leading}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.visuallyHidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -33,6 +33,8 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
/**
|
||||
* One root Tool call at its ordered ChatFlow position. The chat view owns
|
||||
* placement; ui-tool owns root/subcall composition and keyed dispatch.
|
||||
* The filler preserves the call-anchor DOM contract documented by
|
||||
* {@link ToolTreeOwnerProps} for every root and child wrapper.
|
||||
*/
|
||||
'conversation.chat.tool': { kind: 'single'; scope: 'session'; owner: ToolTreeOwnerProps }
|
||||
/**
|
||||
@@ -176,7 +178,12 @@ export interface TurnTailOwnerProps {
|
||||
openFile: (path: string) => void
|
||||
}
|
||||
|
||||
/** Owner currency of the chat view's whole-Tool rendering seat. */
|
||||
/**
|
||||
* Owner currency of the chat view's whole-Tool rendering seat. The filler
|
||||
* wraps every rendered root and child with `data-chat-anchor-key="call:<id>"`
|
||||
* and `data-chat-call-id="<id>"`, plus `data-selected="true"` for the selected
|
||||
* call. ChatView consumes those anchors to restore prepend/paging position.
|
||||
*/
|
||||
export interface ToolTreeOwnerProps {
|
||||
/** Root Tool call identity, stable across running → settled. */
|
||||
callId: CallId
|
||||
|
||||
@@ -1269,6 +1269,7 @@ describe('ChatView', () => {
|
||||
const fv = render(<failed.ChatView {...failed.props} />)
|
||||
expect(fv.container.querySelector('[data-state="error"]')).not.toBeNull()
|
||||
expect(fv.getByText('命令失败')).toBeTruthy()
|
||||
expect(fv.getByText('失败')).toBeTruthy()
|
||||
|
||||
// Still executing: running state with the executing copy.
|
||||
const executing = makeHarness({
|
||||
|
||||
@@ -47,6 +47,7 @@ describe('ReasoningRow', () => {
|
||||
streaming
|
||||
/>,
|
||||
)
|
||||
expect(view.getByText('运行中')).toBeTruthy()
|
||||
const summary = view.getByText('Newest reasoning tokens')
|
||||
Object.defineProperties(summary, {
|
||||
scrollWidth: { configurable: true, value: 300 },
|
||||
@@ -76,6 +77,7 @@ describe('ReasoningRow', () => {
|
||||
)
|
||||
flushAnimationFrames(3)
|
||||
expect(view.getByText('Inspect the session')).toBeTruthy()
|
||||
expect(view.queryByText('运行中')).toBeNull()
|
||||
expect(summary.scrollLeft).toBe(0)
|
||||
expect(summary.hasAttribute('data-follow-end')).toBe(false)
|
||||
})
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-tool/README.md
|
||||
README.md: 69d6931d33788f2df593919d160c4bfeaef8d15c
|
||||
README.zh.md: eb49465a743711bd48358bf3953a485f9ef037eb
|
||||
README.md: bf6213ebfacd8f7963463b2c443524c631c28bcd
|
||||
README.zh.md: 06a46a525eead005375bcf67794a1ceecde678bc
|
||||
@@ -10,6 +10,8 @@ Business UI packages register only their wire Tool names and atomic views. They
|
||||
|
||||
`ToolCallTree` receives one root `ToolCallBlock`, selection state, the session `cwd`, and Host callbacks for opening files and inspecting calls. Through its standard session slot props it selects the Runtime-projected `codeDispatches[rootCallId]` array, then sends the root and every child through the same atomic dispatch path. The Runtime currently exposes only one Code Dispatch child level, so the renderer preserves that shape instead of inventing recursive data.
|
||||
|
||||
Each root and child wrapper preserves the `conversation.chat.tool` call-anchor DOM contract used for paging and selection.
|
||||
|
||||
The package also fills `conversation.details.tool` with `ToolDetails`. The row and details renderers share the same pure card models for `terminal`, `read`, `diff`, `search`, and `web` render intents. Unknown intent tags and malformed wire card data fall back to flattened Tool result text.
|
||||
|
||||
Generic rows classify known Tool names into search, read, shell, write, edit, code, or generic variants. Running, successful, failed, and interrupted lifecycle states come only from the frozen call/result slice. File paths resolve against the session `cwd` only when the user invokes the Host open-file callback; presentation code does not read Session services.
|
||||
@@ -30,6 +32,8 @@ The owner payload is `ToolCallOwnerProps`: `callId`, `toolName`, the frozen `blo
|
||||
|
||||
This package currently owns the generic fallback and the built-in bash/pwsh, read, write/edit, grep/glob, web, todo, question, and Code Dispatch presentations. `ui-skill` demonstrates a business-owned registration for `skill`.
|
||||
|
||||
Card-specific limits and fallback rules remain in the owning [terminal](../../../.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md), [diff](../../../.agents/notes/implemented/feature/2026-07-30-web-diff-card.md), [read](../../../.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md), [search](../../../.agents/notes/implemented/feature/2026-07-30-web-search-card.md), and [web](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card-frontend.md) notes.
|
||||
|
||||
## Model Experience
|
||||
|
||||
None, as this package renders already logged Tool calls and results without altering model requests, Tool execution, or session events.
|
||||
@@ -42,3 +46,4 @@ None. The package is client-only presentation.
|
||||
|
||||
- The Runtime currently exposes one level of Code Dispatch children. The renderer sends roots and children through the same atomic path, but it does not claim an arbitrary recursive wire topology.
|
||||
- Existing first-party Tool views are initially colocated here and can move to their owning business packages independently through the keyed slot.
|
||||
- Tool copy temporarily reuses the `ui-conversation` locale namespace.
|
||||
@@ -10,6 +10,8 @@ Client Tool 展示插件。`ui-conversation` 通过 `conversation.chat.tool` 交
|
||||
|
||||
`ToolCallTree` 接收一个 root `ToolCallBlock`、selection 状态、会话 `cwd`,以及用于打开文件和检查调用的 Host 回调。它通过标准 session slot props 选择 Runtime 投影的 `codeDispatches[rootCallId]` 数组,再让 root 与每个 child 经过同一条原子分发路径。Runtime 当前只暴露一层 Code Dispatch child,因此 renderer 保留该形状,不自行发明递归数据。
|
||||
|
||||
每个 root 和 child wrapper 都保留 `conversation.chat.tool` 的 call-anchor DOM 契约,供分页和 selection 使用。
|
||||
|
||||
本包还通过 `ToolDetails` 填充 `conversation.details.tool`。行 renderer 与详情 renderer 为 `terminal`、`read`、`diff`、`search` 和 `web` render intent 共用同一组纯 card model。本版本不认识的 intent 标签和格式错误的 wire card 数据都会回退为压平的 Tool result 文本。
|
||||
|
||||
通用行把已知 Tool 名称归类为 search、read、shell、write、edit、code 或 generic 变体。运行中、成功、失败和中断状态只来自冻结的 call/result slice。只有用户调用 Host 打开文件回调时,文件路径才相对会话 `cwd` 解析;展示代码不读取 Session service。
|
||||
@@ -30,6 +32,8 @@ owner 载荷为 `ToolCallOwnerProps`:`callId`、`toolName`、冻结的 `block`
|
||||
|
||||
本包当前拥有 generic fallback,以及 bash/pwsh、read、write/edit、grep/glob、web、todo、question 和 Code Dispatch 的内置展示。`ui-skill` 展示了业务包如何拥有 `skill` 注册。
|
||||
|
||||
各类卡片的上限与 fallback 规则仍由对应的 [terminal](../../../.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md)、[diff](../../../.agents/notes/implemented/feature/2026-07-30-web-diff-card.md)、[read](../../../.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md)、[search](../../../.agents/notes/implemented/feature/2026-07-30-web-search-card.md) 和 [web](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card-frontend.md) Note 负责。
|
||||
|
||||
## 模型体验
|
||||
|
||||
无,因为本包只渲染已经记录的 Tool 调用和结果,不改变模型请求、Tool 执行或 Session Event。
|
||||
@@ -42,3 +46,4 @@ owner 载荷为 `ToolCallOwnerProps`:`callId`、`toolName`、冻结的 `block`
|
||||
|
||||
- Runtime 当前只暴露一层 Code Dispatch 子调用。renderer 会让 root 和 child 经过同一个原子分发路径,但不宣称 wire 拓扑已经支持任意递归。
|
||||
- 现有第一方 Tool 视图初期仍集中在本包,之后可以通过 keyed slot 独立迁回各自业务包。
|
||||
- Tool 文案暂时复用 `ui-conversation` locale namespace。
|
||||
@@ -14,7 +14,7 @@ import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
// Export discipline: packages/client/AGENTS.md.
|
||||
import { AskQuestionRow, askQuestionToolview } from '../src/client/tool/toolviews/ask-question-row.tsx'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { ToolRow } from '../src/client/tool/components/ToolRow.tsx'
|
||||
import { BashRow } from '../src/client/tool/toolviews/bash-sample.tsx'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
type BashRowProps = Parameters<typeof BashRow>[0]
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import type { SelectionTarget } from '@deepseek-ai/dsh-client-ui-conversation/cl
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import { CHAT_DIFF_MAX_LINES, diffCardModel } from '../src/client/tool/models/diff-card-model.ts'
|
||||
import { createChatStore } from '../../ui-conversation/src/client/stores.ts'
|
||||
import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/client/stores.ts'
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '../../ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { FileMutationRow, fileMutationToolview } from '../src/client/tool/toolviews/file-mutation-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ import type {
|
||||
import type { ToolResultView } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type { SelectionTarget } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import { CHAT_READ_MAX_LINES, readCardModel } from '../src/client/tool/models/read-card-model.ts'
|
||||
import { createChatStore } from '../../ui-conversation/src/client/stores.ts'
|
||||
import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/client/stores.ts'
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { DetailsPanel } from '../../ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { ReadRow, readToolview } from '../src/client/tool/toolviews/read-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ import type { SelectionTarget } from '@deepseek-ai/dsh-client-ui-conversation/cl
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import { CHAT_SEARCH_MAX_LINES, searchCardModel } from '../src/client/tool/models/search-card-model.ts'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { createChatStore } from '../../ui-conversation/src/client/stores.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/client/stores.ts'
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '../../ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { SearchRow, searchToolview } from '../src/client/tool/toolviews/search-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import type { SelectionTarget } from '@deepseek-ai/dsh-client-ui-conversation/cl
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import { terminalCardModel, terminalFailed } from '../src/client/tool/models/terminal-card-model.ts'
|
||||
import { createChatStore } from '../../ui-conversation/src/client/stores.ts'
|
||||
import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/client/stores.ts'
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '../../ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { BashRow } from '../src/client/tool/toolviews/bash-sample.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
type BashRowProps = Parameters<typeof BashRow>[0]
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts
|
||||
import { TodoRow, todoToolview } from '../src/client/tool/toolviews/todo-row.tsx'
|
||||
import { planSummary } from '../src/client/tool/toolviews/plan-summary.ts'
|
||||
import { CONVERSATION_NS as NS } from '../src/client/locale.ts'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
type TodoRowProps = Parameters<typeof TodoRow>[0]
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import type { ToolTreeProps } from '../src/client/contract/slots.ts'
|
||||
import { ToolCallTree } from '../src/client/tool/ToolCallTree.tsx'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Test adapter for the production conversation.details.tool registration. */
|
||||
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { SessionProviderComponent, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type { DetailsSlotProps, DetailsToolOwnerProps } from '../../ui-conversation/src/client/contract/slots.ts'
|
||||
import type { DetailsSlotProps, DetailsToolOwnerProps } from '@deepseek-ai/dsh-client-ui-conversation/src/client/contract/slots.ts'
|
||||
import { ToolDetails } from '../src/client/tool/ToolDetails.tsx'
|
||||
|
||||
/** Framework session-area seat used by direct DetailsPanel tests. */
|
||||
|
||||
@@ -9,7 +9,7 @@ import { resolveWorkspacePath } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { classifyTool, resultText, toolRowModel } from '../src/client/tool/models/tool-call-model.ts'
|
||||
import { ToolRow } from '../src/client/tool/components/ToolRow.tsx'
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
|
||||
@@ -19,14 +19,14 @@ import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
|
||||
import type { SelectionTarget } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import type { ToolCallOwnerProps } from '@deepseek-ai/dsh-client-ui-tool/client'
|
||||
import { webCardModel } from '../src/client/tool/models/web-card-model.ts'
|
||||
import { createChatStore } from '../../ui-conversation/src/client/stores.ts'
|
||||
import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/client/stores.ts'
|
||||
import { GenericToolCard } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '../../ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { WebRow, webToolview } from '../src/client/tool/toolviews/web-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import { zh } from '../../ui-conversation/src/client/locales.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ export default defineConfig({
|
||||
'packages/client/web-react/src/*',
|
||||
'packages/client/runtime/src/*',
|
||||
'packages/client/ui-conversation/src/*',
|
||||
'packages/client/ui-primitives/src/DisclosureRow.tsx',
|
||||
'packages/client/ui-tool/src/*',
|
||||
'packages/client/ui-slots/src/*',
|
||||
'packages/client/ui-layout/src/*',
|
||||
|
||||
Reference in New Issue
Block a user