Skip to content

fix(navigation): keep empty new chats out of Back history - #1617

Open
morgmart wants to merge 11 commits into
mainfrom
morganm/new-chat-back-nav
Open

morgmart wants to merge 11 commits into
mainfrom
morganm/new-chat-back-nav

Conversation

@morgmart

@morgmart morgmart commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #1622 (merged), which made a saved tag one the person typed.

What

Back no longer lands on an empty New chat or New message page.

A new conversation page is a Back stop only once it holds something you wrote: text (including a tag you typed), an attached chat, or files. Until then, the next page you open takes its place in history. That covers clicking New chat in two places and then sending, leaving an empty draft for another chat, and starting a chat from a draft.

  • One draft definition. The page checks the same hasDraftContent (text, an attached chat, or files) that the Drafts list uses, so Back and Drafts cannot disagree about what a draft is. It reads the composer's latest edit before the saved copy, so a draft that could not be saved (storage full) still counts. New message also counts a message the outbox holds for delivery or retry when no draft is saved.
  • A new chat's default agent tag is shown, not saved. The composer seeds it in memory (feat(solo): sync the main agent choice to the account #1665 on main now does this too), so an untouched new chat holds nothing a person wrote. Typing saves the draft, tag included. Every saved draft is kept as the person's, including a tag-only one.
  • One navigation rule. The page showing the visit registers PageNavigation.passThrough(passes). When anything opens, the controller asks the current visit whether it passes. A passing visit leaves the way a dismissal does: if the page opening is the one it was opened from, history returns to that entry; otherwise the new page replaces it. This uses the legacy return/replace path, which retires no visits (see navigation history). The controller doesn't need to know about routes, and the page owns emptiness, including in-memory files.
  • Settings is exempt. Settings returns to the visit it opened over, so it never replaces it. A page whose check throws keeps its visit.
  • A Solo start that fails now replaces the never-created chat with its draft.

Tradeoffs

Replacing keeps any forward entries. After going Back to an empty draft and opening another page, Forward can still go to a page you visited before. The most common case is Settings, after closing it. The alternative was leaving the empty draft as a Back stop.

Linked chat drafts (started from a message) count as drafted while their source is unused, even with no agent to tag, because they hold their source chat and can only be recreated from that message.

Earlier releases saved a new chat's automatic default tag as its draft. That shape matches a tag-only draft someone typed, so it is kept: an upgrading user's untouched New chat stays a Back stop until they type or send in it.

Testing

  • Controller: a passing visit is replaced, returning to its opener reuses that entry instead of adding a duplicate, a drafted one stays, Settings never replaces, a Settings round trip still replaces, an unregistered visit stays, and a throwing check keeps the visit.
  • Solo page: an auto tag only passes and is not saved, typing stops it passing, a sent draft passes again, an attached file counts, and a linked draft stays, with or without an agent, until its source is used. A typed draft whose save fails stops it passing. A saved draft that only tags the default agent stays drafted and is not cleared.
  • New message: recipients alone pass, typed text stops it passing, including when its save fails, a sent message passes again, and a failed message held only by the outbox stays drafted.
  • The regression tests for failed saves, returning to the opener, a sent New message, a saved tag-only draft and an outbox-held message each fail without their fix and pass with it.
  • Rebased onto main at e210c8a: bin/pnpm typecheck and biome clean; Vitest over app, navigation, messages, direct-messages, solo, channels, agents, inbox and huddle: 238 files, 4397 tests passed.
  • Not yet done: a human trying it in the running app.

Screenshots

None. The change is to Back and Forward history only, with nothing new on screen.

@morgmart
morgmart requested a review from a team as a code owner October 26, 2026 02:47
@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from bce4e0e to 270a3ee Compare October 26, 2026 02:55

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot lanes.

One P2 is inline: preserve the Back stop for a fresh linked draft even when no agent can be tagged. The documented forward-history tradeoff is respected.

