Skip to content

fix(chat): stop duplicating pasted-text attachments when editing a sent message#273

Merged
su-fen merged 2 commits into
mainfrom
fix/edit-duplicate-pasted-text-attachment
Jul 25, 2026
Merged

fix(chat): stop duplicating pasted-text attachments when editing a sent message#273
su-fen merged 2 commits into
mainfrom
fix/edit-duplicate-pasted-text-attachment

Conversation

@coder-hhx

@coder-hhx coder-hhx commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

问题

复制一段长文本粘贴到输入框时,会被转成"粘贴文本"附件并在正文里插入 [Pasted text N: 相对路径] 标记;发送后只读气泡会把该附件从卡片列表里隐藏,只用 PastedTextChip 把它渲染成内嵌的绿色 tag。

点击该用户气泡上的"编辑"按钮后,编辑态直接复用了未经过滤的附件列表,同时文本框展示的是原始文本(仍带着 [Pasted text N: 路径] 标记)。于是同一份粘贴内容被展示了两次:一次是附件卡片,一次是文本框里那段本该被渲染成 tag 的原始标记文字。

agent-gui(桌面主实现)与 agent-gateway/web(网关 WebUI 镜像)的 EditableUserMessageBubble 是各自独立实现,同样的疏漏两边都存在。

修复

复用只读气泡已经在用的 splitUserAttachmentsForDisplay,让编辑态的附件卡片列表也按"文本里是否还带着该文件的标记"过滤;提交/重发时仍然使用完整、未过滤的附件列表,因此不会因为隐藏了卡片就把该附件从消息里丢掉。

  • crates/agent-gui/src/pages/chat/transcript/EditableUserMessageBubble.tsx
  • crates/agent-gateway/web/src/components/GatewayTranscript.tsx(镜像同步)

测试

  • crates/agent-gui/test/chat/editable-user-message-bubble.test.mjs(新增,4 个用例):
    • 标记存在时隐藏重复卡片 / 无标记时正常显示全部附件 / 一条消息引用多段粘贴时每个标记各自隐藏对应卡片 / 陈旧标记(对应附件已不在列表)不误伤任何卡片。
    • 其中前者与「多段粘贴」两个用例已对旧代码验证过必现失败,确认是真实回归守卫。
    • 本仓库测试仅有 renderToStaticMarkup(无 jsdom/testing-library),无法驱动 state 变更,因此不含「边打字边删标记」的实时用例;useMemo 依赖为 [draftAttachments, draftText]
  • agent-guitest:frontend(1251 用例)、tsc --noEmitbiome lint 全部通过;lint 警告数与 main 基线一致(365 条,均为历史遗留、与本次改动无关)。
  • agent-gateway/webtsc --noEmitnode --test(416 用例)全部通过;biome check src/ 306 条警告,与 main 基线逐条一致(全为历史遗留)。

无用户可见的行为回归——粘贴附件在发送/重发链路上的内容不变(提交时仍使用完整、未过滤的附件列表),只是编辑视图不再重复展示。

coder-hhx and others added 2 commits July 26, 2026 00:55
…nt message

Pasting a long block of text turns it into an uploaded "pasted text" file
referenced inline by the message via a "[Pasted text N: path]" marker,
rendered as a chip once the message is sent (PastedTextChip /
splitUserAttachmentsForDisplay). Clicking "edit" on that sent user bubble
reused the raw, unfiltered attachment list for the editor's attachment-card
row instead of applying the same split the read-only bubble already uses,
so the pasted text showed up twice: once as a floating attachment card, and
once as the raw "[Pasted text N: path]" marker text inside the editable
textarea.

Reuse splitUserAttachmentsForDisplay inside EditableUserMessageBubble so
the attachment-card row only shows files not already referenced by a
marker in the (live) editable text. The full, unfiltered attachment list
is still what gets submitted on resend, so nothing is dropped from the
message.

Mirror the same fix into the gateway WebUI (crates/agent-gateway/web).

Add regression coverage in agent-gui verifying the duplicate card is
hidden while the marker is present, still shown when there is no marker,
and reappears if the marker is edited away.
The third case claimed to prove the card list reacts "live" as the marker
is retyped, but it only rendered once with a marker-free initialText — so
it asserted the same thing as the case above it and never drove a state
transition. There is no DOM test environment here (renderToStaticMarkup
only), so a genuine retype cannot be expressed; drop the misleading name
rather than keep a test that overstates what it checks.

Replace it with two cases that cover ground the suite was missing:

- multiple markers in one message must each suppress their own card
  (fails against the pre-fix component, so it is a real guard)
- a stale marker whose file is no longer attached must hide nothing,
  pinning the match to relativePath rather than label or position

test:frontend 1251/1251, tsc --noEmit clean, biome unchanged at 365
warnings. No source changes.
@su-fen
su-fen merged commit 740530a into main Jul 25, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants