Skip to content

feat(app): sidebar Back/Forward route-history controls#66

Draft
brsbl wants to merge 3 commits into
mainfrom
bb/implement-left-panel-history-navigation-buttons-thr_ysusjqansr
Draft

feat(app): sidebar Back/Forward route-history controls#66
brsbl wants to merge 3 commits into
mainfrom
bb/implement-left-panel-history-navigation-buttons-thr_ysusjqansr

Conversation

@brsbl
Copy link
Copy Markdown
Collaborator

@brsbl brsbl commented Jun 5, 2026

Summary

Adds browser-style Back and Forward navigation buttons to the left sidebar that move through the bb app-shell route history. This is app-route history only — it does not touch the in-thread browser tab history in BrowserTabContent.

The controls live in the sidebar primary-actions area, above New Thread / New Manager (keeping them clear of the macOS titlebar/traffic-light drag region and hidden in the icon-collapsed sidebar).

What changed

  • apps/app/src/lib/app-route-history.ts — new useAppRouteHistoryNavigation hook. Tracks the actual React Router entries visited while mounted, including duplicate same-URL pushes (distinct location.keys):
    • PUSH appends after the current slot and drops the forward stack.
    • REPLACE overwrites the current slot (no extra Back entry).
    • POP reconciles to a known slot by location.key; an unrecorded key is treated as the app-owned history boundary (stack resets to the current route) so the controls never step into unrecorded/off-app history.
    • canGoBack / canGoForward and goBack / goForward skip equal-URL slots and move by a real navigate(delta), so one click lands on the nearest visibly different route (no fake no-op steps) while preserving router/browser state.
  • apps/app/src/components/sidebar/SidebarHistoryNavigationControls.tsx — two accessible icon buttons (Go back / Go forward) built on the Button primitive + sidebar tokens, native disabled, aria-hidden icons, type="button", matching title/aria-label. Exposes an onNavigate callback so the sidebar can close the mobile drawer after an enabled press. Owns its own row spacing.
  • apps/app/src/components/sidebar/AppSidebar.tsx — renders the controls inside app-sidebar-primary-actions above ProjectListActionButtons, passing closeOnMobile as onNavigate. Existing top-reserve / macOS chrome behavior is untouched.

Tests

  • SidebarHistoryNavigationControls.test.tsx (10 tests): initial disabled state, accessibility attributes, push enables Back only, Back→Forward URL + disabled-state updates, duplicate same-URL skipping (A→B→B: one Back lands on A, one Forward returns to B), forward-stack clearing (A→B→C, Back, push D), replace updates the slot, native POP reconciles to a known entry, unknown POP boundary, and the onNavigate (drawer-close) contract.
  • AppLayout.test.tsx: controls render above the primary actions, Back before Forward, with exactly one sidebar toggle (desktop chrome unchanged). Existing chrome/resize tests remain valid.

Validation

  • pnpm exec turbo run typecheck --filter=@bb/app — pass
  • pnpm exec turbo run lint --filter=@bb/app — pass
  • pnpm exec turbo run test --filter=@bb/app --force — 910 passed (139 files), including the new suites

Live browser UI verification was not run: no browser-automation tooling is available in this environment, and the only running server is the installed prod build, which does not include these worktree changes. Behavior is covered by the automated tests above.

🤖 Generated with Claude Code

brsbl and others added 2 commits June 4, 2026 18:03
Add browser-style Back/Forward navigation to the left sidebar that moves
through the app-shell route history.

- New useAppRouteHistoryNavigation hook (app-route-history.ts) tracks the
  actual React Router entries visited while mounted (including duplicate
  same-URL pushes with distinct location.keys). PUSH appends and clears the
  forward stack, REPLACE updates the current slot, POP reconciles to a known
  entry by key and treats an unrecorded key as the app-owned history
  boundary. Back/Forward move by a real navigate(delta) that skips equal-URL
  slots so one click lands on the nearest visibly different route.
- New SidebarHistoryNavigationControls renders two accessible icon buttons
  (Go back / Go forward) using the Button primitive + sidebar tokens, native
  disabled state, aria-hidden icons, and an onNavigate hook for closing the
  mobile drawer.
- Wire the controls into AppSidebar's primary-actions area above New Thread /
  New Manager, passing closeOnMobile so the compact drawer closes after an
  enabled press. Placement keeps the controls out of the macOS titlebar drag
  region and hidden in the icon-collapsed sidebar.

Tests cover initial disabled state, push/back/forward, duplicate same-URL
skipping, forward-stack clearing, replace, native/unknown POP boundaries, the
onNavigate contract, and sidebar placement with unchanged desktop chrome.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace inline types in function signatures with named aliases per AGENTS:
- AppRouteNavigationType for reduceHistory's navigation-kind parameter.
- NavigateToOptions for the test navigateTo helper's options argument.

No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lisions

BrowserRouter labels unkeyed document-history entries "default", so a POP can
share the mounted entry's key while pointing at a different URL. Two stale-state
bugs followed:

- The effect deduped on location.key, so a same-key POP was dropped entirely
  and never reduced. Dedupe on the location object identity instead (React
  Router hands out a fresh location per navigation).
- The POP reducer reconciled by key alone, so it could snap to a recorded slot
  whose URL no longer matches. Require key AND normalized URL to match;
  otherwise treat the popped route as the app-owned history boundary.

Adds a regression test with MemoryRouter entries that intentionally share the
"default" key but have different URLs (verified to fail before this fix).

Co-Authored-By: Claude Opus 4.8 (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