# App Layout (v2) ### Route: `*` (all pages) ### Source: `packages/web/src/routes/__root.tsx` --- ## v1 -> v2 Changes | Aspect | v1 | v2 | |--------|----|----| | Header height | 56px + nav row | Single 48px row | | Nav layout | Wordmark on row 1, tabs on row 2 | Logo + tabs + utilities all on one row | | Badges | None | Live agent count on Agents, unresolved count on Inbox | | Theme toggle | None | 3-state Sun/Monitor/Moon | | Command palette | None | Cmd+K trigger in header | | Health indicator | None | Status dot (green/yellow/red) | --- ## Default State ``` +=============================================================================+ | [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] * myws | +=============================================================================+ | | | | | full-width padded content (px-4 sm:px-6 lg:px-8) | | | | | | | | | | | | | | | +=============================================================================+ ``` Note: `max-w-*` constraints are set per-page, not globally. Pages like Agents and Initiative Detail use full-width layouts. The Initiatives list and Settings may opt into `max-w-6xl` or similar. The root layout applies only horizontal padding. ## Active Tab Highlighted ``` +=============================================================================+ | [CW] [*Initiatives*] Agents [*3] Inbox [2] Settings [⌘K] [sun] * myws | +=============================================================================+ | | | | | | +=============================================================================+ ``` Active tab gets `bg-muted rounded-md` treatment. Text is `font-medium` (not bold — `font-medium` is 500 weight, sufficient contrast without the heaviness of `font-bold` at these small sizes). ## Zero Badges (no running agents, no pending conversations) ``` +=============================================================================+ | [CW] Initiatives Agents Inbox Settings [⌘K] [sun] * myws | +=============================================================================+ | | | | | | +=============================================================================+ ``` Badges are completely hidden when count is 0 — no empty brackets or `*0`. ## Health Dot States ``` * ← green: WebSocket connected, last heartbeat < 5s ago ? ← yellow: connected but heartbeat > 5s (slow/degraded) - ← red: WebSocket disconnected or server unreachable ``` The dot uses `status-success-dot`, `status-warning-dot`, or `status-error-dot` tokens from the theme. The green state gets a subtle `animate-pulse` removed after 3s of stable connection to avoid distraction. ### Tooltip on hover Rich tooltip (not just `title` attr) with structured content: ``` +-------------------------------+ | ● Connected | | Latency: 42ms | | Uptime: 2h 14m | | Last heartbeat: <1s ago | +-------------------------------+ +-------------------------------+ | ◐ Degraded | | Latency: 2,340ms | | Last heartbeat: 8s ago | | Server may be under load | +-------------------------------+ +-------------------------------+ | ○ Disconnected | | Lost connection 12s ago | | Retrying... (attempt 3) | +-------------------------------+ ``` - Uses shadcn `` with `` - Tooltip content is `text-xs font-mono` for data readability - Status label uses matching status token color ## Connection Banner When the WebSocket disconnects, a full-width banner slides down between the header and page content. This is more aggressive than the dot alone — the dot is for glanceable status, the banner is for "you need to know this NOW." ### Reconnecting ``` +=============================================================================+ | [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] - myws | +=============================================================================+ | [spinner] Reconnecting to server... [Dismiss] | +=============================================================================+ | | | | +=============================================================================+ ``` - `bg-status-warning-bg border-b border-status-warning-border` - Text: `text-status-warning-fg text-sm` - Spinner: 14px animated, same as SaveIndicator - Dismiss hides the banner but the health dot stays red/yellow ### Offline (> 30s disconnected) ``` +=============================================================================+ | [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] - myws | +=============================================================================+ | [!] Server unreachable — data may be stale [Retry] [Dismiss] | +=============================================================================+ | | | | +=============================================================================+ ``` - `bg-status-error-bg border-b border-status-error-border` - Text: `text-status-error-fg text-sm` - Retry button: `text-status-error-fg underline font-medium` ### Reconnected (success, auto-dismisses) ``` +=============================================================================+ | [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] * myws | +=============================================================================+ | [v] Reconnected | +=============================================================================+ ``` - `bg-status-success-bg text-status-success-fg text-sm` - Auto-dismisses after 3s with fade-out ### Timing | Duration disconnected | Behavior | |----------------------|----------| | 0-3s | No banner (brief blips are silent, dot goes yellow) | | 3-30s | "Reconnecting..." banner | | >30s | "Server unreachable" banner with Retry | | On reconnect | "Reconnected" for 3s, then dismiss | --- ## Notification Indicators Important events must surface even when the user is on a different tab. Three mechanisms work together: ### 1. Tab badges (already specified above) `Agents [*3]` and `Inbox [2]` update in real-time via tRPC subscriptions. ### 2. Toast notifications (sonner) Critical events fire a toast regardless of current page: | Event | Toast | Duration | |-------|-------|----------| | Agent crashed | `"blue-fox-7 crashed"` + link to Agents | Persistent (manual dismiss) | | Task needs approval | `"Task ready for approval"` + link | Persistent | | All tasks complete | `"Initiative complete — ready for review"` | 8s auto-dismiss | | Account exhausted | `"Account X exhausted, switched to Y"` | 5s auto-dismiss | | Agent asking question | `"blue-fox-7 has a question"` + link to Inbox | 8s auto-dismiss | Persistent toasts stack in bottom-right. Max 3 visible; older ones collapse into a "+N more" indicator. ### 3. Browser tab title When the app is backgrounded and events occur: - `(2) Codewalk District` — number prefix for unread attention items - Counts: crashed agents + pending approvals + unanswered inbox items - Resets to `Codewalk District` when user returns to the tab --- ## Collapsed Mobile View (not yet implemented) ``` +=============================================================================+ | [CW] [⌘K] [sun] * [☰ 2] | +=============================================================================+ | | | | | | +=============================================================================+ ``` Note: Mobile hamburger menu is not implemented. This is a placeholder for future work. At `< 768px`, nav tabs collapse into a hamburger `[☰]` dropdown. The hamburger icon shows a combined badge count (crashed agents + unread inbox) so that attention items are never hidden behind the collapsed menu. ## 404 Page ``` +=============================================================================+ | [CW] Initiatives Agents Inbox Settings [⌘K] [sun] * myws | +=============================================================================+ | | | | | [AlertCircle] | | Page not found | | | | [ Back to Initiatives ] | | | | | +=============================================================================+ ``` --- ## 48px Header Anatomy 48px is tight. This works because: - Logo is icon-only (24px, no wordmark) - Nav tabs use `px-3 py-1.5` (compact hit targets, 36px tall inside 48px) - Badges are inline pills, not separate elements - Right cluster items are icon-sized (no text labels except ⌘K) If this still feels cramped at certain content widths, the Cmd+K button drops its text label at `< 1024px` (see Responsive Notes below). ``` +-----------------------------------------------------------------------------+ | LEFT CLUSTER SPACER RIGHT CLUSTER | | [CW] [Nav] [Nav [N]] [Nav [N]] [Nav] ------- [⌘K] [◐] [●] [workspace] | +-----------------------------------------------------------------------------+ ↑ ↑ ↑ ↑ ↑ 24px icon Cmd+K Theme Health Workspace ``` ### Left cluster (nav) - `[CW]` — 24x24 logo icon, links to `/initiatives` - Nav tabs: `Initiatives`, `Agents`, `Inbox`, `Settings` - Each tab is an `` with `gap-1.5` between label and optional badge - Active tab: `bg-muted text-foreground font-medium rounded-md px-3 py-1.5` - Inactive tab: `text-muted-foreground hover:text-foreground px-3 py-1.5` - Keyboard: `1` / `2` / `3` / `4` navigate to tabs (when no input is focused) ### Badge rendering — unified `` component Both tabs use the same badge component for visual consistency: ```tsx // Unified pill badge: inline, compact, color-coded {count} ``` | Tab | Badge color | Example | Semantics | |-----|-------------|---------|-----------| | Agents | `bg-status-active-bg text-status-active-fg` (blue) | `Agents [3]` | Running agent count | | Inbox | `bg-status-warning-bg text-status-warning-fg` (amber) | `Inbox [2]` | Unresolved conversation count | Both badges: - Hidden entirely when count is 0 - Use `tabular-nums` so width doesn't shift when count changes (e.g., 9 -> 10) - Pill shape: `rounded-full` with tight `px-1.5 py-0.5` - Font: `text-[10px]` — small enough to not compete with the tab label **Attention escalation**: When an agent has crashed or a task needs approval, the Agents badge switches to `bg-status-error-bg text-status-error-fg` (red) to draw the eye. The count reflects crashed + running agents in this state. ### Right cluster (utilities) - `[⌘K]` — Shows platform-aware shortcut hint as label: `⌘K` on Mac, `Ctrl+K` on Windows/Linux. Rendered as a `` style pill: `bg-muted text-muted-foreground text-xs font-mono px-2 py-1 rounded-md border border-border`. Clicking opens the CommandPalette overlay (see shared-components.md). - `[◐]` — ThemeToggle, 3-state segmented control (see shared-components.md) - `[●]` — Health status dot, 8px circle with color (see Health Dot States above) - `[workspace]` — Workspace name, derived from `.cwrc` or directory basename. `text-xs text-muted-foreground font-mono truncate max-w-[120px]`. Tooltip shows full path on hover: `/Users/me/projects/my-app`. Hidden at `< 1024px` to save space. ### Workspace identity The workspace label solves a real problem: developers often run multiple Codewalk instances for different projects. Without it, you can't tell which instance you're looking at without checking the terminal. ``` Right cluster detail: [⌘K] [sun|monitor|moon] ● codewalk-district ^^^^^^^^^^^^^^^^^^ text-xs font-mono text-muted-foreground truncated at 120px, full path in tooltip ``` ### Spacing - Left cluster: `gap-1` between items - Between clusters: `flex-1` spacer pushes them apart - Right cluster: `gap-2` between items (tightened from `gap-3` — the workspace label needs room) --- ## Responsive Notes | Breakpoint | Header | Content | |------------|--------|---------| | `>= 1280px` | Full header as shown, workspace label visible | Page decides own `max-w-*` | | `1024px - 1279px` | Workspace label hidden, ⌘K shows icon-only `[search]` | Page decides own `max-w-*` | | `768px - 1023px` | Same as above, tab labels may truncate | Horizontal padding reduces | | `< 768px` | Nav tabs collapse into hamburger (future) | Single-column, `px-4` | Content area uses `px-4 sm:px-6 lg:px-8` horizontal padding only. There is **no global** `max-w-7xl`. Each page sets its own max-width constraint (or uses none for full-bleed layouts like the Agents split-pane view). ### Per-page width strategy | Page | Width constraint | Rationale | |------|-----------------|-----------| | Initiatives list | `max-w-6xl mx-auto` | Card grid, doesn't benefit from extreme width | | Initiative detail | Full width | Tab content (execution, agents) needs room | | Agents | Full width | Split-pane: agent list + output viewer | | Inbox | Full width | Split-pane: conversation list + detail | | Settings | `max-w-4xl mx-auto` | Forms, narrow content | --- ## Global Components (rendered in root layout) - `` (sonner) — bottom-right toast notifications, max 3 visible - `` — wraps the page outlet - `` — overlay, triggered by Cmd+K or header button - `` — slides between header and content on disconnect - `` — invisible, manages `document.title` with unread counts --- ## Keyboard Shortcuts (global) These are registered at the root layout level, suppressed when an input/textarea is focused: | Shortcut | Action | |----------|--------| | `⌘K` / `Ctrl+K` | Open command palette | | `1` | Navigate to Initiatives | | `2` | Navigate to Agents | | `3` | Navigate to Inbox | | `4` | Navigate to Settings | | `?` | Show keyboard shortcut help overlay (future) | --- ## Source - `packages/web/src/routes/__root.tsx` - `packages/web/src/layouts/AppLayout.tsx` - `packages/web/src/components/ThemeToggle.tsx` (proposed) - `packages/web/src/components/CommandPalette.tsx` (proposed) - `packages/web/src/components/HealthDot.tsx` (proposed) - `packages/web/src/components/ConnectionBanner.tsx` (proposed) - `packages/web/src/components/NavBadge.tsx` (proposed) - `packages/web/src/hooks/useWorkspaceName.ts` (proposed) - `packages/web/src/hooks/useBrowserTitle.ts` (proposed) --- ## Design Review Notes Reviewed against MISSION CONTROL criteria (dense, status-at-a-glance, keyboard-first, dark-mode-first). Changes applied inline above. ### 1. 48px header — is it cramped? 48px is fine IF the content is disciplined: icon-only logo, no wordmark, compact tab padding, and small inline badges. Added an explicit note to the Header Anatomy section explaining why 48px works and where the escape hatch is (responsive breakpoints drop ⌘K text and workspace label). The real risk is not height but **horizontal** crowding when all badges are showing and the workspace name is long. The `truncate max-w-[120px]` on the workspace label and responsive hiding address this. ### 2. Badge inconsistency (`*N` vs `(N)`) This was a real problem. The v1 spec used `*N` for Agents and `(N)` for Inbox, two completely different visual languages for the same concept (a count badge on a nav tab). Unified both into a `` component — a colored pill with just the number. Color differentiates meaning (blue=active, amber=pending), not punctuation. Added `tabular-nums` to prevent layout shift on count changes. ### 3. Health dot — too subtle? The dot alone was easy to miss, yes. But the right fix is NOT making the dot bigger — it is adding a **second tier of communication**. The dot stays as a glanceable ambient indicator (like a server rack LED). The new ConnectionBanner handles the "you need to stop and pay attention" case. Added a rich tooltip with latency, uptime, and heartbeat data for the "I want details" case. Three tiers: dot (ambient) -> tooltip (on-demand) -> banner (urgent). ### 4. Connection banner — missing Added full spec: Reconnecting (3-30s), Offline (>30s), Reconnected (success). The 3s delay before showing the banner prevents flicker on brief WebSocket reconnections. The banner is dismissible but the dot stays colored. See "Connection Banner" section above. ### 5. Workspace identity — missing Added workspace name to the right cluster. Derived from `.cwrc` config or directory basename. This is essential for multi-instance users. Hidden below 1024px because it is useful but not critical — the browser tab title or terminal provides fallback identification. ### 6. Cmd+K shortcut hint Changed from `[cmd-k]` text to a ``-styled `[⌘K]` pill showing the actual platform shortcut. This doubles as both a clickable trigger and a discoverability hint. Users see the shortcut every time they glance at the header, which builds muscle memory. On Windows/Linux it renders as `Ctrl+K`. ### 7. Notification system — missing This was the biggest gap. Added three tiers: - **Tab badges**: real-time counts (already existed, now specified with attention escalation) - **Toast notifications**: critical events fire toasts regardless of current page. Persistent for crashes/approvals, auto-dismiss for informational events. - **Browser tab title**: `(2) Codewalk District` prefix when backgrounded with unread attention items. Standard web app pattern. The attention escalation on the Agents badge (blue -> red when something crashes) is important — it means you do not need to be on the Agents page to know something went wrong. ### 8. `max-w-7xl` constraint — wrong default Removed. A mission control tool should default to full-width and let pages opt into narrower layouts. The split-pane views (Agents, Inbox) and the Initiative Detail tabs lose significant usable space under `max-w-7xl` (1280px). Replaced with per-page width strategy: forms/lists get `max-w-4xl`/`max-w-6xl`, operational views get full width. The root layout only applies horizontal padding. ### Open questions for next review - **Favicon badge**: Should the browser favicon show a red dot for attention items, like Slack does? More noticeable than title prefix alone. - **Sound**: Should critical events (agent crash) play a subtle notification sound? Power users managing 10+ agents may not be watching the screen. - **Keyboard shortcut overlay**: `?` to show all shortcuts is specced as "future" — should it be in v2 scope given the keyboard-first stance?