Skip to content

feat: inline user message editing in timeline (with image)#1683

Open
ChrisLally wants to merge 1 commit intopingdotgg:mainfrom
ChrisLally:chrislally/message-edit-review-base
Open

feat: inline user message editing in timeline (with image)#1683
ChrisLally wants to merge 1 commit intopingdotgg:mainfrom
ChrisLally:chrislally/message-edit-review-base

Conversation

@ChrisLally
Copy link
Copy Markdown

@ChrisLally ChrisLally commented Apr 2, 2026

What changed

  • Revert-eligible user bubbles: Edit (pencil) next to Revert.
  • Edit → inline text + images → Send: silent revert to that checkpoint → wait for the old row to leave the store → new user turn; main composer draft unchanged. Cancel / Escape = discard local edits only.
  • Composer Send behavior unchanged; shared submitComposerTurn + clearComposerDraft for normal vs edit resend.

Why

  • Revert is for “drop everything after this point.”
  • #331 is the usual case: fix the prompt (copy, attachment)—not a full undo mindset.
  • Edit stays in the bubble: less friction than revert → retype; bottom composer untouched.
  • Cancel / Escape: throw away local edits only—no checkpoint change.
  • Send: still the same revert + resend machinery underneath; the change is mainly affordance / UX.

UI changes

Order of assets:

  1. Before — baseline UI (no edit affordance / no inline editor).
  2. After — static screenshots showing hover actions and/or inline edit + send.
  3. Video — short clip of the interaction (hover → edit → send → thread updates). Last item in this section.

Before

Screenshot 2026-04-02 at 6 27 25 AM

After

After — timeline with edit affordance After — inline editor

Demo (video)

download.mp4

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots and a short video for UI/interaction

Credits

Inspiration and reference implementation: pingdotgg/t3code#346 — feat: Add inline user message editing (@6crucified). That PR established the core UX and mechanics (inline bubble editor, silent revert, wait for the old message row to disappear, then resend; shared send helper with a flag to preserve vs clear the composer; image attachment handling in the editor).

This change cleans up and adapts that approach for the current codebase: MessagesTimeline (instead of a monolithic ChatView bubble), the present composer/send pipeline (submitComposerTurn, terminal context handling, deriveDisplayedUserMessageState for editable text), and updated tests/harness.


Note

Medium Risk
Adds a new edit→silent revert→resend flow that coordinates UI state with thread checkpoint reverts and store synchronization, so timing/edge cases could cause message duplication, lost edits, or leaked blob URLs. Changes are mostly UI/state management but touch the send/revert pipeline.

Overview
Adds inline editing for revert-eligible user messages directly in the timeline, including a textarea editor, image add/remove (file picker + paste), and keyboard shortcuts (Esc cancel, Ctrl/Cmd+Enter send).

Implements an edit resend flow that silently reverts the thread to the message’s checkpoint, waits (with timeout) for the original message to be removed from the store, then re-sends the edited content while leaving the main composer draft untouched via a shared submitComposerTurn helper.

Introduces new helpers in ChatView.logic.ts for store-sync waiting (waitForThreadMessageRemoval) and reconstructing image attachments for editing (materializeMessageImageAttachmentForEdit), and wires new edit-related props through MessagesTimeline tests and virtualization harness.

Written by Cursor Bugbot for commit 7c2f359. This will update automatically on new commits. Configure here.

Note

Add inline editing of user messages with image support in the chat timeline

  • Adds an edit button to user messages in MessagesTimeline.tsx that opens an inline textarea pre-populated with the message text and existing image attachments.
  • Submitting an edit reverts the conversation to the target turn, waits for the original message to disappear (up to 3 seconds), then resubmits the edited content via the existing send path.
  • Users can add images via paste or file picker and remove them during editing, subject to provider attachment limits.
  • Edit, revert, and send actions are disabled while the model is working or a send is in progress.
  • Behavioral Change: onRevertToTurnCount is now async and accepts an options.confirm flag to skip the confirmation dialog; it returns a boolean indicating success.
📊 Macroscope summarized 7c2f359. 5 files reviewed, 2 issues evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 02213911-c00e-4b1f-aaa8-730db04fc9fb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 2, 2026
async () => {},
);

submitEditUserMessageFnRef.current = async (message: TimelineUserChatMessage) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low components/ChatView.tsx:3035

When a user starts editing message B while message A's edit submission is in progress, submitEditUserMessageFnRef.current awaits waitForThreadMessageRemoval and submitComposerTurn, then calls discardUserMessageEditSession() at line 3064. This clears the edit state for message B even though B was edited after A's submission started, causing the in-progress edit UI for B to disappear unexpectedly.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/ChatView.tsx around line 3035:

When a user starts editing message B while message A's edit submission is in progress, `submitEditUserMessageFnRef.current` awaits `waitForThreadMessageRemoval` and `submitComposerTurn`, then calls `discardUserMessageEditSession()` at line 3064. This clears the edit state for message B even though B was edited after A's submission started, causing the in-progress edit UI for B to disappear unexpectedly.

Evidence trail:
ChatView.tsx:3030-3066 (submitEditUserMessageFnRef.current function that performs async operations then calls discardUserMessageEditSession without session check), ChatView.tsx:504-515 (discardUserMessageEditSession definition that unconditionally clears all edit state), ChatView.tsx:3146-3164 (onStartEditUserMessage that shows session tracking mechanism exists but is not used in submit flow)

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 2, 2026

Approvability

Verdict: Needs human review

This PR adds a new feature allowing users to edit messages inline in the chat timeline with image support. It introduces significant new UI components, state management, and modifies the core message submission flow. New user-facing features of this scope warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

.catch(() => undefined);
}
if (
clearComposerDraft &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed edit-send leaves orphaned optimistic message in timeline

Medium Severity

When the edit-send flow calls submitComposerTurn with clearComposerDraft: false and the turn start fails, the optimistic message added at line 2814 is never removed. The entire error-recovery block (removing the optimistic message and restoring the composer draft) is gated on clearComposerDraft, but only the draft-restoration part needs that gate. Since the revert already succeeded irreversibly, the user is left with a persistent ghost message in the timeline that no existing cleanup mechanism will remove (the server-sync effect only cleans up messages the server acknowledges, and the thread-change effect only runs on navigation).

Additional Locations (1)
Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant