Skip to content

feat(app): make the sidebar Recents section collapsible - #183

Merged
quickbeard merged 2 commits into
mainfrom
collapsible-sidebar-sections
Aug 11, 2026
Merged

quickbeard merged 2 commits into
mainfrom
collapsible-sidebar-sections

Conversation

@quickbeard

@quickbeard quickbeard commented Aug 11, 2026 •

Copy link
Copy Markdown
Owner

Clicking the Recents header in the sidebar now folds the section away — the New session row, the session list and View all all hide, and a chevron beside the title shows which way it is. The choice persists across launches.

The fold behaviour lives in a new fork-only SidebarSection component rather than in SidebarRecents, because the pinned-sessions section is coming next and needs the same three things:

  • the header row as the toggle (it carries the label text, so aria-expanded alone names the control and there is no extra string to translate),
  • the chevron affordance, matching the one the server rows already hand-roll in home.tsx,
  • the growth rule — an open section is the flexible child that soaks up the column's free height (which is what lets Recents size its list to the space left over), a collapsed one shrinks to its 28px header.

Collapse state is persisted under a single global sidebar.sections key, a record keyed by section name, so the pinned section slots in without a second stored key. Sections default to open: an absent entry is an expanded section.

One knock-on: an expanded Recents is the flexible child that holds the bottom nav down, so with every section collapsed nothing grows and the column has slack to place. It belongs between the last section and the bottom nav — the Recents header stays put under the last project, and Settings/Help stay on the bottom edge — so HomeUtilityNav gets an auto margin. (Letting the Projects list grow into that slack instead is wrong: it sinks the collapsed header to the bottom of the column.)

New session stays one click away in the titlebar while Recents is folded, so collapsing the section does not strand that action.

Divergences

No upstream file changes. Both new files are fork-only, and app-sidebar.tsx is fork-only too. DIVERGENCES.md row for the persistent app sidebar gets one clause: HOME_SECTION_LABEL — an export the fork added to home.tsx — is now consumed by sidebar-section.tsx rather than directly by app-sidebar.tsx, which is the kind of thing that row exists to tell a future syncer.

Verification

  • bun typecheck (packages/app) — clean
  • bun test (packages/app) — 619 pass
  • New e2e regression spec sidebar-section-collapse.spec.ts: collapse + persistence across reload, and that collapsing moves neither the section header nor the bottom nav. The layout assertion was checked against the broken layout and fails there by 388px, so it is not passing vacuously.
  • Full bun test:e2e:local — 93 pass, 2 fail in remote-session-settings.spec.ts; both fail identically on a clean main checkout, so they are pre-existing and unrelated
  • bun lint — identical to the baseline on main (4740 warnings, 1 pre-existing error in session-timeline.fixture.ts)
  • Visually checked both states in the Playwright harness

🤖 Generated with Claude Code

quickbeard and others added 2 commits August 11, 2026 08:49
Extracts the header/fold behaviour into a fork-only `SidebarSection`
component so the pinned-sessions section can reuse it: it owns the
toggle affordance, the aria wiring, and the growth rule that an open
section is the flexible child of the column while a collapsed one
shrinks to its header.

Collapse state is persisted globally under `sidebar.sections`, keyed by
section name, so a second section slots in without a new stored key.
Projects takes the free height whenever no section below it is expanded,
which keeps Settings and Help anchored to the bottom of the column.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Growing the Projects scroll area to absorb the slack an expanded Recents
had been holding also pushed the collapsed header down to sit on top of
Settings/Help. Pin the bottom nav with an auto margin instead, so the
slack opens between the last section and the bottom nav and the header
stays where it was.

The layout spec only asserted that Settings did not move, which the
broken version satisfied too; it now pins the section's own position as
well and fails by 388px against the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard
quickbeard merged commit 3d1c2fd into main Aug 11, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the collapsible-sidebar-sections branch August 11, 2026 03:13
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>
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