- '+' button in header opens popover listing hidden widgets with Add button
- Per-widget kebab menu (⋮) with 'Remove from dashboard' option
- Kebab menu appears on hover, keeps UI clean
- Visible widget set persisted to localStorage (v1 key)
- Reset Layout restores both grid positions AND visible widgets
- Uses widget-meta.ts to show labels + Demo tier badge
- No new endpoints, no new widget types, no search/filter
ROUTING:
- / → /chat/main (was /dashboard)
- Sessions is the default landing, not Dashboard
- Dashboard is for observing, Workspace is for working
DASHBOARD HEADER:
- Removed Quick Actions (New Chat, Terminal, Skills, Files)
Navigation belongs in the WorkspaceShell sidebar, not dashboard
- Added 'Workspace →' cross-link button (goes to /chat/main)
- Header is now: Identity | Context Strip | Workspace→ | Utilities
WIDGET METADATA (structural prep):
- New widget-meta.ts: scope, tier, defaultVisible per widget
- No UI built — data structure only for future widget management
DOCS:
- NAVIGATION_ARCHITECTURE.md: routing map, sidebar IA spec,
shell descriptions, design principles
SIDEBAR (existing):
- Already has Dashboard link — no changes needed
- Already has all workspace routes organized
NOT CHANGED:
- No sidebar redesign (separate PR if needed)
- No dashboard widget changes
- No new features or interactions
WHY REMOVED:
System Status widget showed: connection state, model, uptime, sessions.
All four are now in the header context strip. The widget was pure
duplication — removing it makes the dashboard calmer with no information
loss.
HEADER CONTEXT (left-aligned after product name):
● Connected · Opus 4.6 · 9 sessions · Up 7m
GRID (7 widgets, was 8):
Row 0: Active Agents + Cost Tracker
Row 1: Usage Meter + Recent Sessions
Row 2: Activity Log + Notifications
Row 3: Tasks (Demo)
REMOVED:
- system-status from WidgetId type
- system-status from WIDGET_REGISTRY
- system-status from lg layout
- SystemStatusWidget grid child + import
- Widget file NOT deleted (available for debug page)
NO NEW: widgets, data sources, interactions, or indicators.
THREE-ZONE HEADER:
LEFT — Identity (locked):
Logo + 'OpenClaw Studio', no subtitle, no marketing
CENTER — System Context Strip (new, text-only):
'● Opus 4.6 · SESSIONS 9 · UP 2h 15m'
- Green/red dot for gateway status
- Model name in semibold
- Labels uppercase tracking-wide, values normal
- Tabular numerals, subtle · separators
- Hidden on mobile, truncates uptime first
RIGHT — Utilities (rationalized):
- Quick Actions → icon-only (15px) with aria-labels
- Theme toggle → ghost style (no border/bg)
- Reset + Settings → ghost icons
- Vertical separator between nav and tool icons
- Removed 'Add Widget' disabled button (noise)
- All icons size-7, consistent ghost treatment
STYLE:
- Header: rounded-xl→rounded-lg, py-3→py-2.5
- No shadows, no glass, no backdrop-blur
- Single flex row, no wrapping
HERO METRICS: Intentionally NOT removed. Header provides
context; hero row provides numeric snapshot. If hero row
feels redundant after this, it can be removed in a future
pass (not this PR).
WHAT IS NOT IN THE HEADER:
- Cost/spend (belongs in cards)
- Activity/events (belongs in stream)
- Notifications (secondary)
- Logo + 'OpenClaw Studio' + subtitle on the left
- Ambient pill + theme + reset + add + settings on the right
- All vertically centered in one row
- Subtitle hidden on xs for tight screens
- Quick Actions row below, tighter mt-3 gap
- Logo icon in 'Studio Overview' pill (replaces generic dashboard icon)
- Large logo (10/12px) next to title with 'OpenClaw Studio' branded text
- 'Studio' in muted primary-500 for visual hierarchy
- Shortened subtitle for cleaner header
- Removed unused DashboardSquare01Icon import
- New SettingsDialog component: scrollable overlay with all settings sections
(Gateway, Appearance, Editor, Notifications, Smart Suggestions)
- Gear icon opens dialog instead of navigating to /settings
- Compact layout: smaller icons, tighter spacing, fits 80vh
- Auto-saves to localStorage like the full settings page
- Fetches available models on open for dropdowns
Cost Tracker:
- Period Spend total → text-2xl font-bold (was text-lg font-medium)
- Label → uppercase tracking-wide to match System Status style
Usage Meter:
- Loading state → spinner + softer text instead of plain 'Loading usage data...'
- Stronger border (primary-300) to stand out as authoritative panel
- Labels → uppercase tracking-wide for instrument-panel feel
- Gateway dot has subtle glow shadow
- Values → font-semibold for scanability
- Tighter py (2.5→2) to fit more in card
- Debug button → subtle text link instead of outlined button (visually secondary)
- Description updated: 'Authoritative gateway health and runtime'
- Bumped dark theme CSS variables: primary-200 from 0.2078→0.26, primary-300 from 0.3→0.34, etc.
- Surface now warm dark (oklch 0.11) instead of pure black
- Added dark: variants to hero metrics accent colors (emerald, amber, blue)
- Fixed bg-white references in Quick Actions, theme toggle, weather input
- Added dark variants to error states (red), badges (amber), agent status pills
- Activity log connected badge dark-aware
- Glass card wrapper slightly more opaque in dark mode
- New ThemeToggle component (src/components/theme-toggle.tsx)
- Uses existing Zustand theme store (persisted)
- Toggles dark↔light, applies class to document root
- Placed next to Reset Layout button in dashboard header
- Add HeroMetricsRow component (Model, Sessions, Uptime, Period Spend)
- Move Quick Actions from draggable grid to persistent header buttons
- Remove quick-actions from WidgetId type and WIDGET_REGISTRY
- Move weather widget to bottom of grid (below the fold)
- Wire hero row to live gateway data (sessions, cost, system status)
- Use AiChipIcon for model metric
- docs/QA/DASHBOARD_GOLDEN_PATH.md: full widget audit, RPC map, manual test steps
- scripts/dashboard-smoke.mjs: automated API shape validation (6/6 pass)
- All 10/11 widgets confirmed wired to real gateway data
- Tasks widget labeled Demo (no backend exists)
- Answers all wiring questions from production wiring plan
- Unified DashboardGlassCard as single widget wrapper with optional draggable prop
- Drag handle now integrated in widget header (grab icon, no text)
- Removed hover-float effect (translateY + shadow-md) from all cards
- Replaced with subtle border highlight on hover
- Deleted dead code: widget-chrome.tsx
- Removed DragHandle overlay component from dashboard-screen
- Simplified grid items: widgets receive draggable prop directly
- All 11 widgets updated with draggable prop pass-through
- Build passes, no secrets exposed
- Docs: DASHBOARD_PRODUCTION_POLISH_PLAN.md + QA test plan
Single file review covering:
- All deliverables (checklist, instructions, scripts, report)
- Golden Path (12 steps) breakdown
- Full test suite (35 cases) details
- Build verification output
- Security scan results
- Script details and usage
- Known issues
- Next steps
- Merge checklist
File: PHASE_5_REVIEW.md (~14 KB)
For Eric's review before merge
Golden Path (12 mandatory steps):
1. Fresh install / reset state
2. Start Studio + Gateway
3. Add provider credentials
4. Open Chat
5. Send simple message
6. Switch model (idle)
7. Enable Smart Suggestions
8. Trigger downgrade suggestion
9. Save current state as Mode
10. Apply Mode
11. Restart Studio
12. Verify state restored correctly
Critical rule: If ANY step fails, STOP and report before continuing.
Updated files:
- BETA_CHECKLIST_v2.1.2.md: Added Golden Path section at top
- TESTER_INSTRUCTIONS.md: Emphasized Golden Path requirement
- BETA_READINESS_REPORT.md: Updated test count (35 total)
Total: 35 test cases (12 Golden Path + 23 full suite)
Validation:
- Track unavailable pinned models (not in /api/models)
- Show as "Unavailable" with Remove button
- Red styling to indicate issue
Accessibility:
- All pin/unpin buttons have descriptive aria-labels
- Keyboard focus styles (focus:ring-2)
- focus:opacity-100 on pin button for keyboard users
- Remove button for unavailable pins is keyboard accessible
Changes:
- availableModelIds Set tracks currently available models
- unavailablePinnedModels computed from pinned - available
- Unavailable pins shown in pinned section with Remove button
- aria-label on all model select + pin/unpin buttons
- focus:ring-2 + focus:outline-none for keyboard navigation
Goal: Make model switching personal and intentional
Features:
1) Pinned Models in Model Switcher
- Star icons (☆ unpinned, ⭐ pinned) to pin/unpin models
- Pinned section at top of dropdown
- localStorage persistence + cross-tab sync
- Empty section hidden when no pins
2) Preferred Models in Settings
- Preferred Budget Model dropdown
- Preferred Premium Model dropdown
- Used as primary targets for Smart Suggestions
- Falls back to tier logic if unavailable
3) Only Suggest Cheaper Toggle
- New setting: onlySuggestCheaper (default OFF)
- When enabled: never suggest upgrades, only downgrades
- Provides granular control over suggestion behavior
Implementation:
- usePinnedModels hook (NEW)
- togglePin(modelId), isPinned(modelId)
- storage event listener for cross-tab sync
- useModelSuggestions (UPDATED)
- Check preferred budget/premium models first
- Fall back to tier-based logic
- Skip upgrades when onlySuggestCheaper enabled
- ChatComposer (UPDATED)
- Pinned section renders at top
- Star emoji buttons (no icon dependency)
- Hover to show pin, always visible to unpin
- Settings (UPDATED)
- Smart Suggestions section with 4 controls
- Fetches models via /api/models for dropdowns
- Auto-detect option (empty string)
Storage:
- localStorage.pinnedModels: string[] of model IDs
- settings.preferredBudgetModel: string (model ID or '')
- settings.preferredPremiumModel: string (model ID or '')
- settings.onlySuggestCheaper: boolean
Bundled with Phase 4.1:
- Smart Suggestions (ModelSuggestionToast, useModelSuggestions)
- Not merged to main yet, included here as prerequisite
Docs:
- PHASE_4.2_PINNED_MODELS.md (spec)
- QA test plan (16 tests + 3 edge cases)
- QA results (all build pass)
Build: ✅ Passes in 807ms
Security: ✅ Clean (no secrets)
Bundle: 369.88 kB (+6.68 kB from Phase 4.1)
What changed: Pinned models + preferred defaults + only-cheaper toggle
How to test: Pin models in dropdown, set preferred in Settings, toggle only-cheaper
Risks: None - additive only, no breaking changes
Security: Clean - localStorage only, no sensitive data