feat(client): sidebar redesign per design package #267

Merged
james merged 2 commits from sidebar-design-refresh into main 2026-06-23 23:57:52 +00:00
Owner

Summary

Refactors the (app) sidebar to mirror the canonical design package at /var/home/wynnj/projects/carol-design/design_files/Sidebar.jsx. The visual + structural shape matches the design, while the behavioural plumbing from #210 (collapse/expand, mobile drawer, theme + locale persistence) keeps working.

The sidebar now:

  • Has a brand row with the Carol icon + wordmark (icon rendered inline via react-native-svg from apps/client/lib/nav/CarolIcon.tsx; the SVG source-of-truth is also copied to apps/client/assets/carol-icon.svg).
  • Renders a sectioned nav with two horizontal dividers between three groups: Chat / ApplicationsProfile / Skills / Experience / Projects / NetworkAccount.
  • Swaps icons to match the design exactly: MessagesSquare, ClipboardList, UserRound, Sparkles, Briefcase, FolderGit2, UsersRound, Settings.
  • Has a user-card footer (avatar + name/email + log-out icon button). The avatar shows the uploaded profile picture when available, falling back to initials derived from the profile name (or email local-part). The picture-source plumbing reuses getProfilePictureSource() so off-origin (Android + Tauri) gets the same bearer header + absolute URL rewrite as the profile screen.
  • Pins itself to the dark token set in both light and dark app themes (matches the design and the Slack/Linear/Notion idiom — sidebar is brand chrome, not theme chrome). The slide-in drawer's background matches.

The theme + locale switchers move out of the sidebar footer into a new Preferences card on the Account screen. Theme keeps its three-state shape (Auto / Light / Dark) — preserving Auto avoids a destructive change for users whose persisted setting is auto. Locale stays in the client-side persistence layer (lib/i18n/persistence.ts).

Structure changes

  • New apps/client/lib/nav/items.ts — typed NavLink | NavDivider union, with NAV_ITEMS ordered to match the design. Icons live in Sidebar.tsx via a NAV_ICONS: Record<NavNs, LucideIcon> map so the data module stays import-safe under the Node-environment vitest harness.
  • apps/client/lib/nav/Sidebar.tsx — refactored to render the brand row, sectioned nav, and user-card footer. Drops the renderFooterControls slot.
  • apps/client/lib/nav/FooterControls.tsx — deleted (its responsibilities split between the new PreferencesCard on Account and the user-card footer in Sidebar).
  • apps/client/app/(app)/_layout.tsx — drops renderFooterControls; pins the drawer's backgroundColor to darkTokens.bg so the slide-in drawer matches the sidebar.
  • apps/client/app/(app)/account.tsx — adds the PreferencesCard (theme tri-state segmented + locale segmented).
  • packages/i18n/messages/en.json + es.json — adds account.preferences.* keys, drops unused nav.theme/locale/toggleTheme.
  • apps/client/tests/navItems.test.ts — pins the three-group / two-divider shape so future contributors can't silently drop dividers or reorder groups without an explicit diff.

Notes dropped from sidebar

The /notes screen file at apps/client/app/(app)/notes.tsx stays — users can still reach it by URL and the TanStack scaffolding it carries remains useful as a reference. It's just no longer listed in the sidebar (the design omits it).

Decisions worth flagging

  • Notes dropped from sidebar nav, kept as a routable screen. Per the design.
  • Theme switcher stays tri-state (Auto / Light / Dark) on the Account screen. The design's binary Light/Dark made sense for a tight sidebar footer; in a settings card we have room and preserving Auto matters for users who picked it.
  • Locale switcher moves to Account. The design has no sidebar locale switcher; the Account screen already has the right useTranslation infrastructure.
  • Dark sidebar surface reuses the existing darkTokens from lib/theme/tokens.ts rather than introducing a new dedicated token. The sidebar imports it directly, bypassing useTheme(), so the surface stays dark regardless of the user's overall theme.
  • Profile picture in the user card, with initials fallback. Uses the same off-origin URL rewrite + bearer header path as the profile screen's avatar.

Design source

Mirrored from carol-design/design_files/Sidebar.jsx (the JSX prototype + the matching CSS rules in design_files/index.html).

Test plan

  • pnpm install --frozen-lockfile
  • pnpm -F @carol/api typecheck / lint / test
  • pnpm -F @carol/api-client typecheck / lint / test / check
  • pnpm -F @carol/client typecheck / lint / test / export:web
  • New tests/navItems.test.ts covers divider count, group ordering, no-Notes, no-orphan-divider invariants.

