Repository navigation
feat(app): add a sidebar collapse toggle and a Search button to the titlebar - #175
Merged
Merged
Conversation
…itlebar The sidebar became permanent chrome in #173 with no way to dismiss it, and the recents list was reachable only from its own View all row. The toggle leads the titlebar — ahead of the Windows/Linux app menu, so it is the first item on every platform. Collapsed hides the column outright rather than narrowing it to a rail; the state persists globally, so it survives a relaunch. Search sits right of Home, navigates to the recents list like View all, and focuses its search field. Collapse state lives in a fork-only context so the titlebar button and the sidebar can share it. useAppSidebar() returns undefined outside a provider rather than throwing: useV2Titlebar() is just newLayoutDesigns(), so this titlebar also renders inside the legacy shell, which has no provider and no sidebar of this kind — the toggle is hidden there by construction. Search hands off through a ?focus=search param that NewHome consumes and clears, mirroring how new-session.tsx handles ?prompt. The focus ref had to become a signal: HomeSessionSearch binds it from onMount, which runs after NewHome's effects, so the first attempt no-opped against an undefined ref and cleared the param anyway. No keyboard shortcut, so mod+b keeps its current meaning. Verified on a running build: button order Toggle(92) Home(134) Search(176); collapse and restore both track the pressed state; collapsed survives a reload; Search focuses the input both from a session and from / itself. With the new layout switched off, the legacy shell renders its own rail, no toggle, and throws nothing. 624 unit tests and the 5 smoke e2e tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The app menu dispatches command id "sidebar.toggle", and only LegacyLayout ever registered it — so on the new layout the item did nothing. AppSidebarProvider now registers it with no keybind, since mod+b belongs to Home here rather than to the sidebar as it does in the legacy layout. It lives on the provider rather than on AppSidebar so that collapsing the sidebar does not unregister the one command that brings it back. Verified through the command registry, which is the path the menu item takes: opened the palette with mod+k, typed "toggle sid", got "Toggle sidebar" as a match, and selecting it collapsed the sidebar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 11, 2026
quickbeard
pushed a commit
that referenced
this pull request
Aug 11, 2026
#173, #175 and #179 shipped with no tests; #181 and #183 already carried their own, so this fills the gap left by the other three. Unit (test-browser, since home-model reaches kobalte through the directory picker and will not import under the server condition): - buildHomeSessionRecords: directory filtering, pathKey normalisation so a Windows path and a trailing slash are one directory, sort by last update with a created-time fallback, dedupe keeping the fresher copy, sandbox and projectID resolution, and dropping a session no project claims. - descendantIDs: the subtree a delete evicts, including cycle safety. Exported for this, alongside buildHomeSessionRecords which was already exported. - session-share-request: the row-to-timeline share handoff consumes once, and a request meant for another session is not swallowed. E2E: - sidebar-persistence: the column survives opening a session, orders recents newest first, opens the session a row names, and View all and New session both work from inside a session. - sidebar-toggle-search: the titlebar toggle hides and restores the column, survives a reload and a route change, and Search lands on the recents list with its field focused and the ?focus=search param cleared. - session-actions-menu: the four actions on both lists, Share hidden when the server disables sharing, rename in place without opening the session, cancel sending nothing, and delete asking first. Two defects turned up while writing these, both left unfixed and documented where they bite: - Clicking a Recents row opens the session but never marks the row as open; the marker only appears once the tab list is read back on a fresh mount. Arriving by URL marks it immediately, so the state is right and the click path fails to re-run the memo. - The first activation of a row's actions menu is swallowed. Four clicks give closed, open, closed, open, and focus + Enter is swallowed the same way, so a user's first click on the dots lands on nothing too. 623 unit, 45 browser-condition unit and all 110 e2e regression tests pass; tsgo -b and the e2e project both typecheck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
added a commit
that referenced
this pull request
Aug 11, 2026
* test(app): cover the sidebar, titlebar and session-row menu PRs #173, #175 and #179 shipped with no tests; #181 and #183 already carried their own, so this fills the gap left by the other three. Unit (test-browser, since home-model reaches kobalte through the directory picker and will not import under the server condition): - buildHomeSessionRecords: directory filtering, pathKey normalisation so a Windows path and a trailing slash are one directory, sort by last update with a created-time fallback, dedupe keeping the fresher copy, sandbox and projectID resolution, and dropping a session no project claims. - descendantIDs: the subtree a delete evicts, including cycle safety. Exported for this, alongside buildHomeSessionRecords which was already exported. - session-share-request: the row-to-timeline share handoff consumes once, and a request meant for another session is not swallowed. E2E: - sidebar-persistence: the column survives opening a session, orders recents newest first, opens the session a row names, and View all and New session both work from inside a session. - sidebar-toggle-search: the titlebar toggle hides and restores the column, survives a reload and a route change, and Search lands on the recents list with its field focused and the ?focus=search param cleared. - session-actions-menu: the four actions on both lists, Share hidden when the server disables sharing, rename in place without opening the session, cancel sending nothing, and delete asking first. Two defects turned up while writing these, both left unfixed and documented where they bite: - Clicking a Recents row opens the session but never marks the row as open; the marker only appears once the tab list is read back on a fresh mount. Arriving by URL marks it immediately, so the state is right and the click path fails to re-run the memo. - The first activation of a row's actions menu is swallowed. Four clicks give closed, open, closed, open, and focus + Enter is swallowed the same way, so a user's first click on the dots lands on nothing too. 623 unit, 45 browser-condition unit and all 110 e2e regression tests pass; tsgo -b and the e2e project both typecheck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(app): drop the workarounds #185 makes unnecessary Both specs carried a detour around a defect, each with a comment explaining why. #185 fixes both, so the detours are now dead weight and the comments are wrong. - session-actions-menu: openRowMenu clicked a second time when the first was swallowed. One click now, and the helper no longer needs the page. - sidebar-persistence: "marks the row whose session is already open" reloaded before asserting data-selected. It asserts on the click that opened the session. The hover in openRowMenu stays. That one is not a workaround: the trigger is pointer-events-none until its row is hovered, and Playwright hit-tests before it moves the mouse. Checked both ways rather than assumed. On #185's branch the three specs pass 20/20; on main without it, both edited tests fail — the menu never reports aria-expanded=true, and no row is ever marked selected. So this must merge after #185. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Quickbeard <quickbeard@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <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.
Issue for this PR
Closes #
Type of change
What does this PR do?
Two buttons in the titlebar.
Sidebar collapse toggle, leading the row — ahead of the
WindowsAppMenuthat renders on Windows and Linux, so it is the first item on every platform. #173 made the sidebar permanent chrome with no way to dismiss it short of shrinking the window belowlg. Collapsed hides the 280px column outright rather than narrowing it to a rail, and the state persists globally so it survives a relaunch. It showspressedwhile the sidebar is open.One consequence worth stating: Projects live only in the sidebar now, so while collapsed you re-open it to switch project.
Search, immediately right of Home — opens the recent-sessions list exactly as the sidebar's View all does, and focuses its search field on arrival.
Neither has a keyboard shortcut, so
mod+bkeeps its current meaning.Icons come from the existing v2 sprite, no new artwork:
magnifying-glass, andsidebar-rightfor the toggle — despite the name its divider sits at x=7 of a 2.9–17.1 box, so the narrow pane is on the left. Labels reusecommand.sidebar.toggleandhome.sessions.search.placeholder, both already present inenandes.Two things that shaped the implementation
useAppSidebar()returnsundefinedoutside a provider instead of throwing.useV2Titlebar()is justsettings.general.newLayoutDesigns(), andapp.tsxstill mountsLegacyServerLayout→LegacyLayout→<Titlebar>for/:dirroutes even when the new layout is on — so the v2 titlebar renders in placeslayout-new.tsxdoes not wrap.createSimpleContextthrows without a provider, so it is deliberately not used here. The toggle is hidden in that shell by construction, which is correct: it has no sidebar of this kind.Search hands off through a
?focus=searchparam thatNewHomeconsumes and clears, mirroring howpages/new-session.tsxhandles?prompt. The focus ref had to become a signal:HomeSessionSearchbinds it fromonMount, which runs afterNewHome's effects, so the first version no-opped against an undefined ref and cleared the param anyway — the field never got focus. Caught in verification, not review.How did you verify your code works?
bun typecheckclean, 624 unit tests pass, no new lint warnings (20 before and after on the touched files).bunx playwright test e2e/smoke/session-timeline.spec.ts— 5/5 pass.Then drove a real desktop build over CDP, measuring rather than eyeballing:
aria-pressedtracks it both waysdocument.activeElementcarrying the "Search sessions" placeholder, and the query param cleared/re-focuses the fieldAnd the legacy-shell path, since that is the failure mode the optional context exists to prevent: flipped
general.newLayoutDesignsoff in the persisted store, reloaded, and confirmed the legacy shell renders its own rail with no toggle and no exception — then restored it.Not verified: Windows or Linux. Nothing builds the desktop app on those platforms, in CI or locally, and the two OS-specific behaviours here (the toggle sitting ahead of
WindowsAppMenu, the different left inset) are read from the conditionals rather than executed.Screenshots / recordings
Titlebar with both new buttons, sidebar open:
Checklist
DIVERGENCES.md: thetitlebar.tsxand sidebar rows are extended rather than duplicated, including a note not to swapuseAppSidebarforcreateSimpleContext.context/app-sidebar.tsxis fork-only and untracked there. OneCHANGELOG.mdentry under Unreleased.One thing I left out, for your call: the app menu's View → Toggle Sidebar item is permanently greyed out in the new layout, because
sidebar.toggleis only registered byLegacyLayout. Registering it from the new sidebar with no keybind would light it up without adding a shortcut — roughly five lines, say the word.🤖 Generated with Claude Code