Skip to content

fix(web-ui): keep image attachments when editing a user message - #3167

Merged
kev1n77 merged 1 commit into
GCWing:mainfrom
kev1n77:fmy/bugfix
Sep 21, 2026
Merged

kev1n77 merged 1 commit into
GCWing:mainfrom
kev1n77:fmy/bugfix

Conversation

@kev1n77

@kev1n77 kev1n77 commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Editing a user message that carried image attachments and rerunning it dropped the images. An edit-rerun is a rollback plus a fresh submission: the rerun callback forwarded only sessionMutationLeaseId, so the new Turn was created without attachments while the rollback had already deleted the Turn whose userMessage.metadata.images held them. The rerun now rebuilds the message's image contexts and passes them through imageContexts / imageDisplayData, the same way rollback and the failed-message refill path already restore attachments.

Type and Areas

Type: Bug fix

Areas: Web UI (FlowChat message list, message edit service, locales)

Motivation / Impact

Problem. With an image message, the edit path lost the attachments permanently — not just for the new Turn, since the rolled-back Turn was the only place their metadata lived. The edit composer is a text-only surface: images are composer-owned imageContexts that render outside the editor and never enter composerPresentation, and the normal submission path in useMessageSender is the piece that turns them into a payload. The edit rerun skipped that step.

What changes for users. Editing a message that has images keeps them and resubmits them unchanged alongside the edited text. Images stay non-editable: the compact editor still owns text only, and the thumbnails remain outside it — the layout already pinned by UserMessageItemActions.test.ts. The rerun now matches what that layout implies.

Removed dead code that contradicted this behavior. canEditUserMessage() in UserMessageEditService returned false for any message with hasImages and had no caller; UserMessageItem reimplemented the availability check and omitted the image condition, which is what let an image message reach the editor at all. message.editDisabledImages in en-US / zh-CN / zh-TW was the copy written for that gate, and it is removed with it.

Scope. No Rust, protocol, or remote-scenario surface is touched. Editing is already disabled for remote and dispatch sessions (canEditBase), and rollback_session_to_turn is declared Unsupported for remote workspaces, so this adds no new remote exposure.

Verification

pnpm --dir design-system run build:packages
pnpm --dir src/web-ui exec vitest run src/flow_chat/components/modern/UserMessageItem.test.tsx src/flow_chat/components/modern/UserMessageEditComposer.test.tsx src/flow_chat/services/UserMessageEditService.test.ts
pnpm --dir src/web-ui exec tsc --noEmit
pnpm --dir src/web-ui exec eslint src/flow_chat/components/modern/UserMessageItem.tsx src/flow_chat/services/UserMessageEditService.ts
pnpm run i18n:audit
pnpm run i18n:contract:test

Results: 3 test files / 43 tests passed; tsc --noEmit exit 0; eslint 0 errors; i18n:audit passed with 0 warnings; i18n:contract:test 37/37 passed. The design-system build is listed first because @openbitfun/ui type output must exist for tsc to resolve it; it is not a change in this PR.

New tests in UserMessageItem.test.tsx: resubmits the original image attachments when editing a message asserts the restored ImageContext reaches buildImagePayload (id, image name, host path, isLocal: true) and that flowChatManager.sendMessage receives the resulting imageContexts / imageDisplayData together with the edit's sessionMutationLeaseId; reruns an edit without attachments when the message has no images is the control case asserting the send options stay exactly { sessionMutationLeaseId }.

Manual checks: not performed. Please confirm in-app: send a message with an image, edit the text, save, then verify the reran Turn renders the original thumbnails and the model receives the image.

Reviewer Notes

  • No visual change to the editor, so there is no before/after screenshot to attach. The edit layout and thumbnail placement are unchanged; the visible difference is that the reran Turn renders its thumbnails again.
  • Reuse, not new plumbing. Attachments are rebuilt with restoreImageContextsFromPayload (already used by rollback and the failed-message "fill to input" path) and handed to the send path through buildImagePayload, the same call normal submission uses.
  • Known degradation, pre-existing. If an attachment has neither a persisted data_url nor a still-readable host image_path, the rerun can only carry its name — exactly how the existing failed-message refill path degrades.
  • Rollback guidance. Single self-contained commit; revert is sufficient.
  • AI-assisted. Fully tested at the automated level recorded above; no manual UI run was performed in this environment.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable.

An edit-rerun is a rollback plus a fresh submission. The rerun only
carried the text the edit composer owns, so the attachments stayed out
of the new turn while the rollback had already deleted the turn that
held them, and the images were gone for good.

Rebuild the message's image contexts the way rollback and the
failed-message refill already do, and pass them to the rerun as
imageContexts/imageDisplayData.

Drop canEditUserMessage and message.editDisabledImages with it. That
gate forbade editing any message that carried images and had no caller;
images stay uneditable, but they are carried through an edit.
@kev1n77
kev1n77 merged commit 9216d91 into GCWing:main Sep 21, 2026
9 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.

1 participant