Răsfoiți Sursa

Merge pull request #1064 from deepseek-harness/worktree/hide-message-edit

cleanup(web): drop the unimplemented user-message edit control
CreatixChu 1 lună în urmă
părinte
comite
85e766fa89
29 a modificat fișierele cu 74 adăugiri și 61 ștergeri
  1. 6 0
      .agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.i18n.yaml
  2. 27 0
      .agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.md
  3. 27 0
      .agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.zh.md
  4. 3 3
      apps/web/tests/message-actions.e2e.ts
  5. 0 2
      apps/web/tests/snapshots/code-mode-round/ui.expected.md
  6. 0 2
      apps/web/tests/snapshots/cordis-tool-round/ui.expected.md
  7. 0 2
      apps/web/tests/snapshots/fresh-round-trip/ui.expected.md
  8. 0 2
      apps/web/tests/snapshots/lifecycle-chrome/reloaded.expected.md
  9. 0 2
      apps/web/tests/snapshots/live-interactions/cancel.expected.md
  10. 0 2
      apps/web/tests/snapshots/live-interactions/error-auth.expected.md
  11. 0 2
      apps/web/tests/snapshots/live-interactions/loading.expected.md
  12. 0 2
      apps/web/tests/snapshots/live-interactions/retry.expected.md
  13. 0 2
      apps/web/tests/snapshots/message-actions/ui.expected.md
  14. 0 2
      apps/web/tests/snapshots/plan-review/approved.expected.md
  15. 0 2
      apps/web/tests/snapshots/question-composer/answered.expected.md
  16. 0 2
      apps/web/tests/snapshots/queue-actions/collapsed.expected.md
  17. 0 2
      apps/web/tests/snapshots/queue-actions/editing.expected.md
  18. 0 2
      apps/web/tests/snapshots/queue-actions/ui.expected.md
  19. 0 2
      apps/web/tests/snapshots/seeded-history/command-row.expected.md
  20. 0 2
      apps/web/tests/snapshots/seeded-history/ui.expected.md
  21. 0 2
      apps/web/tests/snapshots/steering/mid-steer.expected.md
  22. 0 2
      apps/web/tests/snapshots/steering/settled.expected.md
  23. 0 2
      apps/web/tests/snapshots/web-search-round/ui.expected.md
  24. 2 2
      packages/client/ui-conversation/README.i18n.yaml
  25. 1 0
      packages/client/ui-conversation/README.md
  26. 1 0
      packages/client/ui-conversation/README.zh.md
  27. 4 14
      packages/client/ui-conversation/src/client/chat/MessageIconActions.tsx
  28. 1 2
      packages/client/ui-conversation/src/client/chat/MessageItem.tsx
  29. 2 2
      packages/client/ui-conversation/tests/chat-branch-tails.spec.tsx

+ 6 - 0
.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.i18n.yaml

@@ -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

+ 27 - 0
.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.md

@@ -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.

+ 27 - 0
.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.zh.md

@@ -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 改动。

+ 3 - 3
apps/web/tests/message-actions.e2e.ts

@@ -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)
 
     // Focus-reveal the footers (hover:hover keeps them opacity-hidden until
