Skip to content

feat(app): one New session tab, at the front of the strip - #192

Merged
quickbeard merged 4 commits into
mainfrom
reuse-new-session-tab
Aug 11, 2026
Merged

quickbeard merged 4 commits into
mainfrom
reuse-new-session-tab

Conversation

@quickbeard

@quickbeard quickbeard commented Aug 11, 2026 •

Copy link
Copy Markdown
Owner

Two changes to how the New session tab behaves in the tab bar.

1. It goes to the draft you already have open

Starting a new session added another empty draft tab every time, so a few clicks left a row of identical ones. It now goes to the draft already open for that project, and only starts one when there isn't a draft to go to.

Every way in is covered:

affordance routed through
sidebar New session row createHomeModel.openProjectNewSession
New session on the recent-sessions screen same
New session in a project's ⋯ menu same
+ at the end of the tab bar titlebar.openNewTab
mod+t / mod+n (tab.new) same

Home (mod+b) already reused a draft; this makes them consistent. openNewTab resolves a project through five fallback branches depending on what's on screen, and all five now go through a local openDraftFor, so the rule holds however the project was worked out.

Matched on server and directory — a draft belongs to a project, so starting a session in project B never lands you in project A's draft. A draft with text in it is reused, not replaced, so you land back on what you were writing; the trade is no one-click route to a second blank draft without closing the first. The model carried from the active tab still seeds a new draft only — a reused one keeps its own, as clicking its tab would.

2. It opens at the front of the strip

newDraft appended, so with several sessions open the draft sat at the far right and could scroll out of view. It unshifts now. Session tabs still append.

before                                    after
─────────────────────────────────         ─────────────────────────────────
[Session A] [Session B] [New session]     [New session] [Session A] [Session B]

Knock-on worth knowing: promoteDraft replaces the draft at its own index, so once you send the first message that session keeps the head position and sessions accumulate newest-first. I took that as the better default — the tab you're working in shouldn't jump across the strip mid-flow — but it is a visible change to session order, and it's easy to reverse: move the tab in promoteDraft, which the DIVERGENCES row spells out. Reverting the insert instead would just put New session back at the end.

Tests

findOpenDraft is a pure helper with 6 unit tests: finds the project's draft, ignores session tabs, misses on a different directory, misses on a different server, takes the first of several, empty list. Typed structurally — directory optional, since session tabs carry none — so it needs nothing from the tabs context at build or run time.

Five e2e tests cover the real flow, and all five fail against the unfixed code — stacking two and three drafts, and rendering the draft last. They navigate away between clicks so each is a real navigation rather than a no-op on the page already showing.

The + gains data-action="titlebar-new-tab". Without it the selector matched by accessible name — which the sidebar row also carries — so the first version of that test silently re-tested the sidebar path instead of the button it named.

Divergences

Two upstream files, both recorded:

  • titlebar.tsx — row 120 grows a fourth change, and needed a correction: it recorded Home's reuse as deliberately unlike the New session button, "which is expected to always make a new one". That is no longer the fork's position, and leaving it would have told a future syncer to restore the stacking.
  • context/tabs.tsx — a new row for the unshift, including the promoteDraft knock-on and where to change it.

home-model.tsx is fork-only and the other files are new.

Verification

  • bun test:e2e:local — 139 passed, 0 failed
  • bun test (packages/app) — 644 pass
  • bun typecheck (packages/app) — clean
  • bun lint — 4742 warnings + 1 error, four below main's 4746: collapsing the five newDraft call sites in the titlebar removed them.

🤖 Generated with Claude Code

quickbeard and others added 2 commits August 11, 2026 17:48
Draft tabs persist until they are promoted into a session or closed, so
every click on New session pushed another identical empty draft into the
tab bar. Focus the one already open for that server and directory
instead, and only create a draft when there is none to go to.

Matched on server and directory because a draft belongs to a project;
jumping to one pointing elsewhere would be a worse answer than opening a
new draft. LaunchNewSession already reused a draft at boot for the same
reason — this extends it to every deliberate New session.

Covers the three labelled affordances, all of which route through
createHomeModel: the sidebar row, the recent-sessions screen button and
a project menu's New session. The tab bar's + and mod+t are left alone —
they are `tab.new`, and always producing a new tab is the point.

Both e2e assertions were run against the unfixed code and fail there,
stacking two and three drafts respectively.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both share `openNewTab`, which resolved a project from whatever was on
screen and then opened a draft unconditionally. All five of its
resolution branches now go through a local `openDraftFor`, which selects
the draft already open for that server and directory and only creates one
when there is none.

They are labelled "New session" and reached for with the same intent as
the sidebar row, so splitting the behaviour on which control you happened
to use was the wrong line to draw. Home (mod+b) already reused a draft;
this makes the three consistent.

The + gains data-action="titlebar-new-tab". Without it the e2e selector
matched by accessible name, which the sidebar row also carries — the
first version of that test silently re-tested the sidebar path instead.

DIVERGENCES row 120 said Home's reuse was deliberately unlike the New
session button, "which is expected to always make a new one". That is no
longer true, so the rationale is corrected rather than left contradicting
the shipped behaviour.

Both new e2e assertions fail against the unfixed titlebar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard quickbeard changed the title feat(app): send New session to the draft already open for that project feat(app): send every New session to the draft already open for that project Aug 11, 2026
`newDraft` appended, so with several sessions open the draft landed at
the far right and could scroll out of view. It unshifts now. Session tabs
still append; nothing else about draft handling changes.

Knock-on worth knowing: `promoteDraft` replaces the draft at its own
index, so the session keeps the head position once the first message is
sent and sessions accumulate newest-first. Recorded in the DIVERGENCES
row, including where to change it if that is ever unwanted — moving the
tab in promoteDraft, not reverting the insert.

The ordering test fails against the appending version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard quickbeard changed the title feat(app): send every New session to the draft already open for that project feat(app): one New session tab, at the front of the strip Aug 11, 2026
@quickbeard
quickbeard merged commit 9316c92 into main Aug 11, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the reuse-new-session-tab branch August 11, 2026 11:16
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.

1 participant