Context: #210 (original sidebar PR).

## Summary Refactors the (app) sidebar to mirror the canonical design package at `/var/home/wynnj/projects/carol-design/design_files/Sidebar.jsx`. The visual + structural shape matches the design, while the behavioural plumbing from #210 (collapse/expand, mobile drawer, theme + locale persistence) keeps working. The sidebar now: - Has a brand row with the Carol icon + wordmark (icon rendered inline via `react-native-svg` from `apps/client/lib/nav/CarolIcon.tsx`; the SVG source-of-truth is also copied to `apps/client/assets/carol-icon.svg`). - Renders a sectioned nav with two horizontal dividers between three groups: `Chat` / `Applications` → `Profile` / `Skills` / `Experience` / `Projects` / `Network` → `Account`. - Swaps icons to match the design exactly: `MessagesSquare`, `ClipboardList`, `UserRound`, `Sparkles`, `Briefcase`, `FolderGit2`, `UsersRound`, `Settings`. - Has a user-card footer (avatar + name/email + log-out icon button). The avatar shows the uploaded profile picture when available, falling back to initials derived from the profile name (or email local-part). The picture-source plumbing reuses `getProfilePictureSource()` so off-origin (Android + Tauri) gets the same bearer header + absolute URL rewrite as the profile screen. - Pins itself to the dark token set in both light and dark app themes (matches the design and the Slack/Linear/Notion idiom — sidebar is brand chrome, not theme chrome). The slide-in drawer's background matches. The theme + locale switchers move out of the sidebar footer into a new **Preferences** card on the Account screen. Theme keeps its three-state shape (Auto / Light / Dark) — preserving Auto avoids a destructive change for users whose persisted setting is `auto`. Locale stays in the client-side persistence layer (`lib/i18n/persistence.ts`). ## Structure changes - New `apps/client/lib/nav/items.ts` — typed `NavLink | NavDivider` union, with `NAV_ITEMS` ordered to match the design. Icons live in `Sidebar.tsx` via a `NAV_ICONS: Record<NavNs, LucideIcon>` map so the data module stays import-safe under the Node-environment vitest harness. - `apps/client/lib/nav/Sidebar.tsx` — refactored to render the brand row, sectioned nav, and user-card footer. Drops the `renderFooterControls` slot. - `apps/client/lib/nav/FooterControls.tsx` — deleted (its responsibilities split between the new `PreferencesCard` on Account and the user-card footer in `Sidebar`). - `apps/client/app/(app)/_layout.tsx` — drops `renderFooterControls`; pins the drawer's `backgroundColor` to `darkTokens.bg` so the slide-in drawer matches the sidebar. - `apps/client/app/(app)/account.tsx` — adds the `PreferencesCard` (theme tri-state segmented + locale segmented). - `packages/i18n/messages/en.json` + `es.json` — adds `account.preferences.*` keys, drops unused `nav.theme/locale/toggleTheme`. - `apps/client/tests/navItems.test.ts` — pins the three-group / two-divider shape so future contributors can't silently drop dividers or reorder groups without an explicit diff. ## Notes dropped from sidebar The `/notes` screen file at `apps/client/app/(app)/notes.tsx` stays — users can still reach it by URL and the TanStack scaffolding it carries remains useful as a reference. It's just no longer listed in the sidebar (the design omits it). ## Decisions worth flagging - **Notes dropped from sidebar nav**, kept as a routable screen. Per the design. - **Theme switcher stays tri-state (Auto / Light / Dark) on the Account screen.** The design's binary Light/Dark made sense for a tight sidebar footer; in a settings card we have room and preserving Auto matters for users who picked it. - **Locale switcher moves to Account.** The design has no sidebar locale switcher; the Account screen already has the right `useTranslation` infrastructure. - **Dark sidebar surface** reuses the existing `darkTokens` from `lib/theme/tokens.ts` rather than introducing a new dedicated token. The sidebar imports it directly, bypassing `useTheme()`, so the surface stays dark regardless of the user's overall theme. - **Profile picture in the user card**, with initials fallback. Uses the same off-origin URL rewrite + bearer header path as the profile screen's avatar. ## Design source Mirrored from `carol-design/design_files/Sidebar.jsx` (the JSX prototype + the matching CSS rules in `design_files/index.html`). ## Test plan - [x] `pnpm install --frozen-lockfile` - [x] `pnpm -F @carol/api typecheck` / `lint` / `test` - [x] `pnpm -F @carol/api-client typecheck` / `lint` / `test` / `check` - [x] `pnpm -F @carol/client typecheck` / `lint` / `test` / `export:web` - [x] New `tests/navItems.test.ts` covers divider count, group ordering, no-Notes, no-orphan-divider invariants. Context: #210 (original sidebar PR).
feat(client): sidebar redesign per design package
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 9s
PR / Static analysis (pull_request) Successful in 36s
PR / OSV-Scanner (pull_request) Successful in 28s
PR / Lint (pull_request) Successful in 1m57s
PR / OpenAPI (pull_request) Successful in 2m10s
PR / Trivy (image) (pull_request) Successful in 1m39s
PR / Test (sqlite) (pull_request) Successful in 2m18s
PR / Typecheck (pull_request) Successful in 2m22s
PR / Package age policy (soft) (pull_request) Successful in 24s
Secrets / gitleaks (pull_request) Successful in 20s
PR / Build (pull_request) Successful in 2m49s
PR / Coverage (soft) (pull_request) Successful in 3m51s
PR / pnpm audit (pull_request) Successful in 16m24s
PR / Client (web export smoke) (pull_request) Successful in 4m0s
PR / Test (postgres) (pull_request) Successful in 3m53s
79758dc213
Mirror the canonical sidebar structure from carol-design/design_files/
Sidebar.jsx: dark surface, brand icon + wordmark, sectioned nav with
two dividers, user-card footer with avatar (profile picture or initials)
plus log-out icon button. Drop the Notes link from the sidebar — the
screen file stays as a TanStack reference but is no longer in the main
nav. Move the theme + locale switchers into a new Preferences card on
the Account screen; the design's sidebar carries neither.

