feat(pwa): collapsable sidebar + narrow-viewport drawer (#162) #166
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!166
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "162-sidebar-collapse"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #162. Follow-up to #140 / #161, which deferred collapse behavior.
Summary
Escapecloses; drawer resets on viewport flip and route change.localStorageundercarol:sidebar:collapsed. A newSidebarInitScript(mirroringThemeInitScript) synchronously sets<html data-sidebar-collapsed>in<head>before paint, so reloads no longer flash an expanded sidebar before snapping to collapsed. The CSS module keys collapsed-mode rules off the html attribute via:global(...).nav.collapseSidebar,nav.expandSidebar,nav.toggleTheme,nav.themekeys. The existing hardcodedaria-label="Theme"on the theme switcher's segmented control is translated in passing.Implementation
app/(app)/components/sidebar-shell.tsx(new)<aside>+<main>grid. Ownscollapsed(viauseSyncExternalStorereading the html attribute) anddrawerOpenstate. Exposes context toSidebarToggle. Renders both the in-aside toggle and the floating toggle, plus a scrim button. Escape closes the drawer; route changes reset it.app/(app)/components/sidebar-toggle.tsx(new)PanelLefticon button rendered in two placements:brand(inside the aside) andfloating(fixed, narrow-viewport only). At narrow widths it controlsdrawerOpen; at wide widths it flips collapsed.app/(app)/components/sidebar-init-script.tsx(new)<script>mounted from the root layout's<head>next toThemeInitScript. Reads localStorage, sets<html data-sidebar-collapsed>before paint.app/(app)/components/sidebar.tsx<aside>(SidebarShell does). Brand row now:[logo + wordmark] [toggle].app/(app)/components/sidebar-nav.tsxaria-labelon eachLink; visible label + hidden tooltip<span>that becomes a positioned tooltip when collapsed.app/(app)/components/sidebar-theme-switcher.tsxthemeToggleIconbutton for collapsed mode (toggles to the opposite theme).app/(app)/components/sidebar.module.css:global(html[data-sidebar-collapsed="1"]) .shell, narrow-viewport rules under@media (max-width: 899px)(fixed-position aside, floating toggle, scrim).app/(app)/layout.tsx<SidebarShell sidebar={<Sidebar />}>{children}</SidebarShell>.app/layout.tsx<SidebarInitScript />alongside<ThemeInitScript />in<head>.messages/en.jsonnav.*keys (collapseSidebar,expandSidebar,toggleTheme,theme).Test plan
npm run typecheck— clean.npm run lint— clean.npm test— 440 passed, 91 skipped (no regressions).html[data-sidebar-collapsed="1"]and aside is 64px on the first sample (no flash).x=-273);data-narrow="true"; floating toggle visible.x=0(full expanded labels); floating toggle hidden; scrim visible.aria-labelon each navLinkandaria-expanded+aria-controlson the toggle, but live AT behavior untested).🤖 Generated with Claude Code
Trivy (container image)
Threshold:
high· Total findings: 121 · At/above threshold: 16.27.0, 7.28.0, 8.5.0📊 Test coverage
Patch coverage: 0.0% (0/204 added lines) ⚠️ (soft target ≥ 80%)
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Changed files in this PR (source only — tests excluded):
app/(app)/components/sidebar-init-script.tsxapp/(app)/components/sidebar-nav.tsxapp/(app)/components/sidebar-shell.tsxapp/(app)/components/sidebar-theme-switcher.tsxapp/(app)/components/sidebar-toggle.tsxapp/(app)/components/sidebar.tsxSoft thresholds per ADR-0019. Coverage is informational and does not block merge.