Repository navigation
perf(messages): close the editor and delete dialog on submit - #3493
Conversation
saving an edit or confirming a delete kept the composer editor or the dialog open until the relay accepted the operation, about one round trip after the row already showed the change. close them on submit instead, a frame after the optimistic row update so the press paints first (useCloseAfterPaint). a failure that arrives before the close keeps them open with retry; a later one is reported by the existing message status, which already survives a closed dialog and a removed row and retries the same operation. an empty edit that becomes a delete now returns the composer to its draft when the deletion is sent, so the per-operation acceptance callbacks are removed. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Kalvin Chau <[email protected]>
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 looks good. the shared close hook cancels both the frame and the timer if a failure lands first, so in-place Retry survives, and MessageManagementStatus still owns edits and deletes that fail after the close. two nonblocking notes:
- nonblocking: since Save and Delete now close before the relay confirms, a later failure only shows up as Retry message update outside the editor or dialog. might be worth a line in
docs/buzz-help.mdso that recovery path is discoverable.
| }} | ||
| dispatched={(dialog) => | ||
| // The row hides now; MessageManagementStatus owns a failed delete. | ||
| selection.done?.( |
There was a problem hiding this comment.
🤖 nonblocking: on the empty-edit → delete path, done runs editing.close() → restore() synchronously inside the Delete click, so the composer's draft-restore render lands in the press task. that's the same-task close the PR measured as raising INP, and only the dialog close is deferred here. could route done through the same after-paint close if it shows up in the numbers
There was a problem hiding this comment.
🤖 Agreed, this is accurate. On the empty-edit → delete path, the draft restore still renders inside the Delete click. That path is rare and the A/B did not cover it, so I have no numbers for it. Routing done through the same after-paint close is a small follow-up. I will add it to this PR if the owner wants it before merge.
|
🤖 Thanks. On the |
* origin/main: (153 commits) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) fix(solo): keep navigation responsive during chat deletion (#3488) fix(channels): keep custom thread tab names until the tab closes (#3458) perf(relay): verify cached history in the native host (#3426) fix(settings): refine claude and typesafe key setup dialogs (#3391) Update bundled help for Bestie and agent settings (#3194) feat(profiles): share and show a local time zone (#3332) Revert "feat(link-previews): unfurl web links below messages by default" (#3480) Show disabled plugin chats in Solo (#3456) perf(messages): host one context menu per message list (#3465) test(claude): keep the quit-release test copy above the minimum version (#3477) fix(profiles): restore verified names from the device cache (#3382) fix(mentions): show addressed agents first in Solo audience picker (#2856) perf: count member access once and scope find highlights to painting (#3464) perf(messages): prepare a message once for its renderer and link previews (#3466) fix(composer): speed up long pastes and paste office text as text (#3441) ... # Conflicts: # docs/plugin-authoring-guide.md
…when-ready * origin/main: (21 commits) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) fix(solo): keep navigation responsive during chat deletion (#3488) fix(channels): keep custom thread tab names until the tab closes (#3458) perf(relay): verify cached history in the native host (#3426) fix(settings): refine claude and typesafe key setup dialogs (#3391) Update bundled help for Bestie and agent settings (#3194) feat(profiles): share and show a local time zone (#3332) Revert "feat(link-previews): unfurl web links below messages by default" (#3480) Show disabled plugin chats in Solo (#3456) perf(messages): host one context menu per message list (#3465) test(claude): keep the quit-release test copy above the minimum version (#3477) fix(profiles): restore verified names from the device cache (#3382) fix(mentions): show addressed agents first in Solo audience picker (#2856) perf: count member access once and scope find highlights to painting (#3464) ...
…n-on-github * origin/main: (21 commits) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) fix(solo): keep navigation responsive during chat deletion (#3488) fix(channels): keep custom thread tab names until the tab closes (#3458) perf(relay): verify cached history in the native host (#3426) fix(settings): refine claude and typesafe key setup dialogs (#3391) Update bundled help for Bestie and agent settings (#3194) feat(profiles): share and show a local time zone (#3332) Revert "feat(link-previews): unfurl web links below messages by default" (#3480) Show disabled plugin chats in Solo (#3456) perf(messages): host one context menu per message list (#3465) test(claude): keep the quit-release test copy above the minimum version (#3477) fix(profiles): restore verified names from the device cache (#3382) fix(mentions): show addressed agents first in Solo audience picker (#2856) perf: count member access once and scope find highlights to painting (#3464) ...
* origin/main: (31 commits) Let plugin panels name their tab with panels.useTabTitle (#3251) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) fix(solo): keep navigation responsive during chat deletion (#3488) fix(channels): keep custom thread tab names until the tab closes (#3458) perf(relay): verify cached history in the native host (#3426) fix(settings): refine claude and typesafe key setup dialogs (#3391) Update bundled help for Bestie and agent settings (#3194) feat(profiles): share and show a local time zone (#3332) Revert "feat(link-previews): unfurl web links below messages by default" (#3480) Show disabled plugin chats in Solo (#3456) perf(messages): host one context menu per message list (#3465) test(claude): keep the quit-release test copy above the minimum version (#3477) fix(profiles): restore verified names from the device cache (#3382) fix(mentions): show addressed agents first in Solo audience picker (#2856) ...
…ve-client * origin/main: feat(solo): smooth chat recency reordering (#3502) Reject self-mentions that cannot wake another session (#3513) feat(status): offer don't clear for every status (#3515) Let plugin panels name their tab with panels.useTabTitle (#3251) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) Signed-off-by: Philip Azar <[email protected]>
Problem
Saving an edit kept the composer in edit mode, and confirming a delete kept the alert dialog open, until the relay accepted the operation. The row already showed the change one frame after the press, through the optimistic update, but the editor or dialog stayed up for about one more relay round trip with no visible progress.
Change
useMessageEdit.ts: newuseCloseAfterPaint(active, close)closes one frame plus one task after an operation starts, so the optimistic row paints before the close render. The editor uses it while its operation is notfailed/unknown.MessageManagement.tsx:DeleteMessageDialoguses the same hook in place of its close-on-accepted effect.completions+observeSend) unused, so they are removed.Why behavior does not change
Only when the surface closes changes. What is sent, and how it is retried, stay the same.
failed/unknown) keeps the editor or dialog open with its in-place Retry, as before.MessageManagementStatus. It already outlives a closed dialog and a removed row, and retries the same operation id. That path existed before for presentation loss and navigation.Evidence
Interleaved A/B on 7 Linux and 7 macOS boxes:
Timings are taken in the page: the rAF timestamp of the first frame where the condition holds, measured from the input event. "shown"/"gone" is the row showing the edit, or the row being removed. "closed" is the editor or dialog being gone. INP is the Event Timing duration of the press.
The baseline is
main(111207e2). The measured change iseed816c3. The final commit adds the empty-edit delete change, the shared hook and tests, and is rebased onto current main.Edit (Enter in the edit composer)
Delete (Delete in the confirmation dialog)
LoAF blocking time is flat on both OSes. Script and task time are lower too, but part of that comes from the shorter sample window, so this PR does not claim it.
Video
Recorded in the browser test harness at 150 ms simulated relay latency, with no CPU throttle. Left is
main, right is this PR. Both sides are aligned on the edit's Enter. The on-screen timer counts from the press until the editor or dialog closes.close-on-submit-before-after.mp4
Checks
vitest run src/features/messages src/features/conversation: 3329 pass.failed/unknownguard is removed.MessageManagement,MessageComposer,MessageEdit.integration,EmbeddedConversation.message-edit,inbox-edit,message-actions-floating,message-management,owned-agent-message-deletion(35 tests).message-editandowned-agent-message-deletionnow expect the close on submit.bin/pnpm checkandtscare clean.🤖 Generated with Claude Code