Device-local tabs + spaces as a searchable sidebar filter - #14
Open
wingleeio wants to merge 9 commits into
Open
Conversation
Contributor
Author
Tabs detach from session existence: a tab is a device-local viewport onto a synced session (UiSettings.openTabs, the vec is the order). Closing a tab is local-only — the session keeps running and stays in the sidebar, where a click reopens it; archiving stays an explicit sidebar-row action. The strip goes cross-space and gains a context menu (close / close others / close left / right). Old files migrate by seeding openTabs from the last space's sessions. Spaces stop being a navigation spine: the sidebar section becomes a searchable dropdown (ref-picker recipe) with "All spaces", filtering only the session list and hosting space management. The new-session canvas gains a space picker chip defaulting to the sidebar filter, else the last selected space; picking a space re-keys the ref selector and harness/model catalogs through the existing selected_space observer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The label now flexes, so long space names ellipsize against the trigger width instead of pushing the caret out of the row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wingleeio
force-pushed
the
tabs-spaces-restructure
branch
from
August 4, 2026 08:44
d9f7abf to
de7f886
Compare
anchored_menu pins to the trigger's top-left, which covered the button. New anchored_menu_below primitive pins the floating layer to the trigger's bottom edge — a dropdown proper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
The selected row's wash (menu_row active styling) is the selection signal; the trailing check fought the @device tag for the row end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
The selected row's wash (menu_row active styling) is the selection signal everywhere — ref/checkout/model pickers, the canvas space picker, and the account device menu — matching the space-filter dropdown. menu_check retires with its last caller. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
…-down Dropdowns/menus previously vanished the frame their state dropped. Every menu state is now a popover::Popup<T> with an exit phase: close paths funnel through a begin_close that keeps the card mounted ~100ms playing menu-out (fade + 2px lift, interactivity deadened), then a reap timer drops the state. The exit progress reads the Popup's closing instant at render time rather than with_animation's element-state clock — that clock replays from 0 on a missed frame, which flashed the dying menu back to full opacity. The frosted backdrop blur ignores element_opacity, so its radius rides the same progress down to zero instead of popping off at unmount. Also: clicking an open trigger now just closes its menu (the outside- click dismissal no longer immediately reopens via the same click), the same just-dismissed guard the user menu already carried. Converted: spaces filter dropdown, user menu, session/tab/space context menus, composer pickers, settings device menu. The @-mention completion stays instant on purpose (keystroke-driven dismissal would read as lag). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The host device was only visible inside the space pickers' rows. The sidebar filter trigger now shows it right after the space name (caret stays at the right edge), and the composer's space chip shows it as the muted suffix — amber "· offline" when the host's heartbeat is stale, same as the rows. Formatting shared via AppState::space_device_tag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both the sidebar filter dropdown and the composer space picker listed spaces in sync-arrival order. They now share AppState::spaces_sorted (case-insensitive by display name, id tie-break): search still ranks prefix matches first — alphabetical within each rank — and the picker's opening highlight indexes the sorted order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.





Restructures the shell around wing's proposal: tabs stop being synced session state, spaces stop being a navigation spine.
What changed
Tabs are now device-local. A tab is a local viewport onto a synced session (
ui-settings.json→openTabs; the vec is the drag order). Closing a tab is local-only — the session keeps running on its host and stays in the sidebar; clicking a sidebar row (re)opens it as a tab. Archiving is a separate, explicit action in the session row's context menu (unchanged — it was already there). The strip is cross-space and gets a context menu: close, close others, close to the right, close to the left (rows that would close nothing are omitted). Existing installs migrate by seedingopenTabsfrom the last selected space's sessions, so nobody's tabs vanish on upgrade.Spaces are a searchable dropdown (same recipe as the ref picker: ranked substring search, keyboard nav) that filters only the sidebar session list. "All spaces" is the first row; "Add space…" (the existing ⌘K palette) is the last; rename/remove live on row context menus inside the dropdown.
New sessions.
+opens the canvas defaulting to the sidebar's filter space — or the last selected space under "All". The composer gains a space chip on the canvas only; picking a space re-keys the ref selector, worktree plan, and harness/model catalogs through the existingselected_spaceobserver (no new invalidation paths).The synced Loro doc schema is untouched — this is purely a view-layer restructure.
Screenshots
Cross-space tab strip; sidebar sessions with the dropdown trigger on top:
The spaces dropdown — searchable, "All spaces", device tags, "Add space…":
Filtered to
soccertcg— note the twocomet-space tabs stay open (the filter never touches the strip):New-session canvas defaults to the filter space, with the new space chip:
The canvas space picker, and the ref footer re-keying after switching to a git space (
Current checkout / mainappears becausecomethas git;soccertcgdidn't):Tab context menu (last tab → no "close to the right"):
Demo video
Filter → open-from-sidebar →
+with space pick → send (mock harness streams) → close the streaming tab locally (session keeps running in the sidebar) → reopen with transcript intact → close others:MP4 version · assets live on the
assets/tabs-spacesbranch, not in this diff.Verification
cargo test --workspacegreen (settings round-trip covers the new fields; tab-neighbor logic keeps its unit tests).🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.