Repository navigation
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 76c52aa2bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
76c52aa to
a0ca200
Compare
|
Parked at
Generated with |
a0ca200 to
72a15a9
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72a15a919c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Parked at
Generated with |
…(transcript-only, second Edit)
…aft, after the unsent draft
72a15a9 to
e8acc69
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8acc69cd8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Parked at
Generated with |
… once to the draft (#5808)
…d in the windowed-replay switch test
It runs on every ChatInputInner commit, stream updates included.
|
Parked at
|
|
Marked as draft. The user chose Option C for #5672 and #5571: a new PR from main, built on the rule that normal draft writes always go to the normal draft. That PR replaces this one. Only the replacement is meant to land. This PR closes as superseded after the replacement merges, with a link to it. Generated with |
Summary
Editing a sent message no longer writes the edit text into the workspace's shared, persisted draft. The edit text lives only in the editing composer's memory. A reload during an edit keeps the unsent draft (#5672). An edit in one window never reaches another window's composer (#5571).
A workspace switch ends the open edit, as it did on main before this PR. Unlike main, nothing is lost: the edit's text, files and review notes move once into that workspace's normal draft, after the unsent draft. No edit state survives a composer remount.
Fixes #5672
Fixes #5571
Part of #5808 (item 1, in a different form: a switch ends the edit and keeps its contents. Items 2-6 exist on main and stay open.)
Background
On main, entering edit mode saved a snapshot of the unsent draft and replaced the shared draft with the message text. The shared draft is persisted and synced across windows. So a reload during an edit lost the unsent draft, and other windows showed the edit text.
An earlier version of this PR kept an open edit alive across a workspace switch, in module-level memory maps. Review round 5 found 3 defects in that mechanism (duplicate edit send after a remount, notes restored into an unmounted composer, a failed refresh stuck after a switch). This version removes that mechanism.
Implementation
One ownership rule. An edit's contents (text, files, notes) have exactly one owner at a time:
Each move settles the edit session first and takes the buffer, so it runs once. Cancel and Escape discard the edit and restore the pre-edit notes, as before. An accepted send ends ownership.
useComposerDraft: a component-local edit buffer (useStatepluseditDraftRef). While an edit is open,setInputandsetAttachmentswrite only that buffer. They make no DraftStore write and no backend draft save.setDraftReviewswrites its ref and its state together, so a send that completes after unmount still reads the notes it put back. It never runs on an edit keystroke.ChatInputInner:moveEditToDraftperforms the move to the normal draft.releaseEndedEditcalls it on unmount (switch, transcript-only), after a commit that cleared the target (row deleted, refresh found no target), and from the send'sfinally. It skips sessions that are in flight or still open.onCancelEdit, so it cannot close an edit open in another workspace. It does not patch the edit target or start a transcript refresh.isMountedRefis set in the mount effect, so StrictMode's simulated unmount does not leave it false.ChatPane.tsx: main's edit slot and main's reset on a workspace switch. The only change vs main is the 2 replay guards in the "row replaced or deleted" effect (+6 lines): wait for a caught-up transcript, and keep the edit when its row is outside the replayed window (hasOlderHistory). A replay of the active workspace can empty its rows while ChatPane stays mounted.ChatInputInnerruns on every commit, stream updates included. It is O(1) with no I/O, and a code comment says so.src/browser/utils/chatEditing.ts(thecanEditDisplayedUserMessageguard is unchanged), edit target row selection, and assistant and streaming paths.Validation
New tests in
tests/ui/chat/editKeepsUnsentDraft.test.ts. "Fails before" means the test failed on its ownexpectwith the previous heade8acc69cd8. Each mutant was applied to the fix and restored afterwards, and the tree was clean after each one.requestTranscriptRefresh), M15 (the refusal replaces notes)onCancelEditwhile unmounted)isMountedRefnot set in the mount effect)hasOlderHistoryguard)hasOlderHistorytrue) ends the edit and keeps its text as the unsent draft, after the existing draft, once.editingMessageForUi, which is undefined once the send takes the text.cancel-editcommand action: it runs only after acceptance.draftStore.setTextandsetAttachmentscalls.composerDraftsFormalRepro.test.tspass unchanged. That covers the reload, the other window, transcript-only, the second Edit, the 🤖 feat: composer-draft follow-ups that need new protocol or mechanism #5226 races and memory-only attachments.editKeepsUnsentDraft.test.ts34/34.tests/ui/chat: 128 passed, 8 skipped. ChatInput unit tests: 162/162.BUGBASH_AI=mock make test-bugbash-repros: 44 selected, all passed, includingreloadDuringEditKeepsDraft.e2e.tson web and phone.make static-checkpasses.make check-react-compiler: "React Compiler coverage OK: 23/24 hot components compile (1 known skipped)."Size
Production vs main is +228/−67 in 3 files:
ChatInput/index.tsx+145/−57,useComposerDraft.ts+77/−10,ChatPane.tsx+6/0. The perf owner accepted this size. Tests are +1169/−99. The repair in this version removes the earlier module-level edit stores (about 145 added lines) and adds the ownership rule's move, put-back and notes routing.Risks
Medium, limited to the composer's edit mode.
Hazards that exist on main and stay open, tracked in #5808:
Dogfood
Bug-bash app with mock AI, two workspaces, composer values read from accessibility snapshots:
history-changedrefusal. The Retry button showed. After a switch and a return, no "refreshing" indicator showed, Send was enabled, and the composer read "unsent draft", then the edit text.The in-flight scenario recording is attached below.
Review record
e8acc69cd8: 11 assessments used. Round 5 found the 3 defects listed in Background.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$92.65s2-in-flight.webm