Skip to content

feat(sessions): a renamed tab stays renamed, and a reload comes back to it (#54)#55

Merged
dnviti merged 1 commit into
chore/version-bump-5.2.0from
feat/issue-54-sticky-tabs
Jul 26, 2026
Merged

feat(sessions): a renamed tab stays renamed, and a reload comes back to it (#54)#55
dnviti merged 1 commit into
chore/version-bump-5.2.0from
feat/issue-54-sticky-tabs

Conversation

@dnviti

@dnviti dnviti commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Closes #54.

What changed

Names. A chosen name is now a field of its own on the session record
(customName), stored beside the created name rather than over it — so a
session nobody renamed still goes through today's generated-name rules and shows
its folder, including when someone renames a tab to something that looks like a
generated name. A new nullable custom_name column carries it across a restart.
PATCH /api/sessions/:id/name validates (string, trimmed, non-empty, capped at
200) and broadcasts session_renamed to every socket the owning user has open,
so an already-open window follows without reloading. The page that asked renames
optimistically and puts the old label back if the server refuses.

The selected tab. Remembered in the browser on every switch: sessionStorage
first, so two windows each keep their own tab across reloads, with localStorage
behind it so a newly opened window still lands where the user last was. A
remembered id for a session that is gone falls back to the first tab.

Acceptance criteria

All ten verified against the running app (probe under .work/probes/, driving
real Chrome over CDP against a seeded server):

  • rename → reload shows the chosen name;
  • a second window shows it too;
  • a rename in one window appears in the other with no reload;
  • names survive a server restart and session recovery (checked on disk and back
    out of /api/sessions/list);
  • empty/whitespace names are refused, previous name kept;
  • a 170-character name does not stretch the desktop strip or push the phone tab
    switcher off the side (new browser check);
  • a never-renamed session's label is unchanged;
  • selecting the second tab and reloading returns to it;
  • a selected session that is gone falls back rather than blanking;
  • two windows each stay on their own tab across reloads.

Tests

  • test/session-rename.test.js (new): route validation, persistence, the
    broadcast fan-out and who it reaches, ownership, the length cap.
  • test/tab-manager-state.test.js: the optimistic rename and its revert, the
    stored-name settle, remote renames, the boot tab and the two-window case.
  • test/session-store.test.js: custom_name round trip and the additive
    migration on a database that predates the column.
  • test/browser/checks.ts: a very long label on desktop and on a phone.

npm test 1400 passing · npm run test:browser 302 checks, no failures ·
npm run typecheck clean.

Open question from the issue

Clearing a custom name: renaming again is enough, and an empty name stays
rejected per the acceptance criteria — so no "reset to automatic" affordance was
added.

🤖 Generated with Claude Code

…to it (#54)

A name the user chose used to live only in the page that typed it. The strip
read it out of its own state, nothing stored it, and the next page load — a
reload, a second window, a restart — rebuilt every tab from the name the session
was created with. The one moment the labels matter, coming back to a set of
long-running sessions, was exactly the moment they were gone.

The chosen name is now a field of its own on the session record, stored beside
the created name rather than over it, so a session nobody renamed still goes
through the generated-name rules and shows its folder. A new nullable
`custom_name` column carries it across a restart. `PATCH /api/sessions/:id/name`
takes the rename and tells every socket the user has open, so a window that is
already looking at the same sessions follows without reloading; the page that
asked renames optimistically and puts the old label back if the server refuses.

The same reload also used to lose the user's place. The selected tab is now
remembered in the browser — sessionStorage first, so two windows keep their own
tab, with localStorage behind it so a newly opened window still lands somewhere
sensible — and a remembered id for a session that is gone falls back to the
first tab rather than an empty screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 22:23

Copilot AI 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.

Pull request overview

This PR implements persistence and cross-window synchronization for renamed session tabs, and adds per-window tab selection persistence so reloads return users to the previously active session.

Changes:

  • Introduces customName on sessions (DB persistence + API + UI) and adds PATCH /api/sessions/:id/name with websocket broadcast (session_renamed) for live updates across windows.
  • Persists the active tab selection to sessionStorage (per-window) with a localStorage fallback (per-browser), and uses it at boot to select the initial tab after reload.
  • Adds/extends unit and browser checks covering rename validation/persistence/broadcasting and long-name layout behavior.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/tab-manager-state.test.js Adds tests for optimistic renaming, remote rename application, and active-tab persistence across reloads/windows.
test/session-store.test.js Adds persistence/migration coverage for the new custom_name column.
test/session-rename.test.js New route-level tests for rename validation, persistence, ownership, and websocket fan-out behavior.
test/browser/checks.ts Adds a browser check ensuring very long tab names don’t break desktop/mobile layouts.
src/server/types.ts Adds customName to SessionRecord and SessionListItem.
src/server/services/session-store.ts Persists customName to/from SQLite as custom_name.
src/server/services/database.ts Adds additive migration to create custom_name column if missing.
src/server/routes/sessions.ts Adds rename route, includes customName in listings, and uses display name in resumable/export contexts.
src/client/types.ts Adds customName to client SessionListItem and introduces WsSessionRenamedMessage.
src/client/terminal/message-handler.ts Handles session_renamed websocket messages and applies them to the tab manager.
src/client/shell/dialogs/SessionsDialog.tsx Displays customName when present.
src/client/sessions/tab-manager.ts Adds customName handling, optimistic rename + revert, remote rename application, and active-tab persistence.
src/client/app.ts Boots into the remembered/valid active tab instead of always the first tab.
CHANGELOG.md Documents the new behavior for tab renames and reload tab restoration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// refuses — a session that has since been deleted, a name it will not take —
// the old label goes back, so the strip never keeps a name that was not
// stored.
void fetch(`/api/sessions/${sessionId}/name`, {
Comment on lines +246 to +254
// A name is what the user typed with the whitespace taken off, and a name
// that is nothing but whitespace is not a name. Capped because this label is
// rendered in a fixed-width strip and stored on every autosave, and neither
// has any use for a paragraph.
const trimmed = name.trim().slice(0, MAX_NAME_LENGTH);
if (!trimmed) {
res.status(400).json({ error: 'empty_name', message: 'Session name cannot be empty' });
return;
}
);

res.write(`# ${session.name}\n\n`);
res.write(`# ${displayName(session)}\n\n`);
@dnviti
dnviti merged commit e3b3959 into chore/version-bump-5.2.0 Jul 26, 2026
5 checks passed
@dnviti
dnviti deleted the feat/issue-54-sticky-tabs branch July 26, 2026 23:02
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.

2 participants