From 2a6d02b46ba10473d2b3cd56afa243fe5ec4ae4c Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 22 Feb 2026 14:28:03 -0600 Subject: [PATCH 1/6] chore(orchestrator): Bootstrap MS15-DashboardShell milestone - Created PRD.md with full platform requirements and milestone plan - Updated tasks.md with 18 tasks across 4 phases - Created scratchpad for ms15 orchestration - Issues #448, #449, #450 created in Gitea - Milestone: MS15-DashboardShell (0.0.15) Co-Authored-By: Claude Opus 4.6 --- docs/PRD.md | 233 +++++++++++++++++++++++ docs/scratchpads/ms15-dashboard-shell.md | 63 ++++++ docs/tasks.md | 31 +++ 3 files changed, 327 insertions(+) create mode 100644 docs/PRD.md create mode 100644 docs/scratchpads/ms15-dashboard-shell.md diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 0000000..c8cf005 --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,233 @@ +# PRD: Mosaic Stack Dashboard & Platform Implementation + +## Metadata + +- Owner: Jason Woltje +- Date: 2026-02-22 +- Status: in-progress +- Best-Guess Mode: true + +## Problem Statement + +The Mosaic Stack web UI has a basic navigation and simple widget-based dashboard that doesn't match the production-ready design vision. The reference design (dashboard.html) defines a comprehensive command center UI with sidebar navigation, topbar, terminal panel, and multiple page layouts. The current implementation uses mismatched design tokens (raw Tailwind colors vs CSS variables), has no collapsible sidebar, no global terminal, and lacks the polished design system from the reference. + +## Objectives + +1. Implement the dashboard.html reference design as the production UI foundation +2. Establish a consistent CSS design token system that supports multiple themes +3. Build a responsive, accessible app shell with collapsible sidebar and full-width header +4. Create a theme system supporting installable theme packages +5. Build all dashboard pages (Dashboard, Projects, Workspace, Kanban, Files, Logs, Settings, Profile) +6. Implement real backend integration (no mock data) +7. Support multi-tenant configuration with RBAC +8. Implement federation (master-master and master-slave) +9. Build global terminal, project chat, and master chat session +10. Configure telemetry with opt-out support + +## Scope + +### In Scope (Milestone 0.0.15 — Dashboard Shell & Design System) + +1. CSS design token system overhaul (colors, fonts, spacing, radii from dashboard.html) +2. App shell layout: sidebar + full-width header + main content area +3. Full-width header with logo, search, system status, terminal toggle, notifications, theme toggle, user avatar dropdown +4. Collapsible sidebar with nav groups, icons, badges, active states, collapse/expand button +5. Responsive layout with hamburger button at small breakpoints, sidebar hidden by default at mobile +6. Light/dark theme matching the reference design +7. Mosaic logo icon as global loading spinner +8. Shared component updates in packages/ui (Card, Badge, Button, Dot, MetricsStrip, ProgressBar, FilterTabs, SectionHeader, Table, LogLine, Terminal panel) +9. Dashboard page: metrics strip, active orchestrator sessions, quick actions, activity feed, token budget +10. Grain overlay texture from reference design + +### In Scope (Future Milestones — Documented for Planning) + +11. Additional pages: Projects, Workspace, Kanban, File Manager, Logs & Telemetry, Settings, Profile +12. Theme system with installable theme packages +13. Widget system with installable widget packages, customizable sizes +14. Global terminal (project/orchestrator level, smart) +15. Project-level orchestrator chat +16. Master chat session (collapsible sidebar/slideout, always available) +17. Settings page for ALL environment variables, dynamically configurable via webUI +18. Multi-tenant configuration with admin user management +19. Team management with shared data spaces and chat rooms +20. RBAC for file access, resources, models +21. Federation: master-master and master-slave with key exchange +22. Federation testing: 3 instances on Coolify (woltje.com domain) +23. Agent task mapping configuration (system-level defaults, user-level overrides) +24. Telemetry: opt-out, customizable endpoint, sanitized data +25. File manager with WYSIWYG editing (system/user/project levels) +26. User-level and project-level Kanban with filtering +27. Break-glass authentication user +28. Playwright E2E tests for all pages +29. API documentation via Swagger +30. Backend endpoints for all dashboard data + +### Out of Scope + +1. Mobile native app +2. Third-party marketplace for themes/widgets (initial implementation is local package management only) +3. Production deployment to non-Coolify targets +4. Calendar system redesign (existing calendar implementation is retained) + +## User/Stakeholder Requirements + +1. The `jarvis` user must be able to log into mosaic.woltje.com via Authentik as administrator with access to all pages +2. A standard `jarvis-user` must operate at a lower permission level +3. A break-glass user must have access without Authentik authentication +4. All pages must be navigable without errors +5. Light and dark themes must work across all pages and components +6. Sidebar must be collapsible with open/close button; hidden by default at small breakpoints +7. Hamburger button visible at lower breakpoints for sidebar control +8. The Mosaic Stack logo icon must be the site-wide loading spinner +9. No mock data — all data pulled from backend APIs + +## Functional Requirements + +### FR-001: Design Token System + +- CSS custom properties for all colors, spacing, typography, radii +- Dark theme as default (`:root`), light theme via `[data-theme="light"]` +- Fonts: Outfit (body), Fira Code (monospace) +- All components must use design tokens, never hardcoded colors + +### FR-002: App Shell Layout + +- CSS Grid: sidebar column + header row + main content +- Full-width header spanning above sidebar and content +- ASSUMPTION: Header spans full width including above sidebar area. The logo is in the header, not the sidebar. Rationale: User explicitly stated "The logo will NOT be part of the sidebar." + +### FR-003: Sidebar Navigation + +- Nav groups: Overview (Dashboard), Workspace (Projects, Project Workspace, Kanban, File Manager), Operations (Logs & Telemetry, Terminal), System (Settings) +- Collapsible: icon-only mode when collapsed +- Active state indicator (left border accent) +- User card in footer with avatar, name, role, online status +- ASSUMPTION: Sidebar footer user card navigates to Profile page. Rationale: Matches reference design behavior. + +### FR-004: Header/Topbar + +- Logo + brand wordmark (left) +- Search bar with keyboard shortcut hint +- System status indicator +- Terminal toggle button +- Notification bell with badge +- Theme toggle (sun/moon icon) +- User avatar button with dropdown (Profile, Account Settings, Sign Out) + +### FR-005: Responsive Design + +- Breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px) +- Below md: sidebar hidden, hamburger button in header +- md-lg: sidebar can be toggled +- lg+: sidebar visible by default + +### FR-006: Dashboard Page + +- 6-cell metrics strip with colored top borders and trend indicators +- Active Orchestrator Sessions card with agent nodes +- Quick Actions 2x2 grid +- Activity Feed sidebar card +- Token Budget sidebar card with progress bars + +### FR-007: Loading Spinner + +- Mosaic logo icon (4 corner squares + center circle) with CSS rotation animation +- Used as global loading indicator across all pages +- Available as a shared component + +### FR-008: Theme System (Future Milestone) + +- Support multiple themes beyond default dark/light +- Themes are installable packages from Mosaic Stack repo +- Theme installation and selection from Settings page +- ASSUMPTION: Initial implementation supports dark/light from reference design. Multi-theme package system is a future milestone. Rationale: Foundation must be solid before extensibility. + +### FR-009: Terminal Panel (Future Milestone) + +- Bottom drawer panel, toggleable from header and sidebar +- Multiple tabs (Orchestrator, Shell, Build) +- Smart terminal operating at project/orchestrator level +- Global terminal for system interaction + +### FR-010: Settings Page (Future Milestone) + +- All environment variables configurable via UI +- Minimal launch env vars, rest configurable dynamically +- Settings stored in DB with RLS +- Theme selection, widget management, federation config, telemetry config + +## Non-Functional Requirements + +1. Security: All API endpoints require authentication. RBAC enforced. No PII in telemetry. Secrets never hardcoded. +2. Performance: Dashboard loads in <2s. No layout shift during theme toggle. Sidebar toggle is instant (<100ms animation). +3. Reliability: Break-glass auth ensures access when Authentik is down. +4. Observability: Telemetry with opt-out support. Wide-event logging. Customizable telemetry endpoint. + +## Acceptance Criteria + +### Milestone 0.0.15 + +1. Design tokens from dashboard.html are implemented in globals.css +2. App shell shows full-width header with logo, collapsible sidebar, main content area +3. Sidebar has all nav groups with icons, collapses to icon-only mode +4. Hamburger button appears at mobile breakpoints, sidebar hidden by default +5. Light/dark theme toggle works across all components +6. Mosaic logo spinner is used as site-wide loading indicator +7. Dashboard page shows metrics strip, orchestrator sessions, quick actions, activity feed, token budget +8. All shared components in packages/ui use design tokens (no hardcoded colors) +9. Lint, typecheck, and existing tests pass +10. Grain overlay texture from reference is applied + +### Full Project (All Milestones) + +11. jarvis user logs in via Authentik, has admin access to all pages +12. jarvis-user has standard access at lower permission level +13. Break-glass user has access without Authentik +14. Three Mosaic Stack instances on Coolify with federation testing +15. Playwright tests confirm all pages, functions, theming work +16. No errors during site navigation +17. API documented via Swagger with proper auth gating +18. Telemetry working locally with wide-event logging +19. Mosaic Telemetry properly reporting to telemetry endpoint + +## Constraints and Dependencies + +1. Next.js 16 with App Router — all pages use server/client component patterns +2. Tailwind CSS 3.4 — design tokens must integrate with Tailwind's utility class system +3. BetterAuth for authentication — must maintain existing auth flow +4. Authentik as IdP at auth.diversecanvas.com — must remain operational +5. PostgreSQL 17 with Prisma — all settings stored in DB +6. Coolify for deployment — 3 instances needed for federation testing +7. packages/ui is shared across apps — changes affect all consumers + +## Risks and Open Questions + +1. **Risk**: Changing globals.css design tokens may break existing pages (login, knowledge, calendar). Mitigation: Thorough regression testing. +2. **Risk**: packages/ui uses hardcoded Tailwind colors — migration to CSS variables needs care. Mitigation: Phase the migration, test each component. +3. **Open**: Exact federation protocol details for master-master vs master-slave data sync. +4. **Open**: Specific telemetry data points to collect. +5. **Open**: Agent task mapping configuration schema (informed by OpenClaw research). + +## Testing and Verification + +1. Baseline: `pnpm lint && pnpm build` must pass +2. Situational: Visual verification at sm/md/lg/xl breakpoints +3. Situational: Theme toggle across all pages +4. Situational: Sidebar collapse/expand at all breakpoints +5. E2E: Playwright tests for all page navigation +6. E2E: Auth flow with Authentik +7. Federation: Master-master and master-slave data access tests + +## Delivery/Milestone Intent + +| Milestone | Version | Focus | +| ----------------------- | ------- | ----------------------------------------------------------------- | +| MS15-DashboardShell | 0.0.15 | Design system + app shell + dashboard page | +| MS16-Pages | 0.0.16 | Projects, Workspace, Kanban, Settings, Profile, Files, Logs pages | +| MS17-BackendIntegration | 0.0.17 | API endpoints, real data, Swagger docs | +| MS18-ThemeWidgets | 0.0.18 | Theme package system, widget registry, dashboard customization | +| MS19-ChatTerminal | 0.0.19 | Global terminal, project chat, master chat session | +| MS20-MultiTenant | 0.0.20 | Multi-tenant, teams, RBAC, RLS enforcement, break-glass auth | +| MS21-Federation | 0.0.21 | Federation (M-M, M-S), 3 instances, key exchange, data separation | +| MS22-AgentTelemetry | 0.0.22 | Agent task mapping, telemetry, wide-event logging | +| MS23-Testing | 0.0.23 | Playwright E2E, federation tests, documentation finalization | diff --git a/docs/scratchpads/ms15-dashboard-shell.md b/docs/scratchpads/ms15-dashboard-shell.md new file mode 100644 index 0000000..1b343a0 --- /dev/null +++ b/docs/scratchpads/ms15-dashboard-shell.md @@ -0,0 +1,63 @@ +# MS15 — Dashboard Shell & Design System + +## Objective + +Implement the dashboard.html reference design across the Mosaic Stack web app. Establish the design token system, app shell layout, shared components, and dashboard page. + +## Design Reference + +`/home/jwoltje/src/mosaic-stack-website/docs/designs/round-5/claude/01/dashboard.html` + +## Key Architectural Decisions + +1. **Theme approach**: CSS custom properties via `:root` + `[data-theme="light"]`. Tailwind configured to use these variables. ThemeProvider updated to set `data-theme` attribute on ``. +2. **Logo placement**: Logo in full-width header (topbar), NOT in sidebar. User spec overrides reference design. +3. **Sidebar collapse**: Collapsible with icon-only mode. Hidden by default at mobile breakpoints. Hamburger button for small screens. +4. **Fonts**: Outfit (body) + Fira Code (mono). Loaded via `next/font/google`. +5. **Loading spinner**: Mosaic logo icon component with CSS rotation animation. +6. **Grain overlay**: Subtle noise texture via CSS pseudo-element, same as reference. +7. **Per-phase branches**: `feat/ms15-design-system`, `feat/ms15-shared-components`, `feat/ms15-dashboard-page`. + +## Phases + +### Phase 1: Foundation (Design System & App Shell) + +- MS15-FE-001: Design token system overhaul +- MS15-FE-002: App shell grid layout +- MS15-FE-003: Sidebar component +- MS15-FE-004: Topbar/Header component +- MS15-FE-005: Responsive breakpoints +- MS15-FE-006: Loading spinner (Mosaic logo) + +### Phase 2: Shared Components + +- MS15-UI-001: packages/ui token alignment +- MS15-UI-002: Card, Badge, Button, Dot updates +- MS15-UI-003: MetricsStrip, ProgressBar, FilterTabs +- MS15-UI-004: SectionHeader, Table, LogLine +- MS15-UI-005: Terminal panel component + +### Phase 3: Dashboard Page + +- MS15-DASH-001: Metrics strip +- MS15-DASH-002: Active Orchestrator Sessions +- MS15-DASH-003: Quick Actions +- MS15-DASH-004: Activity Feed +- MS15-DASH-005: Token Budget + +### Phase 4: Quality + +- MS15-QA-001: Baseline tests +- MS15-QA-002: Situational tests +- MS15-DOC-001: Documentation + +## Progress Log + +- Session started: 2026-02-22 +- Status: Bootstrap phase + +## Risks + +- Large surface area: globals.css change affects all existing pages +- packages/ui color system mismatch requires careful migration +- Existing pages (login, auth) may need adjustment after token changes diff --git a/docs/tasks.md b/docs/tasks.md index 3026d4f..f09f6b1 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -1,5 +1,36 @@ # Tasks +## MS15-DashboardShell (0.0.15) — Dashboard Shell & Design System + +**Orchestrator:** Claude Code (Opus 4.6) +**Started:** 2026-02-22 +**Branch:** feat/ms15-design-system (Phase 1), feat/ms15-shared-components (Phase 2), feat/ms15-dashboard-page (Phase 3) +**Milestone:** MS15-DashboardShell (0.0.15) +**PRD:** docs/PRD.md + +| id | status | description | issue | repo | branch | depends_on | blocks | agent | started_at | completed_at | estimate | used | notes | +| ------------- | ----------- | -------------------------------------------------------------------------------------------- | ----- | ---- | --------------------------- | ----------------------- | ----------------------------------------------- | ----- | ---------- | ------------ | -------- | ---- | ----- | +| MS15-FE-001 | not-started | Design token system overhaul (globals.css → dashboard.html tokens, dark/light, fonts) | #448 | web | feat/ms15-design-system | | MS15-FE-002,MS15-FE-003,MS15-FE-004,MS15-UI-001 | | | | 25K | | | +| MS15-FE-002 | not-started | App shell grid layout (sidebar + full-width header + main content) | #448 | web | feat/ms15-design-system | MS15-FE-001 | MS15-FE-003,MS15-FE-004,MS15-FE-005 | | | | 20K | | | +| MS15-FE-003 | not-started | Sidebar component (collapsible, nav groups, icons, badges, user card footer) | #448 | web | feat/ms15-design-system | MS15-FE-002 | MS15-FE-005 | | | | 25K | | | +| MS15-FE-004 | not-started | Topbar/Header component (logo, search, status, notifications, theme toggle, avatar dropdown) | #448 | web | feat/ms15-design-system | MS15-FE-002 | MS15-FE-005 | | | | 25K | | | +| MS15-FE-005 | not-started | Responsive layout (breakpoints, hamburger, sidebar auto-hide at mobile) | #448 | web | feat/ms15-design-system | MS15-FE-003,MS15-FE-004 | MS15-QA-001 | | | | 20K | | | +| MS15-FE-006 | not-started | Loading spinner (Mosaic logo icon with rotation animation, site-wide) | #448 | web | feat/ms15-design-system | MS15-FE-001 | | | | | 10K | | | +| MS15-UI-001 | not-started | Align packages/ui tokens with new CSS variable design system | #449 | ui | feat/ms15-shared-components | MS15-FE-001 | MS15-UI-002,MS15-UI-003,MS15-UI-004 | | | | 20K | | | +| MS15-UI-002 | not-started | Update Card, Badge, Button, Dot component variants to match reference | #449 | ui | feat/ms15-shared-components | MS15-UI-001 | MS15-DASH-001 | | | | 25K | | | +| MS15-UI-003 | not-started | Create MetricsStrip, ProgressBar, FilterTabs shared components | #449 | ui | feat/ms15-shared-components | MS15-UI-001 | MS15-DASH-001 | | | | 20K | | | +| MS15-UI-004 | not-started | Create SectionHeader, Table, LogLine shared components | #449 | ui | feat/ms15-shared-components | MS15-UI-001 | MS15-DASH-002 | | | | 15K | | | +| MS15-UI-005 | not-started | Create Terminal panel component (bottom drawer, tabs, output) | #449 | web | feat/ms15-shared-components | MS15-UI-001 | | | | | 20K | | | +| MS15-DASH-001 | not-started | Dashboard metrics strip (6 cells, colored borders, values, trends) | #450 | web | feat/ms15-dashboard-page | MS15-UI-002,MS15-UI-003 | | | | | 15K | | | +| MS15-DASH-002 | not-started | Active Orchestrator Sessions card with agent nodes | #450 | web | feat/ms15-dashboard-page | MS15-UI-004 | | | | | 20K | | | +| MS15-DASH-003 | not-started | Quick Actions 2x2 grid | #450 | web | feat/ms15-dashboard-page | MS15-UI-002 | | | | | 10K | | | +| MS15-DASH-004 | not-started | Activity Feed sidebar card | #450 | web | feat/ms15-dashboard-page | MS15-UI-002 | | | | | 15K | | | +| MS15-DASH-005 | not-started | Token Budget sidebar card with progress bars | #450 | web | feat/ms15-dashboard-page | MS15-UI-003 | | | | | 10K | | | +| MS15-QA-001 | not-started | Baseline tests (lint, typecheck, build) and situational tests (responsive, themes) | #448 | web | feat/ms15-design-system | MS15-FE-005 | | | | | 15K | | | +| MS15-DOC-001 | not-started | Documentation: design system reference, component docs | #448 | docs | feat/ms15-design-system | MS15-QA-001 | | | | | 10K | | | + +--- + ## M10-Telemetry (0.0.10) — Telemetry Integration **Orchestrator:** Claude Code -- 2.49.1 From e615fa80a58a86eddb8fd473bfa66d8f39b5a739 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 22 Feb 2026 14:34:29 -0600 Subject: [PATCH 2/6] feat(web): add MosaicLogo and MosaicSpinner components (MS15-FE-006) - Add MosaicLogo component with 5-element Mosaic brand icon (4 corner squares + center circle), using CSS custom properties for theme-aware colors and optional rotation animation - Add MosaicSpinner wrapper that enables spinning and supports optional label and full-page overlay modes - Replace generic CSS spinner in authenticated layout loading state with MosaicSpinner for consistent brand identity Co-Authored-By: Claude Sonnet 4.6 --- apps/web/src/app/(authenticated)/layout.tsx | 34 +- apps/web/src/app/globals.css | 573 +++++++++--------- apps/web/src/app/layout.tsx | 15 +- apps/web/src/components/layout/AppHeader.tsx | 114 ++++ apps/web/src/components/layout/AppSidebar.tsx | 160 +++++ .../web/src/components/layout/ThemeToggle.tsx | 4 +- apps/web/src/components/ui/MosaicLogo.tsx | 100 +++ apps/web/src/components/ui/MosaicSpinner.tsx | 49 ++ apps/web/src/providers/ThemeProvider.tsx | 32 +- apps/web/tailwind.config.ts | 28 +- .../ms15-fe-006-mosaic-logo-spinner.md | 45 ++ 11 files changed, 850 insertions(+), 304 deletions(-) create mode 100644 apps/web/src/components/layout/AppHeader.tsx create mode 100644 apps/web/src/components/layout/AppSidebar.tsx create mode 100644 apps/web/src/components/ui/MosaicLogo.tsx create mode 100644 apps/web/src/components/ui/MosaicSpinner.tsx create mode 100644 docs/scratchpads/ms15-fe-006-mosaic-logo-spinner.md diff --git a/apps/web/src/app/(authenticated)/layout.tsx b/apps/web/src/app/(authenticated)/layout.tsx index 2580c03..0be3a61 100644 --- a/apps/web/src/app/(authenticated)/layout.tsx +++ b/apps/web/src/app/(authenticated)/layout.tsx @@ -4,8 +4,10 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/lib/auth/auth-context"; import { IS_MOCK_AUTH_MODE } from "@/lib/config"; -import { Navigation } from "@/components/layout/Navigation"; +import { AppHeader } from "@/components/layout/AppHeader"; +import { AppSidebar } from "@/components/layout/AppSidebar"; import { ChatOverlay } from "@/components/chat"; +import { MosaicSpinner } from "@/components/ui/MosaicSpinner"; import type { ReactNode } from "react"; export default function AuthenticatedLayout({ @@ -23,11 +25,7 @@ export default function AuthenticatedLayout({ }, [isAuthenticated, isLoading, router]); if (isLoading) { - return ( -
-
-
- ); + return ; } if (!isAuthenticated) { @@ -35,19 +33,31 @@ export default function AuthenticatedLayout({ } return ( -
- -
+
+ {/* Full-width header — grid-column: 1 / -1 via .app-header CSS class */} + + + {/* Sidebar — left column, row 2, via .app-sidebar CSS class */} + + + {/* Main content — right column, row 2, via .app-main CSS class */} +
{IS_MOCK_AUTH_MODE && (
Mock Auth Mode (Local Only): Real authentication is bypassed for frontend development.
)} - {children} -
+
{children}
+ + {!IS_MOCK_AUTH_MODE && }
); diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index a7bdc88..9206789 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -3,147 +3,250 @@ @tailwind utilities; /* ============================================================================= - DESIGN C: PROFESSIONAL/ENTERPRISE DESIGN SYSTEM - Philosophy: "Good design is as little design as possible." - Dieter Rams + MOSAIC DESIGN SYSTEM — Reference token system from dashboard design ============================================================================= */ /* ----------------------------------------------------------------------------- - CSS Custom Properties - Light Theme (Default) + Primitive Tokens (Dark-first — dark is the default theme) ----------------------------------------------------------------------------- */ :root { - /* Base colors - increased contrast from surfaces */ - --color-background: 245 247 250; - --color-foreground: 15 23 42; + /* Mosaic design tokens — dark palette (default) */ + --ms-bg-950: #080b12; + --ms-bg-900: #0f141d; + --ms-bg-850: #151b26; + --ms-surface-800: #1b2331; + --ms-surface-750: #232d3f; + --ms-border-700: #2f3b52; + --ms-text-100: #eef3ff; + --ms-text-300: #c5d0e6; + --ms-text-500: #8f9db7; + --ms-blue-500: #2f80ff; + --ms-blue-400: #56a0ff; + --ms-red-500: #e5484d; + --ms-red-400: #f06a6f; + --ms-purple-500: #8b5cf6; + --ms-purple-400: #a78bfa; + --ms-teal-500: #14b8a6; + --ms-teal-400: #2dd4bf; + --ms-amber-500: #f59e0b; + --ms-amber-400: #fbbf24; + --ms-pink-500: #ec4899; + --ms-emerald-500: #10b981; + --ms-orange-500: #f97316; + --ms-cyan-500: #06b6d4; + --ms-indigo-500: #6366f1; - /* Surface hierarchy (elevation levels) - improved contrast */ - --surface-0: 255 255 255; - --surface-1: 250 251 252; - --surface-2: 241 245 249; - --surface-3: 226 232 240; + /* Semantic aliases — dark theme is default */ + --bg: var(--ms-bg-900); + --bg-deep: var(--ms-bg-950); + --bg-mid: var(--ms-bg-850); + --surface: var(--ms-surface-800); + --surface-2: var(--ms-surface-750); + --border: var(--ms-border-700); + --text: var(--ms-text-100); + --text-2: var(--ms-text-300); + --muted: var(--ms-text-500); + --primary: var(--ms-blue-500); + --primary-l: var(--ms-blue-400); + --danger: var(--ms-red-500); + --success: var(--ms-teal-500); + --warn: var(--ms-amber-500); + --purple: var(--ms-purple-500); - /* Text hierarchy */ - --text-primary: 15 23 42; - --text-secondary: 51 65 85; - --text-tertiary: 71 85 105; - --text-muted: 100 116 139; + /* Typography */ + --font: var(--font-outfit, 'Outfit'), system-ui, sans-serif; + --mono: var(--font-fira-code, 'Fira Code'), 'Cascadia Code', monospace; - /* Border colors - stronger borders for light mode */ - --border-default: 203 213 225; - --border-subtle: 226 232 240; - --border-strong: 148 163 184; + /* Radius scale */ + --r: 8px; + --r-sm: 5px; + --r-lg: 12px; + --r-xl: 16px; - /* Brand accent - Indigo (professional, trustworthy) */ - --accent-primary: 79 70 229; - --accent-primary-hover: 67 56 202; - --accent-primary-light: 238 242 255; - --accent-primary-muted: 199 210 254; + /* Layout dimensions */ + --sidebar-w: 260px; + --topbar-h: 56px; + --terminal-h: 220px; - /* Semantic colors - Success (Emerald) */ - --semantic-success: 16 185 129; - --semantic-success-light: 209 250 229; - --semantic-success-dark: 6 95 70; + /* Easing */ + --ease: cubic-bezier(0.16, 1, 0.3, 1); - /* Semantic colors - Warning (Amber) */ - --semantic-warning: 245 158 11; - --semantic-warning-light: 254 243 199; - --semantic-warning-dark: 146 64 14; - - /* Semantic colors - Error (Rose) */ - --semantic-error: 244 63 94; - --semantic-error-light: 255 228 230; - --semantic-error-dark: 159 18 57; - - /* Semantic colors - Info (Sky) */ - --semantic-info: 14 165 233; - --semantic-info-light: 224 242 254; - --semantic-info-dark: 3 105 161; - - /* Focus ring */ - --focus-ring: 99 102 241; - --focus-ring-offset: 255 255 255; - - /* Shadows - visible but subtle */ - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05), 0 1px 3px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.06); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.08); -} - -/* ----------------------------------------------------------------------------- - CSS Custom Properties - Dark Theme - ----------------------------------------------------------------------------- */ -.dark { - --color-background: 3 7 18; - --color-foreground: 248 250 252; - - /* Surface hierarchy (elevation levels) */ - --surface-0: 15 23 42; - --surface-1: 30 41 59; - --surface-2: 51 65 85; - --surface-3: 71 85 105; - - /* Text hierarchy */ - --text-primary: 248 250 252; - --text-secondary: 203 213 225; - --text-tertiary: 148 163 184; - --text-muted: 100 116 139; - - /* Border colors */ - --border-default: 51 65 85; - --border-subtle: 30 41 59; - --border-strong: 71 85 105; - - /* Brand accent adjustments for dark mode */ - --accent-primary: 129 140 248; - --accent-primary-hover: 165 180 252; - --accent-primary-light: 30 27 75; - --accent-primary-muted: 55 48 163; - - /* Semantic colors adjustments */ - --semantic-success: 52 211 153; - --semantic-success-light: 6 78 59; - --semantic-success-dark: 167 243 208; - - --semantic-warning: 251 191 36; - --semantic-warning-light: 120 53 15; - --semantic-warning-dark: 253 230 138; - - --semantic-error: 251 113 133; - --semantic-error-light: 136 19 55; - --semantic-error-dark: 253 164 175; - - --semantic-info: 56 189 248; - --semantic-info-light: 12 74 110; - --semantic-info-dark: 186 230 253; - - /* Focus ring */ - --focus-ring: 129 140 248; - --focus-ring-offset: 15 23 42; - - /* Shadows - subtle glow in dark mode */ + /* Legacy shadow tokens (retained for component compat) */ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4); } +/* ----------------------------------------------------------------------------- + Light Theme Override — applied via data-theme attribute on + ----------------------------------------------------------------------------- */ +[data-theme="light"] { + --ms-bg-950: #f8faff; + --ms-bg-900: #f0f4fc; + --ms-bg-850: #e8edf8; + --ms-surface-800: #dde4f2; + --ms-surface-750: #d0d9ec; + --ms-border-700: #b8c4de; + --ms-text-100: #0f141d; + --ms-text-300: #2f3b52; + --ms-text-500: #5a6a87; + + /* Re-alias semantics for light — identical structure, primitive tokens differ */ + --bg: var(--ms-bg-900); + --bg-deep: var(--ms-bg-950); + --bg-mid: var(--ms-bg-850); + --surface: var(--ms-surface-800); + --surface-2: var(--ms-surface-750); + --border: var(--ms-border-700); + --text: var(--ms-text-100); + --text-2: var(--ms-text-300); + --muted: var(--ms-text-500); + + /* Lighter shadows for light mode */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05), 0 1px 3px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.06); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.08); +} + /* ----------------------------------------------------------------------------- Base Styles ----------------------------------------------------------------------------- */ -* { +*, +*::before, +*::after { box-sizing: border-box; } html { + font-size: 15px; font-feature-settings: "cv02", "cv03", "cv04", "cv11"; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { - color: rgb(var(--text-primary)); - background: rgb(var(--color-background)); - font-size: 14px; + font-family: var(--font); + background: var(--bg); + color: var(--text); line-height: 1.5; - transition: background-color 0.15s ease, color 0.15s ease; +} + +a { + color: inherit; + text-decoration: none; +} + +button { + font-family: inherit; + cursor: pointer; + border: none; + background: none; + color: inherit; +} + +input, +select, +textarea { + font-family: inherit; +} + +ul { + list-style: none; +} + +/* Subtle grain/noise overlay for texture */ +body::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 9999; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E"); + opacity: 0.025; +} + +/* ----------------------------------------------------------------------------- + Focus States - Accessible & Visible + ----------------------------------------------------------------------------- */ +@layer base { + :focus-visible { + outline: 2px solid var(--ms-blue-400); + outline-offset: 2px; + } + + :focus:not(:focus-visible) { + outline: none; + } +} + +/* ----------------------------------------------------------------------------- + Scrollbar Styling - Minimal & Professional + ----------------------------------------------------------------------------- */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--muted); +} + +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} + +/* ----------------------------------------------------------------------------- + App Shell Grid Layout + ----------------------------------------------------------------------------- */ +.app-shell { + display: grid; + grid-template-columns: var(--sidebar-w) 1fr; + grid-template-rows: var(--topbar-h) 1fr; + height: 100vh; + overflow: hidden; +} + +.app-header { + grid-column: 1 / -1; + grid-row: 1; + background: var(--bg-deep); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 20px; + gap: 12px; + z-index: 100; +} + +.app-sidebar { + grid-column: 1; + grid-row: 2; + background: var(--bg-deep); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.app-main { + grid-column: 2; + grid-row: 2; + background: var(--bg); + display: flex; + flex-direction: column; + overflow: hidden; + position: relative; } /* ----------------------------------------------------------------------------- @@ -182,102 +285,10 @@ body { } .text-mono { - font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + font-family: var(--mono); font-size: 0.8125rem; line-height: 1.25rem; } - - /* Text color utilities */ - .text-primary { - color: rgb(var(--text-primary)); - } - - .text-secondary { - color: rgb(var(--text-secondary)); - } - - .text-tertiary { - color: rgb(var(--text-tertiary)); - } - - .text-muted { - color: rgb(var(--text-muted)); - } -} - -/* ----------------------------------------------------------------------------- - Surface & Card Utilities - ----------------------------------------------------------------------------- */ -@layer utilities { - .surface-0 { - background-color: rgb(var(--surface-0)); - } - - .surface-1 { - background-color: rgb(var(--surface-1)); - } - - .surface-2 { - background-color: rgb(var(--surface-2)); - } - - .surface-3 { - background-color: rgb(var(--surface-3)); - } - - .border-default { - border-color: rgb(var(--border-default)); - } - - .border-subtle { - border-color: rgb(var(--border-subtle)); - } - - .border-strong { - border-color: rgb(var(--border-strong)); - } -} - -/* ----------------------------------------------------------------------------- - Focus States - Accessible & Visible - ----------------------------------------------------------------------------- */ -@layer base { - :focus-visible { - outline: 2px solid rgb(var(--focus-ring)); - outline-offset: 2px; - } - - /* Remove default focus for mouse users */ - :focus:not(:focus-visible) { - outline: none; - } -} - -/* ----------------------------------------------------------------------------- - Scrollbar Styling - Minimal & Professional - ----------------------------------------------------------------------------- */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: rgb(var(--text-muted) / 0.4); - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: rgb(var(--text-muted) / 0.6); -} - -/* Firefox */ -* { - scrollbar-width: thin; - scrollbar-color: rgb(var(--text-muted) / 0.4) transparent; } /* ----------------------------------------------------------------------------- @@ -292,40 +303,46 @@ body { .btn-primary { @apply btn px-4 py-2; - background-color: rgb(var(--accent-primary)); + background: linear-gradient(135deg, var(--ms-blue-500), var(--ms-purple-500)); color: white; + border-radius: var(--r); } .btn-primary:hover:not(:disabled) { - background-color: rgb(var(--accent-primary-hover)); + box-shadow: 0 8px 28px rgba(47, 128, 255, 0.38); + transform: translateY(-1px); } .btn-secondary { @apply btn px-4 py-2; - background-color: rgb(var(--surface-2)); - color: rgb(var(--text-primary)); - border: 1px solid rgb(var(--border-default)); + background-color: var(--surface); + color: var(--text-2); + border: 1px solid var(--border); + border-radius: var(--r); } .btn-secondary:hover:not(:disabled) { - background-color: rgb(var(--surface-3)); + background-color: var(--surface-2); + color: var(--text); } .btn-ghost { @apply btn px-3 py-2; background-color: transparent; - color: rgb(var(--text-secondary)); + color: var(--muted); + border-radius: var(--r); } .btn-ghost:hover:not(:disabled) { - background-color: rgb(var(--surface-2)); - color: rgb(var(--text-primary)); + background-color: var(--surface); + color: var(--text); } .btn-danger { @apply btn px-4 py-2; - background-color: rgb(var(--semantic-error)); + background-color: var(--danger); color: white; + border-radius: var(--r); } .btn-danger:hover:not(:disabled) { @@ -346,34 +363,36 @@ body { ----------------------------------------------------------------------------- */ @layer components { .input { - @apply w-full rounded-md px-3 py-2 text-sm transition-all duration-150; - @apply focus:outline-none focus:ring-2 focus:ring-offset-0; - background-color: rgb(var(--surface-0)); - border: 1px solid rgb(var(--border-default)); - color: rgb(var(--text-primary)); + @apply w-full text-sm transition-all duration-150; + @apply focus:outline-none; + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: var(--r); + color: var(--text); + padding: 11px 14px; } .input::placeholder { - color: rgb(var(--text-muted)); + color: var(--muted); } .input:focus { - border-color: rgb(var(--accent-primary)); - box-shadow: 0 0 0 3px rgb(var(--accent-primary) / 0.1); + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(47, 128, 255, 0.12); } .input:disabled { @apply opacity-50 cursor-not-allowed; - background-color: rgb(var(--surface-1)); + background-color: var(--surface); } .input-error { - border-color: rgb(var(--semantic-error)); + border-color: var(--danger); } .input-error:focus { - border-color: rgb(var(--semantic-error)); - box-shadow: 0 0 0 3px rgb(var(--semantic-error) / 0.1); + border-color: var(--danger); + box-shadow: 0 0 0 3px rgba(229, 72, 77, 0.12); } } @@ -383,8 +402,8 @@ body { @layer components { .card { @apply rounded-lg p-4; - background-color: rgb(var(--surface-0)); - border: 1px solid rgb(var(--border-default)); + background-color: var(--surface); + border: 1px solid var(--border); box-shadow: var(--shadow-sm); } @@ -398,7 +417,7 @@ body { } .card-interactive:hover { - border-color: rgb(var(--border-strong)); + border-color: var(--muted); box-shadow: var(--shadow-md); } } @@ -412,33 +431,33 @@ body { } .badge-success { - background-color: rgb(var(--semantic-success-light)); - color: rgb(var(--semantic-success-dark)); + background-color: rgba(20, 184, 166, 0.15); + color: var(--ms-teal-400); } .badge-warning { - background-color: rgb(var(--semantic-warning-light)); - color: rgb(var(--semantic-warning-dark)); + background-color: rgba(245, 158, 11, 0.15); + color: var(--ms-amber-400); } .badge-error { - background-color: rgb(var(--semantic-error-light)); - color: rgb(var(--semantic-error-dark)); + background-color: rgba(229, 72, 77, 0.15); + color: var(--ms-red-400); } .badge-info { - background-color: rgb(var(--semantic-info-light)); - color: rgb(var(--semantic-info-dark)); + background-color: rgba(47, 128, 255, 0.15); + color: var(--ms-blue-400); } .badge-neutral { - background-color: rgb(var(--surface-2)); - color: rgb(var(--text-secondary)); + background-color: var(--surface-2); + color: var(--text-2); } .badge-primary { - background-color: rgb(var(--accent-primary-light)); - color: rgb(var(--accent-primary)); + background-color: rgba(47, 128, 255, 0.15); + color: var(--primary-l); } } @@ -451,26 +470,29 @@ body { } .status-dot-success { - background-color: rgb(var(--semantic-success)); + background-color: var(--success); + box-shadow: 0 0 5px var(--success); } .status-dot-warning { - background-color: rgb(var(--semantic-warning)); + background-color: var(--warn); + box-shadow: 0 0 5px var(--warn); } .status-dot-error { - background-color: rgb(var(--semantic-error)); + background-color: var(--danger); + box-shadow: 0 0 5px var(--danger); } .status-dot-info { - background-color: rgb(var(--semantic-info)); + background-color: var(--primary); + box-shadow: 0 0 5px var(--primary); } .status-dot-neutral { - background-color: rgb(var(--text-muted)); + background-color: var(--muted); } - /* Pulsing indicator for live/active status */ .status-dot-pulse { @apply relative; } @@ -489,12 +511,12 @@ body { @layer components { .kbd { @apply inline-flex items-center justify-center rounded px-1.5 py-0.5 text-xs font-medium; - background-color: rgb(var(--surface-2)); - border: 1px solid rgb(var(--border-default)); - color: rgb(var(--text-tertiary)); - font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + background-color: var(--surface-2); + border: 1px solid var(--border); + color: var(--muted); + font-family: var(--mono); min-width: 1.5rem; - box-shadow: 0 1px 0 rgb(var(--border-strong)); + box-shadow: 0 1px 0 var(--border); } .kbd-group { @@ -512,13 +534,13 @@ body { .table-pro thead { @apply sticky top-0; - background-color: rgb(var(--surface-1)); - border-bottom: 1px solid rgb(var(--border-default)); + background-color: var(--surface); + border-bottom: 1px solid var(--border); } .table-pro th { @apply px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider; - color: rgb(var(--text-tertiary)); + color: var(--muted); } .table-pro th.sortable { @@ -526,16 +548,16 @@ body { } .table-pro th.sortable:hover { - color: rgb(var(--text-primary)); + color: var(--text); } .table-pro tbody tr { - border-bottom: 1px solid rgb(var(--border-subtle)); + border-bottom: 1px solid var(--border); transition: background-color 0.1s ease; } .table-pro tbody tr:hover { - background-color: rgb(var(--surface-1)); + background-color: var(--surface); } .table-pro td { @@ -555,9 +577,9 @@ body { @apply animate-pulse rounded; background: linear-gradient( 90deg, - rgb(var(--surface-2)) 0%, - rgb(var(--surface-1)) 50%, - rgb(var(--surface-2)) 100% + var(--surface) 0%, + var(--surface-2) 50%, + var(--surface) 100% ); background-size: 200% 100%; } @@ -590,15 +612,16 @@ body { } .modal-content { - @apply relative max-h-[90vh] w-full max-w-lg overflow-y-auto rounded-lg; - background-color: rgb(var(--surface-0)); - border: 1px solid rgb(var(--border-default)); + @apply relative max-h-[90vh] w-full max-w-lg overflow-y-auto; + background-color: var(--surface); + border: 1px solid var(--border); + border-radius: var(--r-lg); box-shadow: var(--shadow-lg); } .modal-header { @apply flex items-center justify-between p-4 border-b; - border-color: rgb(var(--border-default)); + border-color: var(--border); } .modal-body { @@ -607,7 +630,7 @@ body { .modal-footer { @apply flex items-center justify-end gap-3 p-4 border-t; - border-color: rgb(var(--border-default)); + border-color: var(--border); } } @@ -617,9 +640,10 @@ body { @layer components { .tooltip { @apply absolute z-50 rounded px-2 py-1 text-xs font-medium; - background-color: rgb(var(--text-primary)); - color: rgb(var(--color-background)); + background-color: var(--text); + color: var(--bg); box-shadow: var(--shadow-md); + border-radius: var(--r-sm); } .tooltip::before { @@ -630,7 +654,7 @@ body { .tooltip-top::before { @apply left-1/2 top-full -translate-x-1/2; - border-top-color: rgb(var(--text-primary)); + border-top-color: var(--text); } } @@ -680,12 +704,10 @@ body { animation: scaleIn 0.15s ease-out; } -/* Message animation - subtle for chat */ .message-animate { animation: slideIn 0.2s ease-out; } -/* Menu dropdown animation */ .animate-menu-enter { animation: scaleIn 0.1s ease-out; } @@ -710,13 +732,8 @@ body { ----------------------------------------------------------------------------- */ @media (prefers-contrast: high) { :root { - --border-default: 100 116 139; - --border-strong: 71 85 105; - } - - .dark { - --border-default: 148 163 184; - --border-strong: 203 213 225; + --border: #4a5a78; + --muted: #a0b0cc; } } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index bd9a6c4..90c96bc 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import type { ReactNode } from "react"; +import { Outfit, Fira_Code } from "next/font/google"; import { AuthProvider } from "@/lib/auth/auth-context"; import { ErrorBoundary } from "@/components/error-boundary"; import { ThemeProvider } from "@/providers/ThemeProvider"; @@ -12,6 +13,18 @@ export const metadata: Metadata = { description: "Mosaic Stack Web Application", }; +const outfit = Outfit({ + subsets: ["latin"], + variable: "--font-outfit", + display: "swap", +}); + +const firaCode = Fira_Code({ + subsets: ["latin"], + variable: "--font-fira-code", + display: "swap", +}); + /** * Runtime env vars injected as a synchronous script so client-side modules * can read them before React hydration. This allows Docker env vars to @@ -34,7 +47,7 @@ function runtimeEnvScript(): string { export default function RootLayout({ children }: { children: ReactNode }): React.JSX.Element { return ( - +