cleanup(web): drop the unimplemented user-message edit control
The user bubble's IconActions row carried an edit button with no click handler, no client mutation, and no host operation behind it, advertising an affordance the product cannot honor. The row now renders clock / copy / branch only for both user and assistant chrome, and MessageIconActions loses the `edit` prop with the button. The package README records the missing capability under Known Limitations, the web aria goldens pin the row without the control, and the Agent Note states what must exist before it comes back.
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
|||||||
|
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||||
|
# 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/simplification/2026-07-31-drop-user-message-edit-stub.md
|
||||||
|
2026-07-31-drop-user-message-edit-stub.md: 5a34be1dd285f4aced9e6cfb2e324b20ec734bed
|
||||||
|
2026-07-31-drop-user-message-edit-stub.zh.md: 768cb4618b4a85f61e6348f8c6fb6a93d686e7d3
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Agent Note: Drop the user-message edit stub
|
||||||
|
|
||||||
|
Status: implemented
|
||||||
|
|
||||||
|
English | [中文](2026-07-31-drop-user-message-edit-stub.zh.md)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The user bubble's IconActions row carried an edit button beside copy and branch. Nothing backed it: the control had no click handler, no client mutation, and no host operation for resending an edited message. A user who found it saw an affordance the product cannot honor.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
`MessageIconActions` renders clock / copy / branch only, and its `edit` prop is gone with the button; `MessageItem` no longer passes it. The user bubble and the assistant chrome now differ only by clock side. The package README records the missing capability under Known Limitations, and the web message-actions golden pins the row without the control.
|
||||||
|
|
||||||
|
The common locale keeps its generic `edit` term, which is shared vocabulary rather than this component's copy.
|
||||||
|
|
||||||
|
Reintroduce the control together with the capability: a client mutation that edits a settled user message and the host behavior that decides what the edited message does to the turn that already consumed it.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
**Disable the button with a tooltip.** A visible-but-dead control still advertises editing and costs the same explaining; removal is the honest state.
|
||||||
|
|
||||||
|
**Wire it to the queue editor.** The queue edits a message that has not been sent. A settled user message is already in the transcript and in the model's context, so reusing that editor would silently mean something else.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Web offers no way to correct a sent message; branching from the message is the nearest available gesture. Reintroduction is a UI-only change once the mutation exists, since the row composes its actions from props.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Agent Note: 移除 user 消息的编辑存根
|
||||||
|
|
||||||
|
Status: implemented
|
||||||
|
|
||||||
|
[English](2026-07-31-drop-user-message-edit-stub.md) | 中文
|
||||||
|
|
||||||
|
## 问题
|
||||||
|
|
||||||
|
user 气泡的 IconActions 行在复制和分支旁边还有一个编辑按钮,但其背后什么都没有:该控件没有点击处理、没有 client 侧变更,也没有 host 侧重新发送已编辑消息的操作。用户找到它时,看到的是一个产品无法兑现的可供性。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
`MessageIconActions` 只渲染时钟/复制/分支,其 `edit` prop 随按钮一并删除;`MessageItem` 不再传入该 prop。现在 user 气泡与 assistant chrome 只在时钟位置上不同。包 README 在 Known Limitations 中记录这项缺失的能力,web 的 message-actions 金样固定了不含该控件的动作行。
|
||||||
|
|
||||||
|
公共 locale 保留通用的 `edit` 词条:它是共享词汇,而非本组件的文案。
|
||||||
|
|
||||||
|
重新引入该控件时要与能力一起落地:既需要编辑已定稿 user 消息的 client 变更,也需要 host 侧决定这条编辑后的消息对已经消费过它的轮次意味着什么。
|
||||||
|
|
||||||
|
## 曾考虑的替代方案
|
||||||
|
|
||||||
|
**把按钮置灰并加提示。** 一个可见但无效的控件仍在宣告可以编辑,解释成本相同;直接移除才是诚实的状态。
|
||||||
|
|
||||||
|
**接到队列编辑器上。** 队列编辑的是尚未发送的消息。已定稿的 user 消息已经进入转录和模型上下文,复用该编辑器会让同一个动作悄悄变成另一件事。
|
||||||
|
|
||||||
|
## 后果
|
||||||
|
|
||||||
|
Web 没有任何途径修正已发送的消息;从该消息分支是最接近的现有手势。由于动作行的内容完全由 props 组合而来,能力就绪后重新引入只是一次纯 UI 改动。
|
||||||
@@ -64,14 +64,14 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
|||||||
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBe(1)
|
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBe(1)
|
||||||
|
|
||||||
// Focus-reveal the footers (hover:hover keeps them opacity-hidden until
|
// Focus-reveal the footers (hover:hover keeps them opacity-hidden until
|
||||||
// hover/focus-within). User has three actions; each turn's last content
|
// hover/focus-within). User and each turn's last content assistant both
|
||||||
// assistant has copy + branch.
|
// have copy + branch.
|
||||||
const copyButtons = page.getByRole('button', { name: 'Copy' })
|
const copyButtons = page.getByRole('button', { name: 'Copy' })
|
||||||
await expect.poll(() => copyButtons.count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(2)
|
await expect.poll(() => copyButtons.count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(2)
|
||||||
await copyButtons.first().focus()
|
await copyButtons.first().focus()
|
||||||
await expect.poll(() => page.getByRole('button', { name: 'Branch into a new conversation' }).count(), { timeout: 5_000 })
|
await expect.poll(() => page.getByRole('button', { name: 'Branch into a new conversation' }).count(), { timeout: 5_000 })
|
||||||
.toBeGreaterThanOrEqual(2)
|
.toBeGreaterThanOrEqual(2)
|
||||||
await expect.poll(() => page.getByRole('button', { name: 'Edit' }).count(), { timeout: 5_000 }).toBe(1)
|
await expect.poll(() => page.getByRole('button', { name: 'Edit' }).count(), { timeout: 5_000 }).toBe(0)
|
||||||
}, 60_000)
|
}, 60_000)
|
||||||
|
|
||||||
it.skipIf(MODE === 'record')('matches the conversation aria golden with IconActions and clocks', async () => {
|
it.skipIf(MODE === 'record')('matches the conversation aria golden with IconActions and clocks', async () => {
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- 'button "Think The user wants me to write a single `run_code` program that:"':
|
- 'button "Think The user wants me to write a single `run_code` program that:"':
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to:":
|
- button "Think The user wants me to:":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to run a simple bash command and reply with \"DONE\".":
|
- button "Think The user wants me to run a simple bash command and reply with \"DONE\".":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to reply with a single word. Let me comply.":
|
- button "Think The user wants me to reply with a single word. Let me comply.":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- paragraph: partial
|
- paragraph: partial
|
||||||
- text: Stopped
|
- text: Stopped
|
||||||
- button "Copy":
|
- button "Copy":
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Commands":
|
- button "Commands":
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- paragraph: partial
|
- paragraph: partial
|
||||||
- status: Deep diving...
|
- status: Deep diving...
|
||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- group:
|
- group:
|
||||||
- status: Retried model request (1/2) · {{duration}}
|
- status: Retried model request (1/2) · {{duration}}
|
||||||
- button "Think The user is asking for a one-sentence description of event sourcing. This is a straightforward knowledge question that doesn't require any skill loading or tool calls.":
|
- button "Think The user is asking for a one-sentence description of event sourcing. This is a straightforward knowledge question that doesn't require any skill loading or tool calls.":
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
- tooltip "Copy"
|
- tooltip "Copy"
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- 'button "Think The user wants me to plan a small change to add a `--greeting` flag to a CLI. They explicitly told me not to read or write any files, and to call exit_plan_mode with a short plan. Let me do that directly."':
|
- 'button "Think The user wants me to plan a small change to add a `--greeting` flag to a CLI. They explicitly told me not to read or write any files, and to call exit_plan_mode with a short plan. Let me do that directly."':
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that.":
|
- button "Think The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that.":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- paragraph: partial
|
- paragraph: partial
|
||||||
- status: Deep diving...
|
- status: Deep diving...
|
||||||
- button "2 queued messages"
|
- button "2 queued messages"
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- paragraph: partial
|
- paragraph: partial
|
||||||
- status: Deep diving...
|
- status: Deep diving...
|
||||||
- button "2 queued messages" [disabled] [expanded]
|
- button "2 queued messages" [disabled] [expanded]
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- paragraph: partial
|
- paragraph: partial
|
||||||
- status: Deep diving...
|
- status: Deep diving...
|
||||||
- list:
|
- list:
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
|
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
|
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
|
||||||
- img
|
- img
|
||||||
- img
|
- img
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
- img
|
- img
|
||||||
- button "Branch into a new conversation":
|
- button "Branch into a new conversation":
|
||||||
- img
|
- img
|
||||||
- button "Edit":
|
|
||||||
- img
|
|
||||||
- img
|
- img
|
||||||
- text: Search DeepSeek Harness snapshot search
|
- text: Search DeepSeek Harness snapshot search
|
||||||
- list:
|
- list:
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write packages/client/ui-conversation/README.md
|
# pnpm run verify-translation-pairing --write packages/client/ui-conversation/README.md
|
||||||
README.md: b183aab56a10c3a22eeab31f8b1b0e6b5219beba
|
README.md: 3ee0f5efa653ade99c4e5c94dcc94377244346ba
|
||||||
README.zh.md: af6427774a048de324d57bc9856f77547e9a03cc
|
README.zh.md: d8740fb81b0b464c8002119a5080486432200b33
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ None; this package neither assembles nor sends a provider request.
|
|||||||
- **Stats-line durations cover the in-window flow only** — LLM and tool wall times fold the snapshot's assistant `timing` and tool call/result pairs, so nodes outside the loaded event window (older history) are not counted.
|
- **Stats-line durations cover the in-window flow only** — LLM and tool wall times fold the snapshot's assistant `timing` and tool call/result pairs, so nodes outside the loaded event window (older history) are not counted.
|
||||||
- **Details panel is the minimal form and currently has no entry point** — selected call args/result raw display; the Input/Output/Metadata switch, Prev/Next stepping, and See-in-trajectory deep link are deferred. Tool rows stopped being details-panel click targets and nothing replaced that gesture, so `ChatViewInjected.openDetails` is implemented but uncalled and the panel (including its terminal card) is unreachable in the assembled application; its rendering stays covered by mounting it with a selection directly.
|
- **Details panel is the minimal form and currently has no entry point** — selected call args/result raw display; the Input/Output/Metadata switch, Prev/Next stepping, and See-in-trajectory deep link are deferred. Tool rows stopped being details-panel click targets and nothing replaced that gesture, so `ChatViewInjected.openDetails` is implemented but uncalled and the panel (including its terminal card) is unreachable in the assembled application; its rendering stays covered by mounting it with a selection directly.
|
||||||
- **Assistant per-message paging is a reserved slot** — drawn in the design, not implemented. The finalized content IconActions row (copy / branch / clock) ships under the last content-text assistant of each turn only; mid-turn narration and Think-only nodes stay chrome-free. Branch forks through the turn containing that message, increments the inherited title on the client, and then opens the child, while a fork or rename failure leaves the source selected.
|
- **Assistant per-message paging is a reserved slot** — drawn in the design, not implemented. The finalized content IconActions row (copy / branch / clock) ships under the last content-text assistant of each turn only; mid-turn narration and Think-only nodes stay chrome-free. Branch forks through the turn containing that message, increments the inherited title on the client, and then opens the child, while a fork or rename failure leaves the source selected.
|
||||||
|
- **Sent user messages cannot be edited** — the user bubble's IconActions row carries clock / copy / branch only. The design's edit control shipped as a stub that no click handler backed, and it was removed until resending an edited message exists end to end ([decision](../../../.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.md)).
|
||||||
- **The sparkle icon for the others tool row is a hand-drawn approximation** — the design glyph's vector geometry is not exportable locally; promotion into ui-primitives waits on an exact export.
|
- **The sparkle icon for the others tool row is a hand-drawn approximation** — the design glyph's vector geometry is not exportable locally; promotion into ui-primitives waits on an exact export.
|
||||||
- **The approval panel's "Always allow this type" is deferred** — durable grants need a grant-storage design; only allow-once/reject answer today.
|
- **The approval panel's "Always allow this type" is deferred** — durable grants need a grant-storage design; only allow-once/reject answer today.
|
||||||
- **TodoPanel truncates long item text to one ellipsized line** — the figma strip has no wrap or expand affordance; full text is not readable inline.
|
- **TodoPanel truncates long item text to one ellipsized line** — the figma strip has no wrap or expand affordance; full text is not readable inline.
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ todo 两个面就是在该形状上的两个注册项,都是普通注册方插
|
|||||||
- **统计行的耗时只覆盖窗口内消息流**:LLM(大语言模型)与工具墙钟时间由快照的 assistant `timing` 与工具 call/result 配对折算,落在已加载事件窗口之外的节点(更早的历史)不计入。
|
- **统计行的耗时只覆盖窗口内消息流**:LLM(大语言模型)与工具墙钟时间由快照的 assistant `timing` 与工具 call/result 配对折算,落在已加载事件窗口之外的节点(更早的历史)不计入。
|
||||||
- **详情面板是最小形态,且当前没有入口**:以原始形式显示已选择调用的参数/结果;Input/Output/Metadata 切换、Prev/Next 步进与 See-in-trajectory 深链接暂缓实现。工具行已不再是详情面板的点击目标,且没有任何手势接替它,因此 `ChatViewInjected.openDetails` 虽已实现却无人调用,该面板(含其终端卡片)在组装后的应用中不可达;其渲染仍由直接以选中态挂载它来覆盖。
|
- **详情面板是最小形态,且当前没有入口**:以原始形式显示已选择调用的参数/结果;Input/Output/Metadata 切换、Prev/Next 步进与 See-in-trajectory 深链接暂缓实现。工具行已不再是详情面板的点击目标,且没有任何手势接替它,因此 `ChatViewInjected.openDetails` 虽已实现却无人调用,该面板(含其终端卡片)在组装后的应用中不可达;其渲染仍由直接以选中态挂载它来覆盖。
|
||||||
- **assistant 逐消息分页是预留 slot**:设计中已有图稿,尚未实现。已定稿的内容 IconActions 行(复制/分支/时钟)只挂在每个轮次中最后一条带 text 内容的 assistant 下;轮次中间的叙述与纯 Think 节点不带 chrome。分支会 fork 到包含该消息的轮次末尾,在 client 端递增继承标题后打开子会话,而 fork 或改名失败时源会话保持选中。
|
- **assistant 逐消息分页是预留 slot**:设计中已有图稿,尚未实现。已定稿的内容 IconActions 行(复制/分支/时钟)只挂在每个轮次中最后一条带 text 内容的 assistant 下;轮次中间的叙述与纯 Think 节点不带 chrome。分支会 fork 到包含该消息的轮次末尾,在 client 端递增继承标题后打开子会话,而 fork 或改名失败时源会话保持选中。
|
||||||
|
- **已发送的 user 消息无法编辑**:user 气泡的 IconActions 行只有时钟/复制/分支。设计中的编辑控件曾以存根形式上线,没有任何点击处理挂在其上,现已移除,等到编辑后重新发送这条完整链路存在后再提供([决策](../../../.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.md))。
|
||||||
- **others 工具行的闪光图标是手绘近似版本**:无法在本地导出设计字形的矢量几何;等到存在精确导出后再将其提升到 ui-primitives。
|
- **others 工具行的闪光图标是手绘近似版本**:无法在本地导出设计字形的矢量几何;等到存在精确导出后再将其提升到 ui-primitives。
|
||||||
- **审批面板的「始终允许此类」暂缓**:持久授权需要授权存储设计;今天只能回答允许一次/拒绝。
|
- **审批面板的「始终允许此类」暂缓**:持久授权需要授权存储设计;今天只能回答允许一次/拒绝。
|
||||||
- **TodoPanel 将过长条目截成单行省略号**:figma 条没有换行或展开入口,完整文本无法在行内读完。
|
- **TodoPanel 将过长条目截成单行省略号**:figma 条没有换行或展开入口,完整文本无法在行内读完。
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
// Shared IconActions chrome for user and assistant messages: copy live,
|
// Shared IconActions chrome for user and assistant messages: copy live,
|
||||||
// branch wired through onBranch, date-aware clock,
|
// branch wired through onBranch, date-aware clock.
|
||||||
// optional edit stub.
|
|
||||||
|
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import {
|
import {
|
||||||
IconBranchOutline16, IconCopyOutline16, IconEditOutline16, Tooltip,
|
IconBranchOutline16, IconCopyOutline16, Tooltip,
|
||||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||||
import type { ChatViewSlotProps } from '../contract/slots.ts'
|
import type { ChatViewSlotProps } from '../contract/slots.ts'
|
||||||
import { formatMessageClock, writeClipboard } from './message-chrome.ts'
|
import { formatMessageClock, writeClipboard } from './message-chrome.ts'
|
||||||
@@ -18,8 +17,6 @@ export interface MessageIconActionsProps {
|
|||||||
time: number
|
time: number
|
||||||
/** Clock before icons (user) or after (assistant). */
|
/** Clock before icons (user) or after (assistant). */
|
||||||
clock: 'start' | 'end'
|
clock: 'start' | 'end'
|
||||||
/** When true, append the stub edit control (user bubble). */
|
|
||||||
edit?: boolean | undefined
|
|
||||||
/** Fork the session at this message. */
|
/** Fork the session at this message. */
|
||||||
onBranch?: (() => void) | undefined
|
onBranch?: (() => void) | undefined
|
||||||
/** Parent layout class composed onto the actions row. */
|
/** Parent layout class composed onto the actions row. */
|
||||||
@@ -30,11 +27,11 @@ export interface MessageIconActionsProps {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy / branch (/ clock) IconActions row shared by user and assistant chrome.
|
* Copy / branch (/ clock) IconActions row shared by user and assistant chrome.
|
||||||
* @param props - Copy text, event time, clock side, optional edit, branch callback, className.
|
* @param props - Copy text, event time, clock side, branch callback, className.
|
||||||
* @returns The actions row element.
|
* @returns The actions row element.
|
||||||
*/
|
*/
|
||||||
export function MessageIconActions({
|
export function MessageIconActions({
|
||||||
text, time, clock, edit, onBranch, className, t,
|
text, time, clock, onBranch, className, t,
|
||||||
}: MessageIconActionsProps) {
|
}: MessageIconActionsProps) {
|
||||||
const day = useCalendarDay()
|
const day = useCalendarDay()
|
||||||
const onCopy = useCallback(() => {
|
const onCopy = useCallback(() => {
|
||||||
@@ -58,13 +55,6 @@ export function MessageIconActions({
|
|||||||
<IconBranchOutline16 />
|
<IconBranchOutline16 />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{edit === true && (
|
|
||||||
<Tooltip label={t('edit')} side="bottom">
|
|
||||||
<button type="button" className={css.action} aria-label={t('edit')}>
|
|
||||||
<IconEditOutline16 />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{clock === 'end' ? clockEl : null}
|
{clock === 'end' ? clockEl : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ export const MessageItem = memo(function MessageItem({
|
|||||||
text={text}
|
text={text}
|
||||||
time={node.time}
|
time={node.time}
|
||||||
clock="start"
|
clock="start"
|
||||||
edit
|
|
||||||
onBranch={onFork === undefined ? undefined : () => { onFork(node.seq) }}
|
onBranch={onFork === undefined ? undefined : () => { onFork(node.seq) }}
|
||||||
className={css.actions}
|
className={css.actions}
|
||||||
t={t}
|
t={t}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ afterEach(() => {
|
|||||||
const t: MessageItemProps['t'] = makeTranslate(zh, commonZh)
|
const t: MessageItemProps['t'] = makeTranslate(zh, commonZh)
|
||||||
|
|
||||||
describe('MessageItem arms', () => {
|
describe('MessageItem arms', () => {
|
||||||
it('user bubbles expose clock / copy / branch / edit; copy writes the text', () => {
|
it('user bubbles expose clock / copy / branch and no edit; copy writes the text', () => {
|
||||||
const writeText = vi.fn().mockResolvedValue(undefined)
|
const writeText = vi.fn().mockResolvedValue(undefined)
|
||||||
Object.defineProperty(navigator, 'clipboard', {
|
Object.defineProperty(navigator, 'clipboard', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
@@ -47,7 +47,7 @@ describe('MessageItem arms', () => {
|
|||||||
expect(screen.getByText('14:24')).toBeTruthy()
|
expect(screen.getByText('14:24')).toBeTruthy()
|
||||||
expect(screen.getByRole('button', { name: '复制' })).toBeTruthy()
|
expect(screen.getByRole('button', { name: '复制' })).toBeTruthy()
|
||||||
expect(screen.getByRole('button', { name: '在新对话中分支' })).toBeTruthy()
|
expect(screen.getByRole('button', { name: '在新对话中分支' })).toBeTruthy()
|
||||||
expect(screen.getByRole('button', { name: '编辑' })).toBeTruthy()
|
expect(screen.queryByRole('button', { name: '编辑' })).toBeNull()
|
||||||
fireEvent.click(screen.getByRole('button', { name: '复制' }))
|
fireEvent.click(screen.getByRole('button', { name: '复制' }))
|
||||||
expect(writeText).toHaveBeenCalledWith('hello bubble')
|
expect(writeText).toHaveBeenCalledWith('hello bubble')
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user