Reviewed head 270a3ee6d1dabcb3cffece858540bff2189c7484 against stacked base d06b82c9801a92477e854f4fb9e0c6a803fa852e (#1531). Source-only; no tests or app execution. Browser/native history, focus, accessibility and human acceptance remain unverified; the single CI snapshot still had checks running. No approval.

Comment thread src/features/messages/draft-visit.ts Outdated
@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from 270a3ee to 750e97c Compare October 26, 2026 03:11

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account.

The prior linked-draft P2 remains unresolved. Both feature patches and all 15 feature-file blobs are unchanged; the three-file base update adds no new actionable finding in this review.

Solo follow-up under the unchanged-patch exception to the prior Guardians review. Head 750e97c0aebe5868971dd495e77a20546b54d999; base a0ab737dc698c7d97a91a7d0703612b2d2eab773. Source only; tests, CI status and live navigation/focus behavior were not verified.

Base automatically changed from morgan/tag-only-drafts to main October 26, 2026 05:54
@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from 750e97c to 3bebfee Compare October 26, 2026 05:55

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot lanes.

No changes requested: the prior linked-draft P2 is repaired by reading the live initial source while preserving saved edits and clearing the Back stop after consumption.

Reviewed head faa04399f65f74526817899fef76ab69c18e2aed against base c40dfdf4c81aabe759f3a4329c3e77f854746a59; the documented Forward-history tradeoff is respected. Source-only review: no tests, CI verification, app execution or rendered focus/accessibility validation.

@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch 2 times, most recently from a2a63aa to db9f37e Compare October 26, 2026 18:58
@morgmart
morgmart changed the base branch from main to auto-tag-bug October 26, 2026 18:58

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 No changes requested: the default-agent tag now stays in memory until an edit, and the prior linked-draft repair remains intact.

Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot reviews, covers head db9f37efc777f800dc89d60ac40280f6b792d5bb against base 2c8d877dda3867249789a749a4d7e3ea98979483. The documented Forward-history tradeoff is preserved.

Source-only: no tests, CI verification or app execution; browser/native history, editor timing, focus and accessibility remain unverified.

@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from db9f37e to bc363c0 Compare October 27, 2026 02:35

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 No changes requested: the linked-draft Back-stop repair remains intact after rebasing onto #1622, and the in-memory default tag remains compatible with the updated draft rules. Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot lanes; head bc363c0f7895bd077b89d6e895f9b6d15fe3faec, base 454d838c733e36bc0cdafc16aef9026462a217ec. Source-only: no tests or app execution, CI unassessed; native/browser history, rendered focus and performance remain unverified, and #1622’s documented detached-Huddle reopen limitation remains deferred.

Base automatically changed from auto-tag-bug to main October 28, 2026 11:15

@kalvinnchau kalvinnchau left a comment

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.

🤖 Two actionable findings: failed persistence removes authored drafts from Back history, and legacy saved automatic tags retain empty New chat visits. Details and reproduction evidence are inline.

Comment thread src/features/messages/draft-visit.ts Outdated
Comment thread src/features/messages/MessageComposer.tsx Outdated
@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from bc363c0 to 1740d75 Compare October 29, 2026 00:49

@kalvinnchau kalvinnchau left a comment

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.

🤖 Two remaining findings: duplicate Back entries when returning to an empty draft’s opener, and missing regression coverage for the sent New message reset. The previous failed-save and legacy-tag findings are repaired.

Comment thread src/features/navigation/controller.ts Outdated
Comment thread src/features/direct-messages/NewMessage.tsx
@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from d5ba6d6 to 29d38cb Compare October 29, 2026 03:18

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 One P2 is inline: the new legacy-tag cleanup also discards a deliberately typed tag-only draft on reopen. The prior linked-draft repair remains intact; the documented Forward-history and recipient-only behavior are preserved.

Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot lanes. Head 29d38cbea88122126fa1915f8783820ae1e17f62; base 91e25346668d7ce6f5e38ea53d75b447a75d576f. Source-only: no tests, CI verification or app execution; native/browser history, rendered focus, accessibility and human acceptance remain unverified.

Comment thread src/features/messages/MessageComposer.tsx Outdated

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 No code changes requested: the prior typed-tag finding is repaired by preserving saved drafts. Optional: update the description’s behavior and testing sentences that still say saved default-tag drafts are cleared.

Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot lanes. Head 5a7c58244230fb692029a0797eb2ff58e7a63ce0; base 91e25346668d7ce6f5e38ea53d75b447a75d576f. Source-only: no tests, CI verification or app execution; native/browser history, rendered focus and accessibility remain unverified.

@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from 5a7c582 to 579175c Compare October 29, 2026 04:32

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 No required changes: the typed-tag and linked-source repairs remain intact after the history/dismissal rebase. Optional: in NewMessage, include the recovered outbox draft in the pass-through predicate when its disposable draft view is absent; otherwise Back can skip a visible retryable message (the durable message remains recoverable).

Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot lanes. Head 579175c0ddcf1f37c32346e72a5eec84255db899; base 61695ec6e365f7ba002b09a51d7b040cd2935f2a. Source-only: no tests, CI verification or app execution; native/browser history, rendered focus and accessibility remain unverified.

morgmart and others added 7 commits October 29, 2026 06:10
A New chat or New message page that holds nothing the person wrote is
not a place to come back to. The page reports this to navigation, and
the next page opened from it takes its place in history. A draft with
text beyond its tags, an attached chat, or files stays a Back stop,
using the same draft definition as the Drafts list. Settings never
replaces the visit it returns to, and a forward stack keeps the visit.

Co-authored-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Returning from Settings leaves Settings ahead in history, which kept an
empty new chat as a Back stop. A passing visit is now replaced even with
forward entries ahead; they stay where they are. A page whose check
throws keeps its visit instead of failing the opening.

Co-authored-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
A linked draft shows its source link before anything is saved, so with no
agent to tag it looked empty and the next page replaced it. The Back stop
check now reads the unconsumed source the composer shows.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A saved tag now counts as something the person wrote, so a new chat's
automatic agent tag must not be saved. Seed it in memory, like remembered
agents, so an untouched new chat is neither a draft nor a Back stop.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page navigation contract gained passThrough on this branch; a test
added on main builds the stub by hand.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
…ft cannot be saved

The Back-stop check read only the saved draft, so a failed write made a
draft the person typed look empty and the next page replaced its visit.
The composer now reports each edit to the page, which checks it before the
saved copy and forgets it once the draft is sent.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Earlier releases saved the automatic agent tag as the new chat's draft, so
for upgrading users an untouched New chat still counted as written and
stayed a Back stop. Clear that saved tag-only draft and keep showing the
tag in memory, as a fresh new chat does.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Opening the page an empty new chat was opened from replaced the draft with
a second copy of that page, so the first Back landed on the same page. A
passing visit now leaves as a dismissal does: back to its opener when that
page opens, else replaced by the new one. Settings stays exempt.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Nothing caught dropping the reset that forgets a New message's last edit
once it is sent, which would keep the emptied page as a Back stop.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This reverts commit f9a47c3. A tag-only draft someone typed is saved in
the same shape an earlier release saved its automatic default tag, so
matching the default agent cannot tell them apart and the migration could
clear a tag the person wrote. Keep every saved draft; an upgrading user's
untouched New chat stays a Back stop until they type or send in it.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
New message shows a message the outbox holds for delivery or retry even
when its draft view is gone, but the Back-stop check read only that view,
so Back could skip the page. Count the held message as the draft when no
view is saved.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
@morgmart
morgmart force-pushed the morganm/new-chat-back-nav branch from 579175c to 02321c2 Compare October 29, 2026 06:26

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 No changes requested: the prior outbox-only Back-stop suggestion is addressed, and the typed-tag and linked-draft repairs remain intact.

Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot lanes; head 02321c2f6d822c96f2ba221bf0ccf78233069bb5, base 11dd17bb7775a2932ca2466235354105edf2041b. Source-only: no tests, CI verification or app execution; native/browser history, React scheduling, rendered focus and human acceptance remain unverified.

)
void navigator.open(soloDraftTarget(scope, draft));
// The chat was never made: its draft takes its place.
void navigator.open(soloDraftTarget(scope, draft), {

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.

🤖 [P3] Cover history replacement after Solo creation fails

When an empty draft starts a chat and readiness rejects with ChatNotStarted, this replacement should restore [opener, draft]. Dropping replace instead leaves [opener, chat, draft], so Back reaches a chat that was never created. Removing replace: true still leaves ChannelsPage.solo.test.tsx passing 42/42; SoloDraft.test.tsx:199–228 only asserts the failure callback.

Add a regression that rejects start with ChatNotStarted and asserts Back returns to the opener. This is a test coverage gap; the current replacement behavior is correct.

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.

3 participants