The sidebar pins itself to the dark token set regardless of the user's
app theme, matching the design and the Slack/Linear/Notion idiom — the
sidebar is brand chrome and a stable anchor while the scene swaps light
and dark. The slide-in drawer matches.

NAV_ITEMS becomes a typed link/divider union; icons bind via a separate
NAV_ICONS map in Sidebar.tsx so the data module stays import-safe under
the Node-environment vitest harness. New tests pin the structural shape
(three groups, two dividers, canonical hrefs) so a future refactor can't
silently drop one.

Theme preference keeps its three-state shape (Auto/Light/Dark) on the
Account screen — Auto is still a valid persisted value and dropping it
would be a destructive change for users who picked it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 82.7% ≥ 50%
Branches 75.1% ≥ 75%
Functions 91.7% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 82.7% ✅ | ≥ 50% | | Branches | 75.1% ✅ | ≥ 75% | | Functions | 91.7% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
fix(client): sidebar follows app theme; collapsed brand row is a single tap target
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 13s
PR / OSV-Scanner (pull_request) Successful in 20s
PR / Static analysis (pull_request) Successful in 39s
PR / Trivy (image) (pull_request) Successful in 1m42s
PR / Package age policy (soft) (pull_request) Successful in 13s
Secrets / gitleaks (pull_request) Successful in 14s
PR / Typecheck (pull_request) Successful in 3m44s
PR / Test (sqlite) (pull_request) Successful in 3m50s
PR / pnpm audit (pull_request) Successful in 4m21s
PR / Client (web export smoke) (pull_request) Successful in 4m27s
PR / Lint (pull_request) Successful in 4m35s
PR / OpenAPI (pull_request) Successful in 4m40s
PR / Build (pull_request) Successful in 5m12s
PR / Test (postgres) (pull_request) Successful in 5m19s
PR / Coverage (soft) (pull_request) Successful in 4m42s
11aca71584
Two complaints against the design-refresh first cut:

1. The sidebar was pinned to dark tokens regardless of the user's
   theme. Under the light theme this looked off — the sidebar floated
   as a dark slab against a light scene. Swap the pin for useTheme()
   reads so the sidebar follows the active theme: light surface in
   light mode, dark in dark.

2. Collapsed brand row showed the CarolIcon + the ChevronRight side
   by side. The chevron pushed the icon off-axis from the centred nav
   icons below. In collapsed mode the row now renders ONLY the
   CarolIcon, wrapped in a Pressable that doubles as the expand
   button. Single tap target, centred in the 64px column.

Drawer background in _layout.tsx swaps from darkTokens.bg to
theme.tokens.bg for the same reason — a slide-in transition under
light theme used to expose a dark frame.

Expanded layout is unchanged (icon + wordmark on the left, chevron
on the right).
james merged commit 20452bc890 into main 2026-06-23 23:57:52 +00:00
james deleted branch sidebar-design-refresh 2026-06-23 23:57:52 +00:00
Sign in to join this conversation.
No description provided.