Repository navigation
Inbox: reply drafts and the Draft label - #2028
Open
backnotprop wants to merge 19 commits into
Open
backnotprop wants to merge 19 commits into
backnotprop wants to merge 19 commits into
Conversation
Words typed in a thread's reply box are kept per thread as a server
draft (inbox/reply-drafts/<thread id>.json, outside the event log, a
compare-and-set on updated_at), saved while typing and put back in the
box, without focus, when the thread opens again: across leaving the
thread, a reload and a restart. Cancel keeps them, Discard drops them,
and the Send that carries them clears them (clear_draft on the reply).
"Draft" replaces "Not sent": the card's tag and footer, the thread's
foot ("Draft · 2 picks" before Send and Edit draft), a soft pill with
"Saved 4:43 PM · Discard" above the box, and the list row's count slot
("Draft · Your reply", from the row's additive `unsent`). A thread
holding unsent work (words, picks, comments) never sinks to Quiet
unless archived: it sits in New, not bold for the person's own words.
The sidebar's Drafts row counts these threads and its page lists them
under "Replies not sent". The phone's door reads a draft (7.61).
Approval "inbox-reply-drafts" (variant Label).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # AGENTS.md # packages/inbox/api.ts # packages/inbox/components/InboxList.tsx # packages/server/inbox.ts # packages/shared/inbox/store.ts
The reply box now reopens by itself with the thread's kept words, without focus, and the foot's button reads Edit draft while picks wait, so the spec opens the box through one helper that handles both and focuses it as a click on Reply does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An earlier test's words come back as the thread's reply draft and reopen the box by itself, so the phone test found no Reply button and waited out its timeout. The helper now takes the box either way and starts it empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The drafts event shared the record event's 80 ms debounce timer, so a draft saved just after an agent's message arrived cleared the pending reads, the open thread's included: the list showed the new message and the thread did not (seen in inbox-replaced.spec.ts under load). It has its own timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes for #2028: - A Send waits for the draft's saves before it posts, and each thread has a generation that Send and Discard raise, so a save queued earlier does nothing: a slow reply can no longer bring the words back as a draft. - clear_draft names the updated_at the sent words were saved as; the server clears that draft only, so a newer draft from another window stays. The foot's Send (box closed) reads the draft again, and an event: drafts for the open thread refreshes its copy. - A save sent as the page goes may name base_body (the words the window saved last), so a base a step behind no longer drops it. - Agents read a thread's section without the person's unsent work. - Search drops a thread's cached facts when its draft or a comment changes. - A row's unsent comment count is kept per thread instead of scanned. - inbox-reply-drafts.ts names contract 7.61. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-ask (#2018) took contract 7.61, so the reply draft's door row is 7.62. A Re-ask that waits keeps its row in Sent (or Quiet after a Mark read) even when the thread holds unsent work; the row still shows its Draft label.
… not marked again - list.ts test for the merged rule: a Re-ask that waits keeps the row in Sent (Quiet after a Mark read) whatever the draft; unsent work keeps it out of Quiet otherwise. - A thread whose Mark read still stands is no longer offered Mark read (the row's hover, Mark all as read): unsent work can keep it in New, and marking it again then undoing dropped the earlier Mark read (found by the project menu's property test once Re-ask and drafts met). - store.test.ts: a brace the merge dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es over Re-review fixes for #2028: - A Send claims the thread when it starts: the words waiting are saved, every save in flight is answered, the generation is raised and no save of that thread runs until the Send ends or fails (then the words are saved again). The box is read-only meanwhile. Typing during a slow Send no longer brings back a draft holding the sent words. - A save the server refuses (409) is never retried over the newer state: the window adopts the draft as the server holds it and keeps its words, not saved, with Keep mine / Use theirs. A tab whose draft another tab sent no longer re-creates it. Bases are kept per thread, so the save on leaving a thread no longer fetches the latest base and writes over it. - inbox-mobile.md: the draft's allowlist row names 7.62. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ict is smaller Round 3 fixes for #2028: - reload (on event: drafts) takes the box's words: unchanged, nothing moves; no edits since the last save or read, the box takes the new words or closes when the draft is gone; edits, the base stays, the save is refused and the conflict shows. Sent words no longer come back, and another window's newer words are no longer overwritten. - The conflict shows only when both windows hold words: one line above the box with Keep mine and Discard (only this window's words go). It is said in the thread's live region, named by the textarea's aria-describedby, focus returns to the box after either button, and the buttons sit far enough apart that their hit areas never meet. - An empty box refused shows nothing and never deletes the other draft. A Send after a conflict leaves the other window's draft showing. - A Send whose answer is lost after it went is treated as sent for the draft (read back by its idempotency key): its words are never saved again by themselves; Send again replays it. - The restart guard and a beforeunload question ask while words are not saved (replyDraftUnsaved, now read by unsentReply). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/inbox/components/InboxList.tsx # tests/e2e/playwright.config.ts
…lost A beforeunload listener on every page made Chromium run it before each navigation, which changed the order of requests around a reload (seen in inbox-send-now.spec.ts) and keeps some browsers from caching the page for Back. unsent.ts now adds it only while files pasted into the box, a save on its way or a conflict's words would be lost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds outlive the pane Round 4 fixes for #2028: - A Send whose reply may have gone (its answer lost) keeps its exact requests per thread (keys, targets, comments, clear_draft); Send again with the same words posts them unchanged, so the server replays what went and never takes a second reply, comments included. - When it is not known whether the reply went (the Inbox was down), the draft is released, not forgotten: it stays on the server, and the retry's clear_draft names it, so no draft of the sent words is left behind. - Words a conflict left unsaved are kept per thread at module level, so closing or switching the thread never loses them; they come back with the line when it reopens. - The conflict's second button reads Discard mine. - Contract 7.62: the door forwards clear_draft unchanged, so the phone must not send it for words it did not prefill from that draft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # tests/e2e/playwright.config.ts
Round 5 fix for #2028. The retry plan replayed old requests whenever the words matched, so a later reply could go to an old message, a newly pasted file could be dropped and a new comment left out. It is gone. After a Send whose answer is lost, the thread is read for the reply's key: found, the Send is finished (the box closes); not there, the next Send is an ordinary new one. When the thread cannot be read either (the Inbox is down), the key, words and carried uploads are kept for 10 minutes, and the next Send of the same words looks for that reply first: found, nothing goes again; not there, an ordinary new Send of what is in the box now. The files a Send leaves the box with are those its requests carried. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s added is named Round 6 fixes for #2028: - The remembered unconfirmed Send is forgotten as soon as a thread read shows its reply landed (finishing the Send while the box still holds its words), when the words change or the box empties, when the person leaves the thread, and after 10 minutes. A later reply of the same words is always an ordinary Send. - When the next Send finds the old reply, anything added since (comments, picks, files it did not carry) stays for an ordinary Send and is named: "Your reply went. Not sent yet: 1 pick. Press Send to send it." Said in the live region on every path. - A thread read answered 404 says the thread was deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y is Round 7 for #2028: - When the words change or empty after a lost Send, the error is replaced by an honest line once the thread is read ("Your earlier reply went; Send sends your edited words as another reply.", or "may have gone"), and a save refused because this window's own Send cleared the draft saves the edited words as the new draft, never a conflict line about another window (expectOwnClear). - A thread read that finds the reply names what was added since in the live region, the box closed too. - Cancel after a lost Send reads the reply back; when it went, the draft copy is read again, so no stale "Draft · Your reply" foot stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Words typed in a thread's reply box are kept per thread as a server draft (
inbox/reply-drafts/<thread id>.json, outside the event log, compare-and-set onupdated_at). They are saved while typing and put back in the box, without focus, when the thread opens again, after a reload and after a restart. Cancel keeps them, Discard drops them, and the Send that carries them clears them (clear_drafton the reply)."Draft" replaces "Not sent":
unsent)A thread holding unsent words, picks or comments never sinks to Quiet unless archived. It stays in New, not bold when only the person's own words wait. The sidebar's Drafts count includes these threads, and the Drafts page lists them under "Replies not sent". The phone's door can read a draft (contract 7.61).
Approval:
.product/approved/inbox-reply-drafts/(variant Label).The window reads the list and the Drafts again on
event: drafts, on a debounce timer of its own, so a draft save never cancels the reads a just-arrived message needs.Tests: typecheck; the Inbox DOM tests; the shared, core and server inbox tests; and the Playwright e2e on the compiled binary, including the new
inbox-reply-drafts.spec.tsandinbox-dictation-paste.spec.ts. The dictation spec now opens the reply box through a helper, because a kept draft reopens the box by itself;inbox-tags.spec.tsdoes the same.