-    // hover/focus-within). User has three actions; each turn's last content
-    // assistant has copy + branch.
+    // hover/focus-within). User and each turn's last content assistant both
+    // have copy + branch.
     const copyButtons = page.getByRole('button', { name: 'Copy' })
     await expect.poll(() => copyButtons.count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(2)
     await copyButtons.first().focus()
     await expect.poll(() => page.getByRole('button', { name: 'Branch into a new conversation' }).count(), { timeout: 5_000 })
       .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)
 
   it.skipIf(MODE === 'record')('matches the conversation aria golden with IconActions and clocks', async () => {

+ 0 - 2
apps/web/tests/snapshots/code-mode-round/ui.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/cordis-tool-round/ui.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/fresh-round-trip/ui.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/lifecycle-chrome/reloaded.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/live-interactions/cancel.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/live-interactions/error-auth.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/live-interactions/loading.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/live-interactions/retry.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/message-actions/ui.expected.md

@@ -10,8 +10,6 @@
 - tooltip "Copy"
 - button "Branch into a new conversation":
   - 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.":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/plan-review/approved.expected.md

@@ -10,8 +10,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/question-composer/answered.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/queue-actions/collapsed.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/queue-actions/editing.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/queue-actions/ui.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/seeded-history/command-row.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - 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.":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/seeded-history/ui.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - 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.":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/steering/mid-steer.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/steering/settled.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 0 - 2
apps/web/tests/snapshots/web-search-round/ui.expected.md

@@ -9,8 +9,6 @@
   - img
 - button "Branch into a new conversation":
   - img
-- button "Edit":
-  - img
 - button "Context injection":
   - img
   - img

+ 2 - 2
packages/client/ui-conversation/README.i18n.yaml

@@ -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-conversation/README.md
-README.md: b183aab56a10c3a22eeab31f8b1b0e6b5219beba
-README.zh.md: af6427774a048de324d57bc9856f77547e9a03cc
+README.md: 7f428fd50411a8ee3b8e7e133d6465a77681900e
+README.zh.md: e591072eb95ed461bb26b6636f00e24ab7ad2dfa

+ 1 - 0
packages/client/ui-conversation/README.md

@@ -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.
 - **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.
+- **Sent user messages cannot be edited** — the user bubble's IconActions row carries clock / copy / branch only, and branching from the message is the nearest gesture. The control returns with the capability behind it: a client mutation over a settled user message, plus the host behavior for the turn that already consumed it ([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 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.

+ 1 - 0
packages/client/ui-conversation/README.zh.md

@@ -47,6 +47,7 @@ todo 两个面就是在该形状上的两个注册项,都是普通注册方插
 - **统计行的耗时只覆盖窗口内消息流**:LLM(大语言模型)与工具墙钟时间由快照的 assistant `timing` 与工具 call/result 配对折算,落在已加载事件窗口之外的节点(更早的历史)不计入。
 - **详情面板是最小形态,且当前没有入口**:以原始形式显示已选择调用的参数/结果;Input/Output/Metadata 切换、Prev/Next 步进与 See-in-trajectory 深链接暂缓实现。工具行已不再是详情面板的点击目标,且没有任何手势接替它,因此 `ChatViewInjected.openDetails` 虽已实现却无人调用,该面板(含其终端卡片)在组装后的应用中不可达;其渲染仍由直接以选中态挂载它来覆盖。
 - **assistant 逐消息分页是预留 slot**:设计中已有图稿,尚未实现。已定稿的内容 IconActions 行(复制/分支/时钟)只挂在每个轮次中最后一条带 text 内容的 assistant 下;轮次中间的叙述与纯 Think 节点不带 chrome。分支会 fork 到包含该消息的轮次末尾,在 client 端递增继承标题后打开子会话,而 fork 或改名失败时源会话保持选中。
+- **已发送的 user 消息无法编辑**:user 气泡的 IconActions 行只有时钟/复制/分支,从该消息分支是最接近的手势。该控件要与其背后的能力一起回归:既需要针对已定稿 user 消息的 client 变更,也需要 host 侧对已经消费过它的轮次给出行为([决策](../../../.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.md))。
 - **others 工具行的闪光图标是手绘近似版本**:无法在本地导出设计字形的矢量几何;等到存在精确导出后再将其提升到 ui-primitives。
 - **审批面板的「始终允许此类」暂缓**:持久授权需要授权存储设计;今天只能回答允许一次/拒绝。
 - **TodoPanel 将过长条目截成单行省略号**:figma 条没有换行或展开入口,完整文本无法在行内读完。

+ 4 - 14
packages/client/ui-conversation/src/client/chat/MessageIconActions.tsx

@@ -1,10 +1,9 @@
 // Shared IconActions chrome for user and assistant messages: copy live,
-// branch wired through onBranch, date-aware clock,
-// optional edit stub.
+// branch wired through onBranch, date-aware clock.
 
 import { useCallback } from 'react'
 import {
-  IconBranchOutline16, IconCopyOutline16, IconEditOutline16, Tooltip,
+  IconBranchOutline16, IconCopyOutline16, Tooltip,
 } from '@deepseek-ai/dsh-client-ui-primitives'
 import type { ChatViewSlotProps } from '../contract/slots.ts'
 import { formatMessageClock, writeClipboard } from './message-chrome.ts'
@@ -18,8 +17,6 @@ export interface MessageIconActionsProps {
   time: number
   /** Clock before icons (user) or after (assistant). */
   clock: 'start' | 'end'
-  /** When true, append the stub edit control (user bubble). */
-  edit?: boolean | undefined
   /** Fork the session at this message. */
   onBranch?: (() => void) | undefined
   /** 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.
- * @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.
  */
 export function MessageIconActions({
-  text, time, clock, edit, onBranch, className, t,
+  text, time, clock, onBranch, className, t,
 }: MessageIconActionsProps) {
   const day = useCalendarDay()
   const onCopy = useCallback(() => {
@@ -58,13 +55,6 @@ export function MessageIconActions({
           <IconBranchOutline16 />
         </button>
       </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}
     </div>
   )

+ 1 - 2
packages/client/ui-conversation/src/client/chat/MessageItem.tsx

@@ -1,5 +1,5 @@
 // MessageItem: simple chat nodes — user bubble (right-aligned, with
-// clock + copy / branch / edit IconActions), steering (badged bubble), context
+// clock + copy / branch IconActions), steering (badged bubble), context
 // injection, retry disclosure, and unknown-surface JSON rows.
 
 import { memo, useEffect, useMemo, useState } from 'react'
@@ -156,7 +156,6 @@ export const MessageItem = memo(function MessageItem({
             text={text}
             time={node.time}
             clock="start"
-            edit
             onBranch={onFork === undefined ? undefined : () => { onFork(node.seq) }}
             className={css.actions}
             t={t}

+ 2 - 2
packages/client/ui-conversation/tests/chat-branch-tails.spec.tsx

@@ -27,7 +27,7 @@ afterEach(() => {
 const t: MessageItemProps['t'] = makeTranslate(zh, commonZh)
 
 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)
     Object.defineProperty(navigator, 'clipboard', {
       configurable: true,
@@ -47,7 +47,7 @@ describe('MessageItem arms', () => {
     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.queryByRole('button', { name: '编辑' })).toBeNull()
     fireEvent.click(screen.getByRole('button', { name: '复制' }))
     expect(writeText).toHaveBeenCalledWith('hello bubble')
   })