Repository navigation
fix(channels): keep custom thread tab names until the tab closes - #3458
Conversation
save thread tab names as per-account, per-community view state keyed by channel and thread root. the tab store applies a saved name when a thread tab opens or its root is verified, and saves the name when the agent tabs api or the tab ui renames the tab. clearing a name saves an empty record, so it is not later replaced by a stale provisional name. a provisional tab's name moves to its verified root unless that root has its own saved name or clear, both when the root's tab is closed and when the provisional tab merges into it. a merge deletes the provisional hint's saved name; an explicit close keeps it. Signed-off-by: Kalvin Chau <[email protected]>
keep thread tab names in one view record per account and community, ordered by write, holding the 256 newest. a channel deleted on this device forgets its names through channel lifecycle removal, and a message deleted from this device forgets the tab name keyed by its id. Signed-off-by: Kalvin Chau <[email protected]>
forget a deleted thread's tab name from the session outbox's send receipt for kind 5 deletions, instead of when the delete dialog dispatches, so a refused deletion keeps it and every session deletion path is covered. agent deletions forget it through the tabs client after they publish. document that leaving a channel also forgets its names. Signed-off-by: Kalvin Chau <[email protected]>
move the thread tab name record into features/messages/thread-tab-names and register its outbox and channel-removal observers when the relay service publishes a connected session, before anything can send, instead of when a tab store is first created. a deletion sent from a surface with no tab workspace now forgets the name once accepted. Signed-off-by: Kalvin Chau <[email protected]>
add outbox.observeDelivered, which reports each operation the relay confirms by receipt or verified echo, including one restored from the journal. thread tab name cleanup uses it instead of fresh-send work, and the relay service registers it before the successor session restores, so a deletion pending at quit forgets its name once confirmed. refused and pending deletions keep the name. Signed-off-by: Kalvin Chau <[email protected]>
receipt and echo can both confirm one operation, so observeDelivered now reports it once: an id is remembered while the operation is outstanding or its receipt can still arrive, and an operation an earlier session saved as accepted or seen is not reported again after a restart. a thread tab renamed between two confirmations of its deletion keeps the newer name. Signed-off-by: Kalvin Chau <[email protected]>
save each thread tab name with the time it was saved, and let a confirmed deletion forget a name only if it was saved before the deletion's second. receipt and echo confirming the same deletion, in one session or across restarts, can no longer erase a newer name. revert the outbox's per-operation delivery bookkeeping, which this makes unnecessary and which could not survive journal saves between restarts. Signed-off-by: Kalvin Chau <[email protected]>
…leanup a thread tab's name now lasts until the tab is closed, including across channel switches and restarts; closing a tab, or a provisional tab merging into its root's tab, forgets it. the 256-name cap bounds the record. remove deletion-driven cleanup: the outbox delivery observer, relay service registration, agent tabs forget and channel removal observer, returning the outbox, relay service and buzz-mcp to base. move the name record back into the tab store as plain strings. Signed-off-by: Kalvin Chau <[email protected]>
closing a thread tab forgets its name only when no workspace of the session still has a tab for that thread; the other workspaces are read only when a tab closes. Signed-off-by: Kalvin Chau <[email protected]>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source review via Wes's account.
One correctness finding inline: a provisional root change can delete the saved name of a thread that remains open in another workspace. The persistence belongs in the existing tab store and the scope is otherwise appropriately bounded, but this path needs the same last-copy protection as explicit close.
Reviewed head ccb683cd132c83c71267c11ff8c7a00aaaeac1f1, target b598c32e94c67ae940a78c3314ce9dfe4a4db625, merge-base 7f03764beb8eb0ddcb4ff9ec0b8bda3e07699716. Coverage included storage/lifecycle, account/community isolation, provisional-root and clear precedence, UI/agent callers, navigation-history behavior and test fidelity.
Source-only: no tests, builds or app execution performed. The hosted snapshot reports 63 successful checks, including CI required, and Windows native validation skipped. Native quit/relaunch, interactive behavior and human acceptance remain independently unverified. This is a COMMENT review, not an approval.
| // unless that root has its own name or clear, as when it merges into | ||
| // its tab. | ||
| if (prior) { | ||
| saveThreadTabName(scope, nameKey(prior), undefined); |
There was a problem hiding this comment.
🤖 [P2] Preserve the old name key while another workspace still uses it
Keep a verified thread H named Plan open in one workspace, then use another workspace to open a link to a different thread whose root hint is H. That tab restores H's saved name before the reader resolves its actual root R. This branch unconditionally deletes channel:H; because root resolution retains the tab ID, the removed-tab check below never consults elsewhere(). The original H tab still displays Plan, but its name is gone after restart/reopen even though it was never closed. Apply the same remaining-copy check to old keys removed during retargeting, and cover a wrong-hint resolution while a verified H tab stays open in another workspace.
There was a problem hiding this comment.
[P2] Honor a newer clear before migrating a provisional name
There is another case in this branch: open the same provisional thread named Plan in two workspaces, clear it in A, then let B resolve its real root. B still carries Plan; deleting the old key first discards A's "" marker, and the old name is saved under the root again. Please reconcile the current saved provisional value before deleting/migrating it, including a clear marker, while preserving the destination root's own name precedence.
Posted by Codex (gpt-6.1-sol) on behalf of kylez
There was a problem hiding this comment.
🤖 Both addressed in 79080b4.
- Retargeting no longer deletes the hint key unconditionally. Any name key that no tab holds anymore (closed, merged or retargeted) is now forgotten only if no other workspace in the session still holds it.
- When a provisional name migrates to its root, by retargeting or merging, it now uses the hint's saved value, including a
""clear, ahead of the tab's in-memory name. The root's own saved name still wins.
New tests:
- "a provisional copy's root change keeps a name another workspace shows"
- "…keeps another copy's newer clear" (retarget and merge variants)
All three fail without the fix. useChannelTabState passes 78/78 and pnpm check is clean.
kazemek21
left a comment
There was a problem hiding this comment.
Kyle reviewed this PR and the Nexus review-tracker notes. Approved with a non-blocking tab-name persistence comment.
Posted by Codex (gpt-6.1-sol) on behalf of kylez
| // unless that root has its own name or clear, as when it merges into | ||
| // its tab. | ||
| if (prior) { | ||
| saveThreadTabName(scope, nameKey(prior), undefined); |
There was a problem hiding this comment.
[P2] Honor a newer clear before migrating a provisional name
There is another case in this branch: open the same provisional thread named Plan in two workspaces, clear it in A, then let B resolve its real root. B still carries Plan; deleting the old key first discards A's "" marker, and the old name is saved under the root again. Please reconcile the current saved provisional value before deleting/migrating it, including a clear marker, while preserving the destination root's own name precedence.
Posted by Codex (gpt-6.1-sol) on behalf of kylez
a provisional tab resolving to its root no longer deletes the hint's saved name while another workspace still shows that thread: any name key no tab holds any more, whether closed, merged or retargeted, is forgotten only when no workspace of the session still holds it. migrating a provisional name to its root, by retargeting or merging, now takes the hint's saved name or clear from another copy before the tab's own, so a newer clear is not undone. the root's own saved name still wins. Signed-off-by: Kalvin Chau <[email protected]>
* 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) ...
Problem
A thread tab renamed in the UI or through the agent
tabstool lost its name on app restart and reverted to its automatic title.Change
All production changes are in
src/bundled/channels/useChannelTabState.ts.tabs.close, Close others and Close all. A provisional tab that merges into its root's tab also loses its hint's name. If another tab of the same thread is still open, the name is kept.<channel>:<root>to name, ordered oldest first, keeping the 256 most recently written. Leaving the community clears it along with the rest of its view state."", so a stale provisional name can't later replace the clear.docs/buzz-help.mddescribes this behavior.Layout restoration is out of scope.
Tests
The colocated Vitest tests in
useChannelTabState.test.tsxcover:No browser journeys were added.
Validation
pnpm check: pass.vitest run: 1089 files and 21576 tests pass.