# H3 — Header Declutter Branch: `h3/header-declutter` (branched off `dev`) Worktree: `/home/tradam/projects/dispatch/h3-header-declutter` ## Summary The app header used to be: **`Dispatch | … | Connection · Copy · Theme · Sidebar`** — four right-aligned buttons, two of them unrelated to navigation. It is now: **`Dispatch | … | Connection · Sidebar`**. The two removed buttons moved into the sidebar where the rest of the app's controls already live: | Button | Old location | New location | | ---------- | -------------------------- | ---------------------------------------------- | | **Copy** | Header | New **Debug** sidebar panel | | **Theme** | Header → `ThemeSwitcher` modal | Inlined as a section in the **Settings** sidebar panel | `ThemeSwitcher.svelte` was deleted; its theme list + apply-and-persist logic was inlined into `SettingsPanel.svelte` (a sidebar panel doesn't need a modal, and Settings already owns all other UI preferences). A post-implementation Gemini review surfaced a real bug in the first cut (the Settings panel's hardcoded `"dark"` default disagreed with the DOM's actual first-paint theme, which was `light` via daisyUI fallback). That's fixed by a shared `lib/theme.ts` module; see commit `6c377fb`. ## Files ### Modified - `packages/frontend/src/lib/components/Header.svelte` — removed Copy button, Theme button, ThemeSwitcher import, `showThemeSwitcher` state, `copyLabel` state, `handleCopy` / `resetCopyLabel` helpers, and the `{#if showThemeSwitcher}` block. Only the Dispatch title (left), connection status indicator, and Sidebar toggle (right) remain. - `packages/frontend/src/lib/components/SidebarPanel.svelte` — registered `"Debug"` as a new entry in `viewOptions` (last in the list) and added the corresponding `{:else if panel.selected === "Debug"} ` branch. Imported `DebugPanel`. Also added `aria-label="Remove panel"` to the per-slot ✕ button (pre-existing a11y nit found in review). - `packages/frontend/src/lib/components/SettingsPanel.svelte` — added a Theme `` dropdown on any sidebar slot. - **Removed component**: `ThemeSwitcher` (no other importers; safe). - **New module**: `packages/frontend/src/lib/theme.ts` (exports `THEMES`, `Theme`, `THEME_STORAGE_KEY`, `DEFAULT_THEME`, `loadStoredTheme`, `applyTheme`). Anyone needing to read or write the app theme should import from here. - **Component prop changes**: none. `Header.svelte`'s only prop (`onToggleSidebar: () => void`) is unchanged. `SidebarPanel.svelte`, `SettingsPanel.svelte`, and `DebugPanel.svelte` keep / introduce prop shapes consistent with neighboring panels. ## LocalStorage migration No migration is needed. - `dispatch-theme` localStorage key: unchanged shape, unchanged consumers (boot apply in `App.svelte`'s `onMount`, write in `applyTheme`). A user reloading after this branch sees their previously-selected theme intact. A user with a stored value that is no longer in `THEMES` (e.g. someone removed a daisyUI theme later) now falls back to `DEFAULT_THEME` cleanly — previously they'd have silently rendered the bad value as a `data-theme` attribute that daisyUI doesn't recognize. - `dispatch-sidebar-panels` (the sidebar layout): the existing `loadSidebarPanels` already filters non-string entries and falls back to the default layout when nothing valid is stored. Adding `"Debug"` to `viewOptions` is purely additive: existing users with stored layouts continue to render exactly what they had before, and the new option becomes available to anyone who opens the dropdown. No code change to `sidebar-storage.ts` was required. ## Verification ### `bun run check` ``` $ biome check . Checked 142 files in 171ms. No fixes applied. ``` Exit code: 0. ### `bun run test` ``` Test Files 25 passed (25) Tests 404 passed (404) Start at 09:45:10 Duration 2.76s ``` Exit code: 0. (393 pre-existing tests + 11 new `theme.test.ts` tests.) ### `bun run --cwd packages/frontend typecheck` (svelte-check) ``` svelte-check found 0 errors and 0 warnings ``` ### Build ``` vite v6.4.2 building for production... ✓ 167 modules transformed. ✓ built in 3.91s ``` ### Manual UI smoke (programmatic) Full interactive `dev:frontend` + `dev:api` boot wasn't run from this CLI environment, but the equivalents were exercised: - Production build (`vite build`) succeeds — confirms all components compile and Svelte's reactivity contracts are satisfied. - `vite preview` boots and serves the HTML shell on port 4173. - Static grep over the built bundle confirms the post-refactor wiring: - `"Debug"` appears in the bundle as a panel option ✓ - `"Copy conversation"` button label is present ✓ - `ThemeSwitcher` / `showThemeSwitcher` symbols are absent ✓ - Theme list (`dracula`, `cyberpunk`, `caramellatte`) and `dispatch-theme` storage key are bundled ✓ For a human visual pass: `bun run dev:api` + `bun run dev:frontend`, then verify: 1. Header has only `Dispatch | … | Connection · Sidebar`. 2. **Fresh install** (clear `dispatch-theme` from localStorage): first paint should be **dark** (the new `DEFAULT_THEME`), matching what the Settings dropdown shows. Before the post-review fix the first paint was *light* while Settings showed *dark*. 3. Add a sidebar slot via `+`, choose "Settings" → "Theme → Appearance" ``, not a vertical menu.** The original `ThemeSwitcher` used a menu of buttons because it was a modal with the room to display all 13 themes at once. Inside the sidebar a `` updates; the other still shows the previous value (the DOM and storage are correct, only the second component's local `currentTheme` state is stale). Same is true today for `autoExpandThinking`, `localChunkLimit`, `backendUrl`. Fixing it requires hoisting state into a shared reactive store — out of scope for "declutter the header" and noted for follow-up. - **No interactive-browser smoke test was run.** The environment doesn't ship a browser harness; build + preview + bundle-grep is the closest equivalent, and svelte-check confirms type/template correctness. Recommend a 30-second manual eyeball before merge — particularly to confirm the fresh-install theme now matches Settings. - **`ThemeSwitcher.svelte` was deleted, not retained.** Worth noting because the brief said either was fine. Choice rationale: it had one importer (`Header.svelte`), 58 lines, and using a modal from inside a sidebar panel would be a UX regression. ## Commits (atomic) ``` 5ea668d a11y(sidebar): label the remove-panel button for screen readers 6c377fb fix(theme): consolidate boot apply and Settings picker into shared module 60999dc docs: add HANDOFF.md for h3 header declutter 751e411 feat(settings): inline theme picker into Settings panel dd3c71e feat(sidebar): add Debug panel with copy-conversation action bbc85ff feat(header): remove copy + theme buttons; keep title, status, sidebar toggle ``` The first three (`bbc85ff` → `751e411`) are the original refactor. `60999dc` is the initial handoff. `6c377fb` and `5ea668d` are the post-Gemini-review fixes. Each commit is independent and reviewable on its own.