refactor: Design System v4 — flat dark theme with icon rail sidebar #119

Merged
chihlasm merged 37 commits from refactor/design-system-v4 into main 2026-03-23 02:06:24 +00:00
205 changed files with 3749 additions and 1593 deletions

View File

@@ -19,29 +19,29 @@
| Repository / directory / database / Docker | `patherly` / `patherly_postgres` |
| Backend, frontend UI, production URLs | **ResolutionFlow** |
- **Design:** Dark glassmorphism with ice-cyan accent gradient (`#06b6d4``#22d3ee`). Charcoal backgrounds, frosted-glass cards with `backdrop-filter: blur()`, orchestrated page-load animations, bold display typography. Design doc: [docs/plans/2026-03-03-aesthetic-redesign-design.md](docs/plans/2026-03-03-aesthetic-redesign-design.md)
- **Fonts:** Bricolage Grotesque (`font-heading`, headings/titles), IBM Plex Sans (`font-sans`, body text), JetBrains Mono (`font-label`, labels/badges/timestamps) — loaded via Google Fonts
- **Logo:** Inline SVG in `BrandLogo.tsx` (decision-tree icon with cyan gradient). Wordmark: "Resolution" in `text-foreground` + "Flow" in `text-gradient-brand`
- **Brand assets:** `brand-assets/` (source SVGs + brand-guide.html), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon)
- **CSS utilities:** `text-gradient-brand`, `bg-gradient-brand`, `bg-gradient-brand-hover` (defined in `index.css` via `@theme`). Glass utilities: `.glass-card` (interactive, `scale(1.02)` hover), `.glass-card-static` (no hover transform), `.active-glow` (breathing cyan shadow)
- **Layout:** App shell with persistent sidebar + top bar + main content (CSS Grid). Two fixed atmosphere orbs (cyan top-right, purple bottom-left) behind the shell for ambient glow. See [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md)
- **Navigation:** Sidebar nav with type sub-items (All Flows → Troubleshooting / Projects / Maintenance). Pinned flows section for quick access. NO workspace switcher. See [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md)
- **Design system:** [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) — THE source of truth for all design decisions
- **Design aesthetic:** Flat, high-contrast dark theme (Sentry/PostHog-inspired). No glass morphism, no gradients on surfaces, no ambient effects. Light mode planned.
- **Accent color:** Cyan (#22d3ee / #06b6d4). Used sparingly — ≤5% of the UI.
- **Fonts:** IBM Plex Sans (`font-sans`, body), Bricolage Grotesque (`font-heading`, headings), JetBrains Mono (`font-mono`, code) — loaded via Google Fonts
- **Logo:** 30px gradient square (cyan) + "ResolutionFlow" in Bricolage Grotesque 700
- **Layout:** Icon rail sidebar (72px default) with hover flyout panels. Pinnable to full 260px sidebar. See [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md)
- **Brand assets:** `brand-assets/` (source SVGs), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon)
- **Terminology:** User-facing label is "Flows" (not "Trees"). Procedural flows are called "Projects" in the UI. Maintenance flows are called "Maintenance" in the UI. `tree_type` column values unchanged in DB.
- **Rebrand guide:** [REBRAND-IMPLEMENTATION-GUIDE.md](docs/archive/REBRAND-IMPLEMENTATION-GUIDE.md)
- **Reference mockups:** `docs/mockups/` (HTML files, open in browser)
**Component styling rules:**
- Primary buttons: `bg-gradient-brand` (cyan `135deg`) with `shadow-lg shadow-primary/20`, hover `opacity-0.9`, active `scale(0.97)`
- Secondary buttons: `bg-[rgba(255,255,255,0.04)]` with `border-[rgba(255,255,255,0.06)]`, hover brightens border
- Active nav items: `bg-primary/10` background + 3px left cyan gradient accent bar
- Stat values: use `text-gradient-brand` for highlighted metrics
- Status colors: emerald-400 (success), amber-400 (in-progress), rose-500 (error/critical)
- Category dots: 8px colored circles using the category color palette
- Tags/badges: `font-label` (JetBrains Mono), small rounded chips with `bg-card border-border`
- Cards: `.glass-card` (interactive) or `.glass-card-static` (non-interactive) — semi-transparent bg with `backdrop-filter: blur(16px)`, `border-radius: 16px`
- Section labels: `font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground`
- Primary buttons: solid `accent` background (#22d3ee), white text, 5px radius
- Ghost buttons: transparent with 1px `border-default`, hover `bg-elevated`
- Cards: `bg-card` with 1px `border-default`, 8px radius. NO shadows, NO blur, NO gradients.
- Badges: pill-shaped (20px radius), semantic dim background + matching text color
- Active nav: `accent-dim` background + `accent-text` color + 3px left accent bar
- Stat cards: 3px colored left border (accent/success/warning by position)
- Code blocks: `bg-code` with JetBrains Mono, material-inspired syntax highlighting
- Status colors: green/`#34d399` (success), amber/`#fbbf24` (warning), red/`#f87171` (danger) — ONLY for semantic meaning
- Section labels: 10px, 600 weight, uppercase, `text-muted`, 1.2px letter-spacing
When adding new pages/components: use "ResolutionFlow" branding, ice-cyan gradient accent theme, `.glass-card` / `.glass-card-static` containers, `text-foreground`/`text-muted-foreground` hierarchy. Primary actions use `bg-gradient-brand`. Pages render inside the app shell (CSS Grid: topbar + sidebar + main). Use "Flows" not "Trees" in all user-facing text; use "Projects" not "Procedures" for procedural flows. Reference [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md) for layout patterns, navigation, and component specs.
When adding new pages/components: reference [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md). Use flat dark surfaces, 1px borders, no decorative effects. All colors via CSS variables. Use "Flows" not "Trees" in all user-facing text; use "Projects" not "Procedures" for procedural flows.
## Implementation Principles
@@ -97,7 +97,7 @@ When adding new pages/components: use "ResolutionFlow" branding, ice-cyan gradie
### Frontend
- **Framework:** React 19 + Vite + TypeScript
- **Styling:** Tailwind CSS v4 (`@tailwindcss/vite` plugin, CSS-only config in `index.css`) — dark-first with ice-cyan gradient accents (see Branding section)
- **Styling:** Tailwind CSS v4 (`@tailwindcss/vite` plugin, CSS-only config in `index.css`) — flat dark theme with cyan accent (see [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md))
- **State:** Zustand (with immer + zundo for undo/redo)
- **Routing:** React Router v7
- **API Client:** Axios with token refresh interceptor
@@ -379,20 +379,20 @@ gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusi
---
## Design System (Slate & Ice Modern)
## Design System
- **Theme:** Dark glassmorphism with ice-cyan accent (`#06b6d4``#22d3ee`). Uses `.glass-card` / `.glass-card-static` for card surfaces
- **Backgrounds:** `bg-background` (`#101114` page), glass surfaces use `rgba(24, 26, 31, 0.55)` with `backdrop-filter: blur()`
- **Cards:** `.glass-card` (interactive, hover `scale(1.02)` + border/shadow upgrade) or `.glass-card-static` (no hover). Both have `border-radius: 16px`, semi-transparent bg, backdrop blur
- **Buttons:** Primary: `bg-gradient-brand text-[#101114] font-semibold rounded-[10px] hover:opacity-90 active:scale-[0.97]`. Secondary: `bg-[rgba(255,255,255,0.04)] border-[rgba(255,255,255,0.06)] text-foreground rounded-[10px]`
- **Inputs:** `border-border bg-card text-foreground placeholder:text-muted-foreground` + focus: `focus:border-[rgba(6,182,212,0.3)]`
- **Text:** `text-foreground` (`#f8fafc`) → `text-muted-foreground` (`#8891a0`) → `text-[#5a6170]` (dim, for section labels/timestamps)
- **Borders:** `var(--glass-border)` (`rgba(255,255,255,0.06)`) default, `rgba(255,255,255,0.12)` on hover
- **Hover states:** Border brightens to `rgba(255,255,255,0.12)`, shadow upgrades to `--shadow-float-hover`
- **Active/selected:** `bg-primary/10 text-foreground` or cyan gradient accent bar
- **Functional colors:** emerald-400 (success), rose-500 (error), amber-400 (warning), blue-400 (info). Always pair with icons, not color alone.
- **CSS variables:** Glass system vars (`--glass-bg`, `--glass-border`, `--glass-blur`), shadow system (`--shadow-float`, `--shadow-float-hover`, `--shadow-cyan-glow`), easing (`--ease-out-smooth`) — all in `index.css` `:root`
- **Animations:** Orchestrated page-load sequence (slideDown, slideInLeft, fadeInUp cascade, fadeInRight). `breatheGlow` on first stat card. `bellWobble` on notification hover. See design doc for full spec.
**Source of truth:** [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) — always read this before making visual or UI decisions.
- **Theme:** Flat, high-contrast dark theme (Sentry/PostHog-inspired). No glass morphism, no backdrop blur, no ambient orbs, no gradient backgrounds on surfaces. Light mode planned.
- **Backgrounds:** `bg-page` (`#0c0d10`), `bg-sidebar` (`#0f1118`), `bg-card` (`#14161d`), `bg-elevated` (`#1c1f2a`)
- **Cards:** `bg-card` with 1px `border-default` (`#1e2130`), 8px radius. No shadows, no blur, no gradients. Hover: `border-hover` (`#2a2f3d`)
- **Buttons:** Primary: solid `accent` (#22d3ee), white text, 5px radius. Ghost: transparent + 1px border, hover `bg-elevated`
- **Inputs:** `bg-input` (`#191c25`) with 1px `border-default`, 5px radius. Focus: `border-color: accent` + `box-shadow: 0 0 0 2px accent-dim`
- **Text:** `text-heading` (`#f0f2f5`) → `text-primary` (`#e2e5eb`) → `text-secondary` (`#848b9b`) → `text-muted` (`#4f5666`)
- **Borders:** `border-default` (`#1e2130`), `border-hover` (`#2a2f3d`)
- **Functional colors:** `#34d399` (success), `#fbbf24` (warning), `#f87171` (danger) — each with `-dim` variant at 10% opacity
- **Accent:** Cyan `#22d3ee` — used sparingly (≤5% of UI). `accent-dim` = `rgba(34,211,238,0.10)`, `accent-text` = `#67e8f9`
- **Deprecated:** Do NOT use `glass-card`, `glass-stat`, `bg-gradient-brand`, `text-gradient-brand`, `backdrop-filter: blur()`, ambient orbs, or purple gradients
---
@@ -518,5 +518,5 @@ Use `/browse` from gstack for **all web browsing** — never use `mcp__claude-in
| GitHub Issues | `gh issue list --state open` |
| Bugs & Fixes | CLAUDE.md → Critical Lessons Learned section |
| Feature Specs | [04-FEATURE-SPECIFICATIONS.md](04-FEATURE-SPECIFICATIONS.md) |
| Design System | [docs/plans/Frontend/DESIGN_SYSTEM_GUIDE.md](docs/plans/Frontend/DESIGN_SYSTEM_GUIDE.md) |
| Design System | [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) |
| Dev Environment | [DEV-ENV.md](DEV-ENV.md) — devserver01 setup, Docker, CORS, networking |

404
DESIGN-SYSTEM.md Normal file
View File

@@ -0,0 +1,404 @@
# ResolutionFlow Design System v4
> **Status:** ACTIVE — This document is the single source of truth for all frontend design decisions.
> **Supersedes:** All previous design system docs including `DESIGN_SYSTEM_GUIDE.md`, `UI-DESIGN-SYSTEM.md`, `REBRAND-IMPLEMENTATION-GUIDE.md`, and any `COMPONENT_EXAMPLES.md` files.
> **Last Updated:** March 21, 2026
---
## Design Philosophy
ResolutionFlow uses a **flat, high-contrast dark theme** inspired by Sentry and PostHog. The aesthetic is premium, clean, and minimal — no glass morphism, no backdrop blur, no ambient orbs, no gradient backgrounds on surfaces. The accent color appears in ≤5% of the UI. Every design decision prioritizes **readability over decoration**.
**Light mode** is a planned addition (dark/light toggle). Design all components with CSS variables so theming is a variable swap, not a rewrite.
---
## Color System
All colors are defined as CSS custom properties in `index.css` inside the `@theme` block (Tailwind v4) or `:root` / `.dark` blocks (Tailwind v3).
### Dark Mode (Default)
```
Page background: #0c0d10
Sidebar background: #0f1118
Card background: #14161d
Card hover: #191c25
Input background: #191c25
Code background: #0e1017
Elevated surface: #1c1f2a
Text primary: #e2e5eb
Text heading: #f0f2f5
Text secondary: #848b9b
Text muted: #4f5666
Text rail label: #6b7280
Border default: #1e2130
Border hover: #2a2f3d
Accent (cyan): #22d3ee
Accent hover: #06b6d4
Accent dim (8-10%): rgba(34,211,238,0.10)
Accent text: #67e8f9
Success: #34d399
Success dim: rgba(52,211,153,0.10)
Warning: #fbbf24
Warning dim: rgba(251,191,36,0.10)
Danger: #f87171
Danger dim: rgba(248,113,113,0.10)
```
### Light Mode (Planned)
```
Page background: #f3f4f7
Sidebar background: #ffffff
Card background: #ffffff
Card hover: #f8f9fb
Input background: #eef0f4
Code background: #f5f6f9
Elevated surface: #e8eaef
Text primary: #1a1d24
Text heading: #0d0f13
Text secondary: #5a6274
Text muted: #8b92a1
Border default: #dde0e7
Border hover: #c5c9d3
Accent: #0891b2
Accent dim: rgba(8,145,178,0.07)
Accent text: #0e7490
```
### What NOT To Use
- No glass morphism (`backdrop-filter: blur`)
- No gradient backgrounds on cards or surfaces
- No ambient orbs or floating glow elements
- No `bg-white/[0.04]` opacity-based backgrounds
- No purple gradient accent (`#818cf8 → #a78bfa`) — this is deprecated
- No `text-gradient-brand` utility — replaced by solid `accent-text` color
- No `glass-card`, `glass-stat`, `glass-card-glow` CSS utilities
---
## Typography
### Font Stack
| Role | Font | Weights | CSS Variable |
|------|------|---------|-------------|
| Body text | IBM Plex Sans | 400, 500, 600, 700 | `--font-sans` / `font-sans` |
| Headings | Bricolage Grotesque | 600, 700, 800 | `--font-heading` / `font-heading` |
| Code / Monospace | JetBrains Mono | 400, 500 | `--font-mono` / `font-mono` |
### Typography Scale
| Element | Size | Weight | Color | Letter Spacing |
|---------|------|--------|-------|---------------|
| Page title (h1) | 22px | 700 (Bricolage) | text-heading | -0.03em |
| Section title | 15px | 600 (Bricolage) | text-heading | normal |
| Card title | 14px | 600 (IBM Plex) | text-heading | normal |
| Body text | 14px | 400 (IBM Plex) | text-primary | normal |
| Secondary text | 13px | 400 (IBM Plex) | text-secondary | normal |
| Label / hint | 12px | 500 (IBM Plex) | text-muted | normal |
| Nav section header | 10px | 600 (IBM Plex) | text-muted | 1.2px, uppercase |
| Rail icon label | 10px | 500 (IBM Plex) | text-rail-label | normal |
| Code | 12px | 400 (JetBrains) | text-primary | normal |
| Stat number | 28px | 700 (Bricolage) | text-heading | -0.03em |
| Badge | 11px | 550 (IBM Plex) | varies | normal |
### What NOT To Use
- No Inter, Plus Jakarta Sans, or Outfit — these are deprecated
- No `font-label` utility — use `font-mono` for monospace or `font-sans` at small size
- No font sizes below 10px
---
## Layout Architecture
### App Shell
The app uses a CSS Grid layout with two states:
**Icon Rail (Default):**
```
[icon-rail: 72px] [main-content: 1fr]
```
**Pinned Sidebar:**
```
[full-sidebar: 260px] [main-content: 1fr]
```
### Icon Rail Sidebar
The default navigation is a narrow icon rail (72px) with:
- Logo mark at top (30px square, gradient background, lightning bolt icon)
- Nav items as vertical column: icon (20px) + label text (10px) underneath
- Dividers between nav sections
- User avatar at bottom
- Pin/expand button below avatar
**Hover behavior:** Hovering a rail item opens a flyout panel (220px) to the right of the rail with sub-navigation items for that section. The flyout stays open while the cursor is on either the rail item or the flyout panel.
**Pinned state:** Clicking the pin button expands to a traditional 260px sidebar with full text labels, section headers, badges, and the active left-bar accent. An unpin button in the header returns to the icon rail.
### Active Nav Indicator
- **Icon rail:** `accent-dim` background on the item, `accent-text` color on icon and label
- **Pinned sidebar:** Same as above, plus a 3px left border (`border-radius: 0 3px 3px 0`) in `accent` color
### Main Content Area
- Padding: 28px top/bottom, 36px left/right
- Max content width: not constrained (fills available space)
- Page title: Bricolage Grotesque, 22px, weight 700
---
## Component Patterns
### Stat Cards
```
Background: bg-card
Border: 1px solid border-default
Border-left: 3px solid [varies by position - accent, success, warning, accent]
Border-radius: 8px
Padding: 18px 16px
```
- Label: 11px, uppercase, 600 weight, text-muted
- Value: 28px, Bricolage Grotesque 700, text-heading
- Delta: 12px, 500 weight, success/danger color with ↑/↓ prefix
### Cards
```
Background: bg-card
Border: 1px solid border-default
Border-radius: 8px
Padding: 20px
Hover: border-color transitions to border-hover
```
No shadows. No gradients. No glow effects.
### Flow List Items
```
Layout: flex row, 12px gap
Padding: 10px 8px
Border-bottom: 1px solid border-default (divider style, not bordered cards)
Hover: bg-card-hover
```
- Icon: 34px square, border-radius 5px, semantic dim background + colored stroke
- Name: 13px, 500 weight, text-heading
- Meta: 11.5px, text-muted
- Badge: right-aligned status badge
### Badges
```
Font: 11px, 550 weight
Padding: 3px 9px
Border-radius: 20px (pill)
```
| Type | Background | Text Color |
|------|-----------|------------|
| Info/Accent | accent-dim | accent-text |
| Success | success-dim | success |
| Warning | warning-dim | warning |
| Danger | danger-dim | danger |
### Form Inputs
```
Background: bg-input
Border: 1px solid border-default
Border-radius: 5px
Padding: 9px 12px
Font: 13px, IBM Plex Sans
Focus: border-color accent, box-shadow 0 0 0 2px accent-dim
Placeholder: text-muted
```
### Buttons
**Primary:**
```
Background: accent (#22d3ee)
Color: #fff
Border: none
Border-radius: 5px
Padding: 9px 16px
Font: 13px, 550 weight
Hover: brightness(1.1)
```
**Ghost:**
```
Background: transparent
Color: text-secondary
Border: 1px solid border-default
Hover: bg-elevated, text-primary, border-hover
```
### Code Blocks
```
Background: bg-code (#0e1017)
Border: 1px solid border-default
Border-radius: 8px
Padding: 18px 20px
Font: JetBrains Mono, 12px, line-height 1.7
```
**Syntax colors (dark mode):**
| Token | Color |
|-------|-------|
| Comment | #4a5568 |
| Keyword | #c792ea |
| Function/Cmdlet | #82aaff |
| String | #c3e88d |
| Variable | #89ddff |
| Parameter | #8c93a4 |
| Number | #f78c6c |
### Chips / Tags
```
Font: 11.5px, 500 weight
Padding: 4px 11px
Border-radius: 20px
Background: accent-dim
Color: accent-text
```
---
## Logo
- **Mark:** 30-32px square, border-radius 8px, `linear-gradient(135deg, #06b6d4, #22d3ee)`, white lightning bolt SVG
- **Wordmark:** "ResolutionFlow" in Bricolage Grotesque, 16-17px, weight 700, text-heading color
- **Combined:** Mark + wordmark horizontally, 10px gap
---
## Spacing System
- Component internal gaps: 6px, 8px, 12px, 14px, 16px
- Card padding: 20px
- Section gaps: 24px between major sections
- Page padding: 28px vertical, 36px horizontal
- Stat grid gap: 12px
- Two-column gap: 16px
- Nav item padding: 8px 10px (pinned), 8px 4px (rail)
---
## Border Radius
| Token | Value | Use |
|-------|-------|-----|
| radius-sm | 5px | Inputs, buttons, small elements |
| radius | 8px | Cards, stat cards, code blocks |
| radius-lg | 12px | Large cards, modals |
| radius-xl | 16px | Hero elements, CTA boxes |
| radius-pill | 100px | Badges, chips, toggle tracks |
---
## Shadows
Shadows communicate **interaction state**, not decoration. On dark backgrounds, traditional black shadows are invisible — use **brighter surfaces + faint accent glow** instead.
**Resting state:** No shadows. Elements are flat with 1px borders.
**Elevation on dark backgrounds (the principle):** Instead of shadow = darker, elevation = lighter. A "raised" element gets a brighter surface color (`bg-elevated` / `#1c1f2a`) and optionally a very faint cyan glow. This creates perceived depth through contrast.
**Hover state (buttons):** Lift effect with accent glow.
- Primary button hover: `0 2px 10px rgba(34,211,238,0.2)` + `translateY(-1px)` — cyan glow
- Ghost button hover: brighter border (`border-hover`) + `translateY(-1px)`, no shadow
- Active/click: glow fades, element "presses down" to `translateY(0)`
**Active/selected state (tabs, toggles):** Elevated surface + faint accent glow.
- Active tab: `bg-elevated` + `box-shadow: 0 1px 4px rgba(34,211,238,0.08)` — class: `tab-active-shadow`
**Card hover lift (optional):** For clickable cards.
- Hover: brighter border + `0 2px 8px rgba(34,211,238,0.06)` + `translateY(-2px)` — class: `card-lift`
**Overlays:** Flyouts, dropdowns, modals get stronger shadows (they overlay lighter content).
- Drawer/flyout: `4px 0 12px rgba(0,0,0,0.2)`
- Dropdown: `0 4px 12px rgba(0,0,0,0.3)`
- Modal: `0 8px 24px rgba(0,0,0,0.4)`
**What NOT to do:**
- No `rgba(0,0,0,...)` shadows on resting elements (invisible on dark bg)
- No permanent decorative shadows
- No heavy glow effects — accent glow should be barely perceptible (≤ 0.1 opacity)
---
## Icons
All icons use Lucide React (`lucide-react` package).
- Default size: 16-18px
- Stroke width: 1.6-1.8
- Color: `currentColor` (inherits from parent text color)
- Nav icons: 20px at stroke-width 1.6
- Rail icon opacity: 0.6 default, 0.85 on hover, 1.0 when active
---
## Landing Page
The marketing landing page at `/` shares the same color system and typography but has its own layout (no sidebar, full-width sections). Key differences:
- Navigation: fixed top bar with logo, links, and CTAs
- Hero: centered layout with glow effect (radial gradient, accent at 15% opacity)
- Sections: 100px vertical padding, max-width 1120px container
- Section labels: JetBrains Mono, 12px, uppercase, accent-text color
- Section titles: Bricolage Grotesque, clamp(28px, 4vw, 42px), weight 800
- Pricing cards: same card pattern but with featured state (accent border + subtle glow)
- Product mockup: embedded in hero, shows the actual app UI with browser chrome
---
## Files That Define The Design System
These are the source-of-truth files in the codebase:
| File | What It Controls |
|------|-----------------|
| `frontend/src/index.css` | CSS variables, `@theme` block, base styles |
| `frontend/tailwind.config.js` (v3) or inline in index.css (v4) | Color tokens, font families, spacing extensions |
| `frontend/index.html` | Google Font imports |
| `DESIGN-SYSTEM.md` (this file) | Design decisions, component specs, rules |
---
## Deprecated Files — DO NOT Reference
These files contain outdated design information and should be ignored:
- `DESIGN_SYSTEM_GUIDE.md` — Old monochrome glass-morphism system
- `UI-DESIGN-SYSTEM.md` — Old workspace/purple gradient system
- `REBRAND-IMPLEMENTATION-GUIDE.md` — Old purple rebrand from Patherly
- `COMPONENT_EXAMPLES.md` — Old monochrome component patterns
- Any file referencing `glass-card`, `glass-stat`, `bg-gradient-brand`, or `text-gradient-brand`

View File

@@ -0,0 +1,128 @@
# How To Make Claude Code Follow The New Design System
## The Problem
Your project currently has MULTIPLE contradicting design documents:
- CLAUDE.md references purple gradients, glass-morphism, AND cyan accents
- Old docs like DESIGN_SYSTEM_GUIDE.md describe monochrome glass-card patterns
- UI-DESIGN-SYSTEM.md describes a purple gradient workspace system
- REBRAND-IMPLEMENTATION-GUIDE.md has the old Patherly → ResolutionFlow rebrand
- Your actual CSS has Tailwind v4 with OKLCH cyan tokens
Claude Code sees ALL of these and gets confused about which to follow.
## The Fix — 4 Steps
### Step 1: Place the new DESIGN-SYSTEM.md in your project root
Copy `DESIGN-SYSTEM.md` (the file I created alongside this one) to:
```
C:\Dev\Projects\patherly\DESIGN-SYSTEM.md
```
This is the SINGLE SOURCE OF TRUTH for all design decisions going forward.
---
### Step 2: Update CLAUDE.md — Branding Section
Find the `### Branding` section and REPLACE EVERYTHING between `**Brand details:**`
and the next `---` separator with this:
```markdown
**Brand details:**
- **Design system:** [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) — THE source of truth for all design decisions
- **Design aesthetic:** Flat, high-contrast dark theme (Sentry/PostHog-inspired). No glass morphism, no gradients on surfaces, no ambient effects.
- **Accent color:** Cyan (#22d3ee / #06b6d4). Used sparingly — ≤5% of the UI.
- **Fonts:** IBM Plex Sans (body), Bricolage Grotesque (headings), JetBrains Mono (code) — loaded via Google Fonts
- **Logo:** 30px gradient square (cyan) + "ResolutionFlow" in Bricolage Grotesque 700
- **Layout:** Icon rail sidebar (72px, default) with hover flyout panels. Pinnable to full 260px sidebar.
- **Brand assets:** `brand-assets/` (source SVGs), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon)
- **Landing page:** Shares color system and typography. Full-width marketing layout at `/` route.
- **Reference mockups:** `docs/mockups/` (HTML files, open in browser)
**Component rules:**
- Primary buttons: solid `accent` background (#22d3ee), white text
- Cards: `bg-card` with 1px `border-default`, 8px radius. NO shadows, NO blur, NO gradients.
- Badges: pill-shaped (20px radius), semantic dim background + matching text color
- Active nav: `accent-dim` background + `accent-text` color + 3px left accent bar
- Stat cards: 3px colored left border (accent/success/warning by position)
- Code blocks: `bg-code` with JetBrains Mono, material-inspired syntax highlighting
- Status colors: green (success), amber (warning), red (danger) — ONLY for semantic meaning
When adding new pages/components: reference DESIGN-SYSTEM.md. Use flat dark surfaces, 1px borders, no decorative effects. All colors via CSS variables.
```
---
### Step 3: Update CLAUDE.md — Tech Stack Line
Find the styling line under Tech Stack → Frontend and change it to:
```markdown
- **Styling:** Tailwind CSS v4 — flat dark theme with cyan accent (see DESIGN-SYSTEM.md)
```
Remove any references to "monochrome glass-morphism" or "dark-only" limitations.
---
### Step 4: Archive Old Design Docs
Move these files into an `archive/` folder (don't delete — you might want to reference them later):
```
mkdir archive
mkdir archive\design-docs
move DESIGN_SYSTEM_GUIDE.md archive\design-docs\
move UI-DESIGN-SYSTEM.md archive\design-docs\
move REBRAND-IMPLEMENTATION-GUIDE.md archive\design-docs\
move COMPONENT_EXAMPLES.md archive\design-docs\ (if it exists in root)
move docs\plans\Frontend\DESIGN_SYSTEM_GUIDE.md archive\design-docs\ (if it exists here)
```
If any of these files are referenced elsewhere in CLAUDE.md, remove those references.
---
## Why This Works
Claude Code prioritizes files in this order:
1. **CLAUDE.md** — reads this first, every time
2. **Files referenced in CLAUDE.md** — follows links to read referenced docs
3. **Files in the project root** — scans these for context
4. **Files deeper in the tree** — only reads if specifically asked
By putting DESIGN-SYSTEM.md in the root AND linking it from CLAUDE.md's branding section,
Claude Code will find it immediately and treat it as authoritative. By archiving the old
docs, Claude Code won't stumble on contradicting information.
---
## Verification
After making these changes, test by asking Claude Code:
1. "What color system does this project use?" — Should say cyan/teal flat dark theme, NOT purple gradients or monochrome
2. "Create a new card component" — Should use bg-card with 1px border, NOT glass-card with backdrop-blur
3. "What fonts does this project use?" — Should say IBM Plex Sans + Bricolage Grotesque, NOT Inter + Plus Jakarta Sans
4. "Add a sidebar nav item" — Should follow the icon rail pattern, NOT a traditional full sidebar
If any of these come back wrong, check that the old docs are actually archived and not still being found.
---
## Place These Mockup Files in docs/mockups/
Also copy the HTML mockups we created into your project so Claude Code has visual references:
```
mkdir docs\mockups
copy resolutionflow-redesign.html docs\mockups\
copy resolutionflow-sidebar-final.html docs\mockups\
copy resolutionflow-landing.html docs\mockups\
```
Then Claude Code can open these in a browser to see exactly what the target looks like.

378
Design/DESIGN-SYSTEM.md Normal file
View File

@@ -0,0 +1,378 @@
# ResolutionFlow Design System v4
> **Status:** ACTIVE — This document is the single source of truth for all frontend design decisions.
> **Supersedes:** All previous design system docs including `DESIGN_SYSTEM_GUIDE.md`, `UI-DESIGN-SYSTEM.md`, `REBRAND-IMPLEMENTATION-GUIDE.md`, and any `COMPONENT_EXAMPLES.md` files.
> **Last Updated:** March 21, 2026
---
## Design Philosophy
ResolutionFlow uses a **flat, high-contrast dark theme** inspired by Sentry and PostHog. The aesthetic is premium, clean, and minimal — no glass morphism, no backdrop blur, no ambient orbs, no gradient backgrounds on surfaces. The accent color appears in ≤5% of the UI. Every design decision prioritizes **readability over decoration**.
**Light mode** is a planned addition (dark/light toggle). Design all components with CSS variables so theming is a variable swap, not a rewrite.
---
## Color System
All colors are defined as CSS custom properties in `index.css` inside the `@theme` block (Tailwind v4) or `:root` / `.dark` blocks (Tailwind v3).
### Dark Mode (Default)
```
Page background: #0c0d10
Sidebar background: #0f1118
Card background: #14161d
Card hover: #191c25
Input background: #191c25
Code background: #0e1017
Elevated surface: #1c1f2a
Text primary: #e2e5eb
Text heading: #f0f2f5
Text secondary: #848b9b
Text muted: #4f5666
Text rail label: #6b7280
Border default: #1e2130
Border hover: #2a2f3d
Accent (cyan): #22d3ee
Accent hover: #06b6d4
Accent dim (8-10%): rgba(34,211,238,0.10)
Accent text: #67e8f9
Success: #34d399
Success dim: rgba(52,211,153,0.10)
Warning: #fbbf24
Warning dim: rgba(251,191,36,0.10)
Danger: #f87171
Danger dim: rgba(248,113,113,0.10)
```
### Light Mode (Planned)
```
Page background: #f3f4f7
Sidebar background: #ffffff
Card background: #ffffff
Card hover: #f8f9fb
Input background: #eef0f4
Code background: #f5f6f9
Elevated surface: #e8eaef
Text primary: #1a1d24
Text heading: #0d0f13
Text secondary: #5a6274
Text muted: #8b92a1
Border default: #dde0e7
Border hover: #c5c9d3
Accent: #0891b2
Accent dim: rgba(8,145,178,0.07)
Accent text: #0e7490
```
### What NOT To Use
- No glass morphism (`backdrop-filter: blur`)
- No gradient backgrounds on cards or surfaces
- No ambient orbs or floating glow elements
- No `bg-white/[0.04]` opacity-based backgrounds
- No purple gradient accent (`#818cf8 → #a78bfa`) — this is deprecated
- No `text-gradient-brand` utility — replaced by solid `accent-text` color
- No `glass-card`, `glass-stat`, `glass-card-glow` CSS utilities
---
## Typography
### Font Stack
| Role | Font | Weights | CSS Variable |
|------|------|---------|-------------|
| Body text | IBM Plex Sans | 400, 500, 600, 700 | `--font-sans` / `font-sans` |
| Headings | Bricolage Grotesque | 600, 700, 800 | `--font-heading` / `font-heading` |
| Code / Monospace | JetBrains Mono | 400, 500 | `--font-mono` / `font-mono` |
### Typography Scale
| Element | Size | Weight | Color | Letter Spacing |
|---------|------|--------|-------|---------------|
| Page title (h1) | 22px | 700 (Bricolage) | text-heading | -0.03em |
| Section title | 15px | 600 (Bricolage) | text-heading | normal |
| Card title | 14px | 600 (IBM Plex) | text-heading | normal |
| Body text | 14px | 400 (IBM Plex) | text-primary | normal |
| Secondary text | 13px | 400 (IBM Plex) | text-secondary | normal |
| Label / hint | 12px | 500 (IBM Plex) | text-muted | normal |
| Nav section header | 10px | 600 (IBM Plex) | text-muted | 1.2px, uppercase |
| Rail icon label | 10px | 500 (IBM Plex) | text-rail-label | normal |
| Code | 12px | 400 (JetBrains) | text-primary | normal |
| Stat number | 28px | 700 (Bricolage) | text-heading | -0.03em |
| Badge | 11px | 550 (IBM Plex) | varies | normal |
### What NOT To Use
- No Inter, Plus Jakarta Sans, or Outfit — these are deprecated
- No `font-label` utility — use `font-mono` for monospace or `font-sans` at small size
- No font sizes below 10px
---
## Layout Architecture
### App Shell
The app uses a CSS Grid layout with two states:
**Icon Rail (Default):**
```
[icon-rail: 72px] [main-content: 1fr]
```
**Pinned Sidebar:**
```
[full-sidebar: 260px] [main-content: 1fr]
```
### Icon Rail Sidebar
The default navigation is a narrow icon rail (72px) with:
- Logo mark at top (30px square, gradient background, lightning bolt icon)
- Nav items as vertical column: icon (20px) + label text (10px) underneath
- Dividers between nav sections
- User avatar at bottom
- Pin/expand button below avatar
**Hover behavior:** Hovering a rail item opens a flyout panel (220px) to the right of the rail with sub-navigation items for that section. The flyout stays open while the cursor is on either the rail item or the flyout panel.
**Pinned state:** Clicking the pin button expands to a traditional 260px sidebar with full text labels, section headers, badges, and the active left-bar accent. An unpin button in the header returns to the icon rail.
### Active Nav Indicator
- **Icon rail:** `accent-dim` background on the item, `accent-text` color on icon and label
- **Pinned sidebar:** Same as above, plus a 3px left border (`border-radius: 0 3px 3px 0`) in `accent` color
### Main Content Area
- Padding: 28px top/bottom, 36px left/right
- Max content width: not constrained (fills available space)
- Page title: Bricolage Grotesque, 22px, weight 700
---
## Component Patterns
### Stat Cards
```
Background: bg-card
Border: 1px solid border-default
Border-left: 3px solid [varies by position - accent, success, warning, accent]
Border-radius: 8px
Padding: 18px 16px
```
- Label: 11px, uppercase, 600 weight, text-muted
- Value: 28px, Bricolage Grotesque 700, text-heading
- Delta: 12px, 500 weight, success/danger color with ↑/↓ prefix
### Cards
```
Background: bg-card
Border: 1px solid border-default
Border-radius: 8px
Padding: 20px
Hover: border-color transitions to border-hover
```
No shadows. No gradients. No glow effects.
### Flow List Items
```
Layout: flex row, 12px gap
Padding: 10px 8px
Border-bottom: 1px solid border-default (divider style, not bordered cards)
Hover: bg-card-hover
```
- Icon: 34px square, border-radius 5px, semantic dim background + colored stroke
- Name: 13px, 500 weight, text-heading
- Meta: 11.5px, text-muted
- Badge: right-aligned status badge
### Badges
```
Font: 11px, 550 weight
Padding: 3px 9px
Border-radius: 20px (pill)
```
| Type | Background | Text Color |
|------|-----------|------------|
| Info/Accent | accent-dim | accent-text |
| Success | success-dim | success |
| Warning | warning-dim | warning |
| Danger | danger-dim | danger |
### Form Inputs
```
Background: bg-input
Border: 1px solid border-default
Border-radius: 5px
Padding: 9px 12px
Font: 13px, IBM Plex Sans
Focus: border-color accent, box-shadow 0 0 0 2px accent-dim
Placeholder: text-muted
```
### Buttons
**Primary:**
```
Background: accent (#22d3ee)
Color: #fff
Border: none
Border-radius: 5px
Padding: 9px 16px
Font: 13px, 550 weight
Hover: brightness(1.1)
```
**Ghost:**
```
Background: transparent
Color: text-secondary
Border: 1px solid border-default
Hover: bg-elevated, text-primary, border-hover
```
### Code Blocks
```
Background: bg-code (#0e1017)
Border: 1px solid border-default
Border-radius: 8px
Padding: 18px 20px
Font: JetBrains Mono, 12px, line-height 1.7
```
**Syntax colors (dark mode):**
| Token | Color |
|-------|-------|
| Comment | #4a5568 |
| Keyword | #c792ea |
| Function/Cmdlet | #82aaff |
| String | #c3e88d |
| Variable | #89ddff |
| Parameter | #8c93a4 |
| Number | #f78c6c |
### Chips / Tags
```
Font: 11.5px, 500 weight
Padding: 4px 11px
Border-radius: 20px
Background: accent-dim
Color: accent-text
```
---
## Logo
- **Mark:** 30-32px square, border-radius 8px, `linear-gradient(135deg, #06b6d4, #22d3ee)`, white lightning bolt SVG
- **Wordmark:** "ResolutionFlow" in Bricolage Grotesque, 16-17px, weight 700, text-heading color
- **Combined:** Mark + wordmark horizontally, 10px gap
---
## Spacing System
- Component internal gaps: 6px, 8px, 12px, 14px, 16px
- Card padding: 20px
- Section gaps: 24px between major sections
- Page padding: 28px vertical, 36px horizontal
- Stat grid gap: 12px
- Two-column gap: 16px
- Nav item padding: 8px 10px (pinned), 8px 4px (rail)
---
## Border Radius
| Token | Value | Use |
|-------|-------|-----|
| radius-sm | 5px | Inputs, buttons, small elements |
| radius | 8px | Cards, stat cards, code blocks |
| radius-lg | 12px | Large cards, modals |
| radius-xl | 16px | Hero elements, CTA boxes |
| radius-pill | 100px | Badges, chips, toggle tracks |
---
## Shadows
Minimal to none. No decorative shadows.
- Cards: none (border only)
- Dropdowns/flyouts: `0 4px 12px rgba(0,0,0,0.3)` if needed
- Logo mark: `0 2px 8px rgba(14,165,233,0.25)` (subtle brand glow on logo only)
---
## Icons
All icons use Lucide React (`lucide-react` package).
- Default size: 16-18px
- Stroke width: 1.6-1.8
- Color: `currentColor` (inherits from parent text color)
- Nav icons: 20px at stroke-width 1.6
- Rail icon opacity: 0.6 default, 0.85 on hover, 1.0 when active
---
## Landing Page
The marketing landing page at `/` shares the same color system and typography but has its own layout (no sidebar, full-width sections). Key differences:
- Navigation: fixed top bar with logo, links, and CTAs
- Hero: centered layout with glow effect (radial gradient, accent at 15% opacity)
- Sections: 100px vertical padding, max-width 1120px container
- Section labels: JetBrains Mono, 12px, uppercase, accent-text color
- Section titles: Bricolage Grotesque, clamp(28px, 4vw, 42px), weight 800
- Pricing cards: same card pattern but with featured state (accent border + subtle glow)
- Product mockup: embedded in hero, shows the actual app UI with browser chrome
---
## Files That Define The Design System
These are the source-of-truth files in the codebase:
| File | What It Controls |
|------|-----------------|
| `frontend/src/index.css` | CSS variables, `@theme` block, base styles |
| `frontend/tailwind.config.js` (v3) or inline in index.css (v4) | Color tokens, font families, spacing extensions |
| `frontend/index.html` | Google Font imports |
| `DESIGN-SYSTEM.md` (this file) | Design decisions, component specs, rules |
---
## Deprecated Files — DO NOT Reference
These files contain outdated design information and should be ignored:
- `DESIGN_SYSTEM_GUIDE.md` — Old monochrome glass-morphism system
- `UI-DESIGN-SYSTEM.md` — Old workspace/purple gradient system
- `REBRAND-IMPLEMENTATION-GUIDE.md` — Old purple rebrand from Patherly
- `COMPONENT_EXAMPLES.md` — Old monochrome component patterns
- Any file referencing `glass-card`, `glass-stat`, `bg-gradient-brand`, or `text-gradient-brand`

70
SESSION-HANDOFF.md Normal file
View File

@@ -0,0 +1,70 @@
# Session Handoff — Design System v4 Migration
> **For the next Claude session:** Read this file completely, internalize the context, then delete it (`rm SESSION-HANDOFF.md`). This is a one-time context transfer.
---
## What Was Done This Session
### 1. FlowPilot Message Bar + AI Script Builder (MERGED to main)
- PR #118 merged. Always-visible message bar in FlowPilot sessions, AI Script Builder at `/script-builder`, library reorg (My/Team Scripts tabs), FlowPilot-to-Script-Builder handoff, session abandon/close, unified session history.
- Eng review completed: normalized `script_builder_messages` table, typed content helpers, 6 edge case tests.
### 2. Design System v4 Migration (PR #119, open, branch: `refactor/design-system-v4`)
- Complete frontend redesign from glassmorphism to flat dark theme (Sentry/PostHog-inspired)
- **CSS Foundation:** New color tokens in `index.css`, all via CSS custom properties. Light mode ready (just needs `.light` class values).
- **Icon Rail Sidebar:** 72px rail with 5 grouped icons (Home, Work, Knowledge, Insights, Help). Full-height resizable drawer on hover. Pin-to-expand to 260px. Mobile hamburger overlay.
- **Component Sweep:** ~200 files migrated. All hardcoded hex replaced with semantic Tailwind tokens (bg-card, text-foreground, border-border, etc.).
- **Landing Page:** Flat surfaces, no glow, solid buttons.
- **Interactive Shadows:** Dark-mode-aware — elevated surfaces + faint cyan accent glow (black shadows invisible on dark bg).
- **Stat Cards:** 3px colored left borders.
- **Tab Toggles:** Active state uses `tab-active-shadow` (elevated bg + faint glow).
### 3. GTM Strategy (from /office-hours)
- Shadow & Ship approach: Michael uses ResolutionFlow on real tickets for 2 weeks, then hands logins to 5 MSP colleagues. Key metric: unprompted return.
- Design doc at `~/.gstack/projects/patherly-patherly/`
---
## What Needs To Be Done Next
### Immediate (Design System v4 polish)
1. **Home icon color fix:** The Home icon in the sidebar shouldn't have a cyan background when not active. Instead, the Home icon itself should always be cyan (brand accent), and only show the `bg-accent-dim` background when the route is actually `/`. Michael specifically requested this.
2. **Visual QA pass:** Michael hasn't done a full page-by-page walkthrough yet. Expect feedback on individual pages once he does.
3. **`font-label` cleanup:** ~10 files still reference `font-label` (deprecated alias for `font-mono`). Each needs inspection — some should be `font-mono`, others `font-sans text-xs`.
4. **Inline `style` attributes:** ~29 instances still use hardcoded hex in inline styles (sidebar, drawer, badges). Should be converted to CSS variable references or Tailwind classes where possible.
### Before Merging PR #119
- Run migrations: `docker exec resolutionflow_backend alembic upgrade head` (new tables from the Script Builder PR are on main now)
- Full visual QA with backend running
- Test mobile responsive (hamburger menu)
- Test FlowPilot session with new message bar + action bar positioning
### Future
- **Light mode toggle:** CSS variables are ready. Need to add `.light` class values in `index.css` + toggle in user settings/account page.
- **Script Builder testing:** The AI Script Builder hasn't been tested end-to-end with the backend running yet.
---
## Key Files to Know
| File | What it does |
|------|-------------|
| `DESIGN-SYSTEM.md` | Single source of truth for all design decisions |
| `frontend/src/index.css` | CSS tokens, component utilities, shadow patterns |
| `frontend/src/components/layout/Sidebar.tsx` | Icon rail + drawer + pinned sidebar |
| `frontend/src/components/layout/AppLayout.tsx` | CSS Grid shell |
| `frontend/src/components/dashboard/StartSessionInput.tsx` | The Guided/Chat toggle |
| `frontend/src/components/dashboard/PerformanceCards.tsx` | Stat cards with colored borders |
## Key Lessons From This Session
- The component sweep agents missed `editor-ai/`, `guides/`, `maintenance/`, `scripts/`, `settings/` directories and `text-brand-dark` references. Always do a final grep audit after sweeps.
- `bg-[#hex]` hardcoding defeats the purpose of CSS variables. We had to do a second pass to replace 3,200+ hardcoded values with semantic tokens.
- Black shadows (`rgba(0,0,0,...)`) are invisible on dark backgrounds. Use elevated surfaces + faint accent glow instead.
- The sidebar flyout needed `position: fixed` to escape the CSS Grid cell clipping — `absolute` positioning was hidden behind the main content area.
- Flyout hover timing: individual item `onMouseLeave` was killing the flyout before the mouse reached the drawer. Only the outer wrapper should handle `onMouseLeave`.
---
> **After reading this file:** Save relevant context to your session memory, then run `rm SESSION-HANDOFF.md` and `git add -A && git commit -m "chore: remove session handoff file"`.

View File

@@ -0,0 +1,665 @@
# Design System v4 Migration — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Migrate ResolutionFlow's frontend from glassmorphism/gradient aesthetic to a flat, high-contrast dark theme with an icon rail sidebar, as defined in DESIGN-SYSTEM.md.
**Architecture:** Five phases executed sequentially. Phase 1 rewrites CSS tokens and adds compatibility shims. Phase 2 rebuilds the sidebar as an icon rail. Phase 3 sweeps ~200 component files replacing old patterns. Phase 4 updates the landing page. Phase 5 removes shims and cleans up.
**Tech Stack:** React 19, Tailwind CSS v4, CSS custom properties, Lucide React
**Spec:** `docs/superpowers/specs/2026-03-22-design-system-v4-migration.md`
**Design system reference:** `DESIGN-SYSTEM.md` (project root)
---
## File Map
### Phase 1 — CSS Foundation
| Action | File |
|--------|------|
| Rewrite | `frontend/src/index.css` |
### Phase 2 — Icon Rail Sidebar
| Action | File |
|--------|------|
| Rewrite | `frontend/src/components/layout/Sidebar.tsx` |
| Modify | `frontend/src/components/layout/AppLayout.tsx` |
| Modify | `frontend/src/components/layout/TopBar.tsx` |
| Modify | `frontend/src/components/common/BrandLogo.tsx` and `BrandWordmark.tsx` |
### Phase 3 — Component Sweep (~200 files)
| Action | Files |
|--------|-------|
| Modify | All `.tsx` files in `components/` and `pages/` that reference old patterns |
### Phase 4 — Landing Page
| Action | File |
|--------|------|
| Modify | `frontend/src/pages/LandingPage.tsx` |
| Modify | `frontend/src/styles/landing.css` |
### Phase 5 — Cleanup
| Action | File |
|--------|------|
| Modify | `frontend/src/index.css` (remove shims) |
| Modify | `CLAUDE.md` (verify no stale references) |
---
## Task 1: Rewrite CSS Foundation (index.css)
**Files:**
- Modify: `frontend/src/index.css`
- Reference: `DESIGN-SYSTEM.md` (project root)
- [ ] **Step 1: Rewrite the `@theme` block with new color tokens**
Replace the entire `@theme { ... }` block. New tokens from DESIGN-SYSTEM.md:
```css
@theme {
/* ── Surface colors ────────────────────────────── */
--color-bg-page: #0c0d10;
--color-bg-sidebar: #0f1118;
--color-bg-card: #14161d;
--color-bg-card-hover: #191c25;
--color-bg-input: #191c25;
--color-bg-code: #0e1017;
--color-bg-elevated: #1c1f2a;
/* ── Text colors ───────────────────────────────── */
--color-text-heading: #f0f2f5;
--color-text-primary: #e2e5eb;
--color-text-secondary: #848b9b;
--color-text-muted: #4f5666;
--color-text-rail-label: #6b7280;
/* ── Border colors ─────────────────────────────── */
--color-border-default: #1e2130;
--color-border-hover: #2a2f3d;
/* ── Accent (cyan) ─────────────────────────────── */
--color-accent: #22d3ee;
--color-accent-hover: #06b6d4;
--color-accent-dim: rgba(34,211,238,0.10);
--color-accent-text: #67e8f9;
/* ── Semantic colors ───────────────────────────── */
--color-success: #34d399;
--color-success-dim: rgba(52,211,153,0.10);
--color-warning: #fbbf24;
--color-warning-dim: rgba(251,191,36,0.10);
--color-danger: #f87171;
--color-danger-dim: rgba(248,113,113,0.10);
/* ── Tailwind semantic mappings ─────────────────── */
--color-background: #0c0d10;
--color-foreground: #e2e5eb;
--color-card: #14161d;
--color-card-foreground: #e2e5eb;
--color-popover: #14161d;
--color-popover-foreground: #e2e5eb;
--color-primary: #22d3ee;
--color-primary-foreground: #ffffff;
--color-secondary: #1c1f2a;
--color-secondary-foreground: #e2e5eb;
--color-muted: #1c1f2a;
--color-muted-foreground: #848b9b;
--color-accent-tw: #1c1f2a;
--color-accent-foreground: #e2e5eb;
--color-destructive: #f87171;
--color-destructive-foreground: #ffffff;
--color-border: #1e2130;
--color-input: #191c25;
--color-ring: #22d3ee;
/* ── Radii ─────────────────────────────────────── */
--radius-sm: 5px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
/* ── Fonts ─────────────────────────────────────── */
--font-sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Deprecated alias — remove in Phase 5 */
--font-label: 'JetBrains Mono', monospace;
/* ── Animations ────────────────────────────────── */
--animate-fade-in: fade-in 200ms ease-out;
--animate-fade-in-up: fade-in-up 200ms ease-out;
--animate-slide-in-left: slide-in-from-left 200ms ease-out;
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out;
--animate-scale-in: scale-in 150ms ease-out;
@keyframes fade-in {
from { opacity: 0; } to { opacity: 1; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-left {
from { transform: translateX(-100%); } to { transform: translateX(0); }
}
@keyframes slide-in-from-bottom {
from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); }
}
}
```
- [ ] **Step 2: Replace `:root` variables**
Replace the `:root` block (lines 168-192) with:
```css
:root {
--sidebar-w: 72px;
--sidebar-w-pinned: 260px;
--ease-out-smooth: cubic-bezier(0.4, 0, 0.2, 1);
}
```
- [ ] **Step 3: Add compatibility shims**
Replace the old `@utility` blocks (glass-card, glass-card-static, active-glow, text-gradient-brand) with shims:
```css
/* ── Deprecated shims — remove after Phase 3 sweep ── */
@utility glass-card {
background: var(--color-bg-card);
border: 1px solid var(--color-border-default);
border-radius: 8px;
transition: border-color 200ms ease;
&:hover {
border-color: var(--color-border-hover);
}
}
@utility glass-card-static {
background: var(--color-bg-card);
border: 1px solid var(--color-border-default);
border-radius: 8px;
}
@utility text-gradient-brand {
color: var(--color-accent-text);
}
@utility active-glow {
/* no-op — glow removed in v4 */
}
@utility bg-gradient-brand-hover {
/* Shim — maps to brightness hover until components are swept */
&:hover { filter: brightness(1.1); }
}
```
Remove the old gradient `--background-image-*` theme values. Remove `breatheGlow`, `bellWobble`, `slideDown`, `slideInRight`, `fadeInRight`, `pulse-dot`, `stagger-fade-in` keyframes. Remove `stagger-item` utility.
- [ ] **Step 4: Add new base component classes**
Add after the shims:
```css
/* ── New component base classes ─────────────────── */
@utility card-flat {
background: var(--color-bg-card);
border: 1px solid var(--color-border-default);
border-radius: 8px;
}
@utility card-interactive {
background: var(--color-bg-card);
border: 1px solid var(--color-border-default);
border-radius: 8px;
transition: border-color 200ms ease;
&:hover {
border-color: var(--color-border-hover);
}
}
@utility btn-primary {
background: var(--color-accent);
color: #ffffff;
font-weight: 550;
border-radius: 5px;
padding: 9px 16px;
font-size: 13px;
transition: filter 150ms ease;
&:hover {
filter: brightness(1.1);
}
}
@utility btn-ghost {
background: transparent;
color: var(--color-text-secondary);
border: 1px solid var(--color-border-default);
border-radius: 5px;
padding: 9px 16px;
font-size: 13px;
transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
&:hover {
background: var(--color-bg-elevated);
color: var(--color-text-primary);
border-color: var(--color-border-hover);
}
}
```
- [ ] **Step 5: Update layout utilities**
Update `.app-shell` grid columns:
```css
.app-shell {
display: grid;
grid-template-columns: var(--sidebar-w) 1fr;
grid-template-rows: 1fr; /* No topbar row — topbar is inside main content now, or handled differently */
height: 100vh;
overflow: hidden;
transition: grid-template-columns 200ms ease;
}
.app-shell--pinned {
--sidebar-w: 260px;
}
@media (max-width: 767px) {
.app-shell {
grid-template-columns: 1fr;
}
}
```
- [ ] **Step 6: Update base body styles**
```css
body {
@apply bg-background text-foreground font-sans;
color: var(--color-text-primary);
background-color: var(--color-bg-page);
}
```
- [ ] **Step 7: Keep React Flow, Sonner, scrollbar, and accessibility styles**
These stay mostly unchanged — just verify they reference the correct new variable names. Update any references to removed variables.
- [ ] **Step 8: Verify build**
Run: `cd frontend && npm run build`
Expected: Build succeeds. App looks different (shims mapping old classes to flat styles) but is functional.
- [ ] **Step 9: Commit**
```bash
git add frontend/src/index.css
git commit -m "refactor: rewrite CSS foundation for Design System v4
New flat dark color tokens, remove glass/gradient utilities,
add compatibility shims for phased migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 2: Icon Rail Sidebar
**Files:**
- Rewrite: `frontend/src/components/layout/Sidebar.tsx`
- Modify: `frontend/src/components/layout/AppLayout.tsx`
- Modify: `frontend/src/components/layout/TopBar.tsx`
- Modify: `frontend/src/components/common/BrandLogo.tsx`
- Reference: `DESIGN-SYSTEM.md` → Layout Architecture, Icon Rail Sidebar sections
This is a complex task. The sidebar has three states: icon rail (72px), flyout (hovering), and pinned (260px). Read the current `Sidebar.tsx` and `AppLayout.tsx` fully before starting.
- [ ] **Step 1: Read current sidebar and layout**
Read `frontend/src/components/layout/Sidebar.tsx` and `frontend/src/components/layout/AppLayout.tsx` completely to understand current nav structure, items, mobile handling, and grid layout.
- [ ] **Step 2: Implement icon rail sidebar**
Rewrite `Sidebar.tsx` with:
- Default: 72px icon rail with `bg-[var(--color-bg-sidebar)]`
- Logo mark at top: 30px cyan gradient square with lightning bolt SVG
- Nav items: 20px Lucide icon + 10px label text underneath, centered
- Horizontal dividers between RESOLVE / KNOWLEDGE / INSIGHTS sections
- Active item: `bg-[var(--color-accent-dim)]` background, `text-[var(--color-accent-text)]` on icon + label
- Inactive: `text-[var(--color-text-rail-label)]`, opacity 0.6 default, 0.85 hover
- User avatar at bottom
- Pin toggle button below avatar
Nav items (same as current, just icon + short label):
- Dashboard (LayoutDashboard)
- Sessions (Activity) — with count badge
- Escalations (AlertTriangle)
- Divider
- Flows (GitBranch)
- Steps (Layers)
- Scripts (Code2)
- Builder (Wand2)
- Review (ListChecks)
- Divider
- Exports (Download)
- Analytics (BarChart3)
- FP Analytics (Rocket)
Footer: User Guides, Feedback, Account (as icons), Pin/Unpin toggle
- [ ] **Step 3: Implement flyout panels**
On hover of a nav item, show a 220px flyout panel to the right:
- `bg-[var(--color-bg-card)]` with `border border-[var(--color-border-default)]`
- `box-shadow: 0 4px 12px rgba(0,0,0,0.3)`
- 150ms ease-out transition (opacity + translateX)
- Also opens on keyboard focus/Enter
- Closes on Escape, mouse leave from both rail item and flyout, or focus-out
- `max-height: 70vh; overflow-y: auto` for long sub-item lists
- Sub-items match current sidebar children (e.g., Flows → All Flows, Troubleshooting, Projects, Maintenance)
- [ ] **Step 4: Implement pinned state**
Clicking pin button:
- Expands sidebar to 260px with full text labels, section headers, badges
- Updates `--sidebar-w` CSS variable to `260px`
- Shows unpin button in sidebar header
- Persists preference to localStorage (`sidebarPinned: true/false`)
- Active item gets 3px left accent bar
- [ ] **Step 5: Mobile handling**
Below `sm` breakpoint:
- Icon rail hidden
- Hamburger button in top-left corner
- Opens full-screen overlay with pinned sidebar content
- Close button (X) in overlay header
- [ ] **Step 6: Update AppLayout.tsx**
- Grid layout changes from `grid-template-rows: 56px 1fr` to `grid-template-rows: auto 1fr` — TopBar remains a grid row spanning both columns (same as current), but with flat styling
- Grid columns: `var(--sidebar-w) 1fr` (72px default, 260px when pinned via `.app-shell--pinned` class)
- Sidebar pin state: read from `localStorage.getItem('sidebarPinned')` on mount, toggle sets `localStorage.setItem('sidebarPinned', 'true'/'false')` and updates `--sidebar-w` CSS variable on the `.app-shell` element
- Ensure `--sidebar-w` transition is coordinated with sidebar width transition to prevent layout jump on pin/unpin (both use `transition: 200ms ease`)
- Mobile: single column, no sidebar
- [ ] **Step 7: Update TopBar.tsx**
- Remove backdrop blur
- Flat `bg-[var(--color-bg-sidebar)]` or `bg-[var(--color-bg-page)]` background
- `border-b border-[var(--color-border-default)]`
- Remove any glass/gradient styling
- [ ] **Step 8: Update BrandLogo.tsx**
- Replace decision-tree icon SVG with 30px gradient square (cyan, border-radius 8px) + white lightning bolt
- Wordmark: "ResolutionFlow" in Bricolage Grotesque 700, `text-[var(--color-text-heading)]` (no gradient)
- Remove `text-gradient-brand` usage
- [ ] **Step 9: Verify build and test**
Run: `cd frontend && npm run build`
Expected: Build succeeds. Sidebar renders as icon rail. Flyout works on hover. Pin/unpin works.
- [ ] **Step 10: Commit**
```bash
git add frontend/src/components/layout/ frontend/src/components/common/BrandLogo.tsx
git commit -m "refactor: implement icon rail sidebar for Design System v4
72px icon rail with hover flyouts, pin-to-expand toggle,
keyboard accessible, mobile hamburger overlay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 3: Component Sweep — Layout & Dashboard
**Files:**
- Modify: All files in `frontend/src/components/dashboard/` (~16 files)
- Modify: Any remaining layout components
Apply the pattern replacement map from the spec to every file. For each file:
1. Replace `glass-card` / `glass-card-static``card-flat` or `card-interactive`
2. Replace `bg-gradient-brand``btn-primary` class or `bg-[var(--color-accent)]`
3. Replace `text-gradient-brand``text-[var(--color-accent-text)]`
4. Replace `text-foreground``text-[var(--color-text-primary)]`
5. Replace `text-muted-foreground``text-[var(--color-text-secondary)]`
6. Replace `font-label` on code/monospace → `font-mono`; on labels/badges → `font-sans text-xs`
7. Remove `backdrop-filter`, `shadow-lg shadow-primary/20`, `scale(1.02)` hover
8. Remove atmosphere orb JSX elements
9. Replace `rounded-[10px]` / `rounded-[16px]``rounded-lg` (8px)
10. Replace inline `style={{ background: 'rgba(...)' }}` glass surfaces → CSS variable classes
- [ ] **Step 1: Read and update all dashboard components**
Process all `.tsx` files in `frontend/src/components/dashboard/` (~18 files). Apply the pattern replacement map to each.
- [ ] **Step 2: Verify build**
Run: `cd frontend && npm run build`
- [ ] **Step 3: Commit**
```bash
git add frontend/src/components/dashboard/ frontend/src/components/layout/
git commit -m "refactor: migrate dashboard components to Design System v4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 4: Component Sweep — FlowPilot
**Files:**
- Modify: All files in `frontend/src/components/flowpilot/` (~12 files)
Same pattern replacement map as Task 3.
- [ ] **Step 1: Read and update all FlowPilot components**
Process all `.tsx` files in `frontend/src/components/flowpilot/` (~17 files). Apply the pattern replacement map to each.
- [ ] **Step 2: Verify build**
Run: `cd frontend && npm run build`
- [ ] **Step 3: Commit**
```bash
git add frontend/src/components/flowpilot/
git commit -m "refactor: migrate FlowPilot components to Design System v4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 5: Component Sweep — Pages
**Files:**
- Modify: All files in `frontend/src/pages/` (~25 files)
Same pattern replacement map.
- [ ] **Step 1: Read and update all page components**
Process all `.tsx` files in `frontend/src/pages/` (~41 files). Apply the pattern replacement map to each.
- [ ] **Step 2: Verify build**
Run: `cd frontend && npm run build`
- [ ] **Step 3: Commit**
```bash
git add frontend/src/pages/
git commit -m "refactor: migrate page components to Design System v4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 6: Component Sweep — Session, Script Builder, Account
**Files:**
- Modify: `frontend/src/components/session/` (~21 files)
- Modify: `frontend/src/components/script-builder/` (~5 files)
- Modify: `frontend/src/components/account/` (~5 files)
- [ ] **Step 1: Update session components**
- [ ] **Step 2: Update script-builder components**
- [ ] **Step 3: Update account components**
- [ ] **Step 4: Verify build**
Run: `cd frontend && npm run build`
- [ ] **Step 5: Commit**
```bash
git add frontend/src/components/session/ frontend/src/components/script-builder/ frontend/src/components/account/
git commit -m "refactor: migrate session, script-builder, account to Design System v4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 7: Component Sweep — All Remaining
**Files:**
- Modify: `frontend/src/components/common/` (~22 files)
- Modify: `frontend/src/components/tree-editor/` (~20 files)
- Modify: `frontend/src/components/kb-accelerator/` (~5 files)
- Modify: `frontend/src/components/copilot/` (~3 files)
- Modify: `frontend/src/components/assistant/` (~3 files)
- Modify: `frontend/src/components/analytics/` (~3 files)
- Modify: `frontend/src/components/library/` (~3 files)
- Modify: `frontend/src/components/procedural/` (~3 files)
- Modify: `frontend/src/components/public/` (~3 files)
- Modify: `frontend/src/components/script-editor/` (~6 files)
- Modify: `frontend/src/components/ui/` (any using old patterns)
- Modify: `frontend/src/components/admin/` (~12 files)
- [ ] **Step 1: Update all remaining component directories**
Process each directory. Use the same pattern replacement map.
- [ ] **Step 2: Verify build**
Run: `cd frontend && npm run build`
- [ ] **Step 3: Commit**
```bash
git add frontend/src/components/
git commit -m "refactor: migrate remaining components to Design System v4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 8: Landing Page
**Files:**
- Modify: `frontend/src/pages/LandingPage.tsx`
- Modify: Landing page CSS (check if `landing.css` exists or if styles are in `index.css`)
- [ ] **Step 1: Find landing page styles**
```bash
find frontend/src -name "landing*" -o -name "Landing*" | head -10
grep -r "landing" frontend/src/index.css | head -5
```
- [ ] **Step 2: Update landing page**
- Remove ambient glow effects, gradient overlays
- Hero: subtle radial gradient at 15% accent opacity
- Section labels: `font-mono`, 12px, uppercase, `text-[var(--color-accent-text)]`
- Section titles: `font-heading`, `clamp(28px, 4vw, 42px)`, weight 800
- Pricing cards: `card-flat` with featured card getting `border-[var(--color-accent)]`
- Top nav: flat, no blur
- CTAs: `btn-primary` (solid accent)
- [ ] **Step 3: Verify build**
Run: `cd frontend && npm run build`
- [ ] **Step 4: Commit**
```bash
git add frontend/src/pages/LandingPage.tsx frontend/src/styles/landing.css
git commit -m "refactor: migrate landing page to Design System v4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```
---
## Task 9: Cleanup & Verification
**Files:**
- Modify: `frontend/src/index.css`
- Modify: `CLAUDE.md` (verify)
- [ ] **Step 1: Remove compatibility shims**
Remove from `index.css`:
- The `glass-card` shim utility
- The `glass-card-static` shim utility
- The `text-gradient-brand` shim utility
- The `active-glow` shim utility
- The `--font-label` alias (keep only `--font-mono`)
- Any remaining old gradient references
- [ ] **Step 1.5: Update BrandWordmark.tsx**
Check `frontend/src/components/common/BrandWordmark.tsx` for `text-gradient-brand` usage. Replace with `text-[var(--color-accent-text)]`.
- [ ] **Step 2: Verify no old patterns remain**
```bash
cd frontend && grep -r "glass-card\|glass-stat\|bg-gradient-brand\|text-gradient-brand\|backdrop-filter.*blur\|atmosphere-orb\|active-glow\|breatheGlow\|bellWobble" src/ --include="*.tsx" --include="*.css" | head -20
```
Expected: Zero results (or only in comments/string literals).
- [ ] **Step 3: Verify CLAUDE.md**
Grep CLAUDE.md for any stale references to old design system patterns. Should already be clean from the doc-swap commit, but verify.
- [ ] **Step 4: Final build verification**
Run: `cd frontend && npm run build`
Expected: Clean build with zero errors.
- [ ] **Step 5: Commit**
```bash
git add frontend/src/index.css CLAUDE.md
git commit -m "chore: remove design system v3 compatibility shims
All components migrated to v4 flat dark theme.
No glass-card, gradient, or blur references remain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
```

View File

@@ -0,0 +1,276 @@
# Design System v4 Migration — Flat Dark Theme
> **Goal:** Migrate ResolutionFlow's frontend from glassmorphism/gradient aesthetic to a flat, high-contrast dark theme (Sentry/PostHog-inspired) with an icon rail sidebar, as defined in DESIGN-SYSTEM.md.
## Scope
~130 files across 15+ directories use old design patterns (`glass-card`, `bg-gradient-brand`, `text-gradient-brand`, `backdrop-filter: blur()`, ambient orbs). All must be updated to the new flat dark system.
## Approach
**Foundation first, then sweep.** Three phases:
1. **Phase 1: CSS Foundation** — Rewrite `index.css` tokens, remove old utilities, add new variables
2. **Phase 2: Layout Shell** — Icon rail sidebar + app layout restructure
3. **Phase 3: Component Sweep** — Update all 132 files directory-by-directory
Each phase produces deployable commits. The app will look inconsistent during Phase 3 but will always be functional.
---
## Phase 1: CSS Foundation
### index.css @theme Block
Rewrite the `@theme` block with new color tokens matching DESIGN-SYSTEM.md:
```
Page background: #0c0d10 → --bg-page
Sidebar background: #0f1118 → --bg-sidebar
Card background: #14161d → --bg-card
Card hover: #191c25 → --bg-card-hover
Input background: #191c25 → --bg-input
Code background: #0e1017 → --bg-code
Elevated surface: #1c1f2a → --bg-elevated
Text heading: #f0f2f5 → --text-heading
Text primary: #e2e5eb → --text-primary
Text secondary: #848b9b → --text-secondary
Text muted: #4f5666 → --text-muted
Border default: #1e2130 → --border-default
Border hover: #2a2f3d → --border-hover
Accent: #22d3ee → --accent
Accent hover: #06b6d4 → --accent-hover
Accent dim (10%): rgba(34,211,238,0.10) → --accent-dim
Accent text: #67e8f9 → --accent-text
Success: #34d399 + dim at 10%
Warning: #fbbf24 + dim at 10%
Danger: #f87171 + dim at 10%
Rail label: #6b7280 → --text-rail-label
```
### Variables Structure
All colors in `:root` (dark is default). Light mode values prepared as comments — implementing the toggle is a future follow-up that just adds a `.light` class block.
### Compatibility Shims (Critical)
To avoid breaking the entire app while Phase 3 sweeps components, Phase 1 keeps the old utility class names as **deprecated aliases** mapping to the new values:
```css
/* Deprecated — remove after Phase 3 sweep completes */
.glass-card, .glass-card-static { @apply bg-[var(--bg-card)] border border-[var(--border-default)] rounded-lg; }
.glass-card:hover { @apply border-[var(--border-hover)]; }
```
These shims are removed in a final cleanup commit after Phase 3 is complete.
### Remove Old Utilities
Delete from `index.css` (after Phase 3 sweep — shims keep the app functional until then):
- `.glass-card`, `.glass-card-static`, `.glass-stat`, `.glass-card-glow`
- `.active-glow`, `.breatheGlow` keyframes
- `bg-gradient-brand`, `text-gradient-brand`, `bg-gradient-brand-hover`
- `--glass-bg`, `--glass-border`, `--glass-blur` variables
- `--shadow-float`, `--shadow-float-hover`, `--shadow-cyan-glow` shadow variables
- Atmosphere orb styles (`.atmosphere-orb`)
- Orchestrated page-load animation keyframes (slideDown, slideInLeft, fadeInUp, fadeInRight)
### Rename
- `--font-label``--font-mono` (JetBrains Mono, same font, clearer name)
### Keep
- Google Fonts imports (same three fonts)
- React Flow import
- Tailwind v4 `@theme` approach
- Base animation keyframes: `fade-in`, `fade-in-up`, `slide-in-from-left`, `slide-in-from-bottom` — all others removed (no `breatheGlow`, `bellWobble`, `slideDown`, orchestrated page-load sequence)
### New Base Classes
Add reusable component classes:
```css
.card { /* bg-card, 1px border-default, 8px radius, no shadow */ }
.card-interactive { /* extends .card + hover border-hover transition */ }
.stat-card { /* extends .card + 3px left border */ }
.badge { /* 11px, pill shape, dim bg + matching text */ }
.btn-primary { /* solid accent, white text, 5px radius — NOTE: text changes from dark (#101114) to white */ }
.btn-ghost { /* transparent, 1px border, hover bg-elevated */ }
```
Implement each class per DESIGN-SYSTEM.md Component Patterns section for exact values.
---
## Phase 2: Icon Rail Sidebar
### New Component: IconRailSidebar
Replaces current `Sidebar.tsx`. Two states:
**Icon Rail (72px, default):**
- `bg-sidebar` background, full viewport height
- Logo mark at top: 30px cyan gradient square with lightning bolt
- Nav items: vertical column, 20px icon + 10px label underneath
- Horizontal dividers between sections (RESOLVE / KNOWLEDGE / INSIGHTS)
- User avatar at bottom
- Pin/expand toggle below avatar
- Active item: `accent-dim` background, `accent-text` on icon + label
**Hover Flyout (220px panel):**
- Opens to the right of the hovered rail item with 150ms ease-out transition
- Also opens on keyboard focus/Enter for accessibility; closes on Escape or focus-out
- Shows sub-navigation for that section
- If section has many sub-items, flyout scrolls with `max-height: 70vh; overflow-y: auto`
- `bg-card` with `border-default` and `box-shadow: 0 4px 12px rgba(0,0,0,0.3)`
- Stays open while cursor is on rail item OR flyout panel
- Closes on mouse leave from both
**Pinned State (260px full sidebar):**
- Click pin → expands to traditional sidebar with text labels, section headers, badges
- 3px left accent bar on active item
- Unpin button in sidebar header
- Preference persisted in localStorage / userPreferences store
**Mobile:**
- Icon rail hidden below `sm` breakpoint
- Hamburger menu opens full overlay sidebar (same as pinned content)
### Layout Changes
`AppLayout.tsx` CSS Grid:
- Default: `grid-template-columns: 72px 1fr`
- Pinned: `grid-template-columns: 260px 1fr`
- Mobile: `grid-template-columns: 1fr`
- CSS variable `--sidebar-w` updated accordingly (used by FlowPilotMessageBar, FlowPilotActionBar)
- Ensure `--sidebar-w` transition is coordinated with sidebar width transition to prevent layout jump on pin/unpin
**Sidebar commits:** Phase 2 produces 2-3 commits (icon rail core, flyout behavior, pinned state + persistence).
### BrandLogo Update
Replace current decision-tree icon SVG in `BrandLogo.tsx` with 30px gradient square (cyan, `border-radius: 8px`) + white lightning bolt mark. Wordmark: "ResolutionFlow" in Bricolage Grotesque 700, `text-heading` color (no gradient).
### Files
| Action | File |
|--------|------|
| Rewrite | `frontend/src/components/layout/Sidebar.tsx` → icon rail with flyout + pinned state |
| Modify | `frontend/src/components/layout/AppLayout.tsx` → grid columns, mobile handling |
| Modify | `frontend/src/components/layout/TopBar.tsx` → flat styling, remove blur |
| Keep | `FlowPilotMessageBar.tsx`, `FlowPilotActionBar.tsx``left: var(--sidebar-w)` still works |
---
## Phase 3: Component Sweep
### Pattern Replacement Map
| Old Pattern | New Pattern |
|------------|-------------|
| `glass-card` / `glass-card-static` | `bg-[var(--bg-card)] border border-[var(--border-default)] rounded-lg` (or `.card` class) |
| `bg-gradient-brand` | `bg-[var(--accent)]` (solid cyan) |
| `text-gradient-brand` | `text-[var(--accent-text)]` |
| `bg-gradient-brand-hover` | `hover:brightness-110` |
| `text-foreground` | `text-[var(--text-primary)]` |
| `text-muted-foreground` | `text-[var(--text-secondary)]` |
| `bg-background` | `bg-[var(--bg-page)]` |
| `bg-card` (old token) | `bg-[var(--bg-card)]` |
| `border-border` | `border-[var(--border-default)]` |
| `font-label` (on code/monospace content) | `font-mono` |
| `font-label` (on labels/badges/timestamps) | `font-sans text-xs` — inspect each usage |
| `backdrop-filter: blur(...)` | Remove |
| `shadow-lg shadow-primary/20` on buttons | Remove |
| `rounded-[10px]` / `rounded-[16px]` on cards | `rounded-lg` (8px) |
| `scale(1.02)` hover on cards | Border color transition |
| `.atmosphere-orb` elements | Remove from JSX |
| `style={{ background: 'rgba(...)' }}` glass surfaces | Use CSS variable classes |
### Sweep Order (one commit per directory group, ~10 commits total)
Phase 3 covers app components only; landing page is handled in Phase 4.
1. **`components/layout/`** (3 files) — AppLayout, TopBar, BrandLogo
2. **`components/dashboard/`** (16 files) — highest visibility
3. **`components/flowpilot/`** (12 files) — core product
4. **`pages/`** (25 files) — all page components
5. **`components/session/`** (8 files)
6. **`components/script-builder/`** (4 files)
7. **`components/kb-accelerator/`** (5 files)
8. **`components/account/`** (4 files)
9. **`components/tree-editor/`** (4 files)
10. **All remaining directories** (~51 files: admin, analytics, assistant, common, copilot, library, procedural, public, script-editor, ui)
### What Stays the Same
- Component logic, state management, hooks, API calls — untouched
- Lucide icons — same
- `cn()` utility — same
- All non-design Tailwind utilities (flex, grid, padding, margin, etc.)
- React Router, Zustand stores, type definitions
---
## Phase 4: Landing Page
- Remove ambient glow effects and gradient overlays from `landing.css`
- Hero: subtle radial gradient at 15% accent opacity (replaces heavy glow)
- Section labels: JetBrains Mono, 12px, uppercase, accent-text
- Section titles: Bricolage Grotesque, `clamp(28px, 4vw, 42px)`, weight 800
- Pricing cards: flat `bg-card` + `border-default`, featured card gets accent border
- Top nav: flat, no blur backdrop
- CTAs: solid accent buttons
- Product mockup: update screenshot to show new flat UI
**Files:** `landing.css`, `LandingPage.tsx`, landing-specific components
---
## Phase 5: Cleanup & Documentation
- Remove compatibility shims from `index.css` (deprecated `glass-card` aliases from Phase 1)
- Update CLAUDE.md: verify branding section and design system section reflect the new flat theme (already partially done in the doc-swap commit — verify no stale references remain)
- Update `BrandWordmark.tsx` if it references `text-gradient-brand`
- Verify `npm run build` passes with zero references to removed utilities
**Light mode note:** All colors use CSS custom properties. Light mode values from DESIGN-SYSTEM.md are stored as comments in `index.css`. Adding light mode later = add `.light` class values + toggle in user settings. Not implemented in this migration.
---
## Out of Scope
- Light mode toggle (future follow-up — just variable swap + settings toggle)
- React Flow canvas theme updates (separate concern, works with any color scheme)
- Backend changes (none needed)
- New features or functionality (purely visual migration)
- Test changes (no visual tests exist)
---
## Risk Mitigation
- Each phase produces deployable commits
- The app is functional throughout (just visually inconsistent during Phase 3)
- Old CSS utilities removed in Phase 1 — if a component breaks visually, it's obvious
- `npm run build` verified after each phase
- No logic changes = no new bugs in functionality
---
## Verification
After each phase:
1. `npm run build` passes
2. Visual spot-check of key pages (Dashboard, FlowPilot session, Script Builder, Landing)
After all phases:
1. Full app walkthrough — every sidebar nav item
2. Mobile responsive check (icon rail hidden, hamburger menu works)
3. Landing page check
4. FlowPilot session check (message bar, action bar positioning)

View File

@@ -18,7 +18,6 @@
"@sentry/vite-plugin": "^5.1.1",
"@stripe/stripe-js": "^8.7.0",
"@tailwindcss/vite": "^4.2.1",
"@types/react-syntax-highlighter": "^15.5.13",
"@xyflow/react": "^12.10.0",
"axios": "^1.13.4",
"class-variance-authority": "^0.7.1",
@@ -51,6 +50,7 @@
"@types/node": "^24.10.9",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@vitejs/plugin-react": "^5.1.1",
"@vitest/coverage-v8": "^4.0.18",
"eslint": "^9.39.1",
@@ -3021,6 +3021,7 @@
"version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"

View File

@@ -31,7 +31,6 @@
"@sentry/vite-plugin": "^5.1.1",
"@stripe/stripe-js": "^8.7.0",
"@tailwindcss/vite": "^4.2.1",
"@types/react-syntax-highlighter": "^15.5.13",
"@xyflow/react": "^12.10.0",
"axios": "^1.13.4",
"class-variance-authority": "^0.7.1",
@@ -64,6 +63,7 @@
"@types/node": "^24.10.9",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@vitejs/plugin-react": "^5.1.1",
"@vitest/coverage-v8": "^4.0.18",
"eslint": "^9.39.1",

View File

@@ -36,7 +36,7 @@ export function DeleteAccountModal({ onClose }: Props) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
<div className="glass-card-static w-full max-w-md p-6">
<div className="card-flat w-full max-w-md p-6">
<div className="flex items-center gap-2 text-rose-500 mb-4">
<AlertTriangle className="h-5 w-5" />
<h2 className="text-lg font-semibold font-heading text-foreground">Delete Account</h2>
@@ -55,7 +55,7 @@ export function DeleteAccountModal({ onClose }: Props) {
onChange={(e) => setPassword(e.target.value)}
required
className={cn(
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
'mt-1 block w-full rounded-lg border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-hidden'
)}
/>
@@ -68,8 +68,8 @@ export function DeleteAccountModal({ onClose }: Props) {
type="button"
onClick={onClose}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-medium',
'bg-white/[0.04] border border-brand-border text-foreground'
'rounded-lg px-4 py-2 text-sm font-medium',
'bg-input border border-border text-foreground'
)}
>
Cancel
@@ -78,7 +78,7 @@ export function DeleteAccountModal({ onClose }: Props) {
type="submit"
disabled={isSubmitting || !password}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-semibold',
'rounded-lg px-4 py-2 text-sm font-semibold',
'bg-rose-500 text-white hover:bg-rose-400 disabled:opacity-50'
)}
>

View File

@@ -31,7 +31,7 @@ export function LeaveAccountModal({ accountName, onClose }: Props) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
<div className="glass-card-static w-full max-w-md p-6">
<div className="card-flat w-full max-w-md p-6">
<div className="flex items-center gap-2 text-amber-400 mb-4">
<AlertTriangle className="h-5 w-5" />
<h2 className="text-lg font-semibold font-heading text-foreground">Leave Account</h2>
@@ -44,8 +44,8 @@ export function LeaveAccountModal({ accountName, onClose }: Props) {
<button
onClick={onClose}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-medium',
'bg-white/[0.04] border border-brand-border text-foreground'
'rounded-lg px-4 py-2 text-sm font-medium',
'bg-input border border-border text-foreground'
)}
>
Cancel
@@ -54,7 +54,7 @@ export function LeaveAccountModal({ accountName, onClose }: Props) {
onClick={handleLeave}
disabled={isSubmitting}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-semibold',
'rounded-lg px-4 py-2 text-sm font-semibold',
'bg-rose-500 text-white hover:bg-rose-400 disabled:opacity-50'
)}
>

View File

@@ -181,7 +181,7 @@ export function NotificationSettings() {
<button
onClick={() => setShowDropdown(!showDropdown)}
className={cn(
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
'hover:border-[rgba(255,255,255,0.12)] transition-all'
)}
@@ -220,7 +220,7 @@ export function NotificationSettings() {
{/* Empty state */}
{!loading && configs.length === 0 && !addingChannel && (
<div className="glass-card-static p-6 text-center">
<div className="card-flat p-6 text-center">
<Bell className="mx-auto h-8 w-8 text-muted-foreground/50 mb-3" />
<p className="text-sm text-muted-foreground">
No notification channels configured. Add a channel to receive alerts for session events.
@@ -234,7 +234,7 @@ export function NotificationSettings() {
{configs.map(config => {
const Icon = CHANNEL_ICONS[config.channel]
return (
<div key={config.id} className="glass-card-static p-5">
<div key={config.id} className="card-flat p-5">
{/* Header row */}
<div className="flex items-center gap-3 mb-4">
<Icon className="h-5 w-5 text-muted-foreground" />
@@ -248,7 +248,7 @@ export function NotificationSettings() {
)}
/>
<span className={cn(
'text-xs font-label',
'text-xs font-sans text-xs',
config.is_active ? 'text-emerald-400' : 'text-muted-foreground'
)}>
{config.is_active ? 'Active' : 'Inactive'}
@@ -259,7 +259,7 @@ export function NotificationSettings() {
<div className="mb-4">
{config.webhook_url && (
<div>
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Webhook URL
</span>
<p className="mt-0.5 text-sm text-foreground font-mono">
@@ -269,7 +269,7 @@ export function NotificationSettings() {
)}
{config.email_addresses && config.email_addresses.length > 0 && (
<div>
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Email Addresses
</span>
<p className="mt-0.5 text-sm text-foreground">
@@ -281,7 +281,7 @@ export function NotificationSettings() {
{/* Event toggles */}
<div className="mb-4">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Events
</span>
<div className="mt-2 grid gap-2 sm:grid-cols-2">
@@ -307,7 +307,7 @@ export function NotificationSettings() {
<button
onClick={() => handleToggleActive(config)}
className={cn(
'inline-flex items-center gap-1.5 rounded-[10px] px-3 py-1.5 text-sm font-medium',
'inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
'hover:border-[rgba(255,255,255,0.12)] transition-all'
)}
@@ -324,7 +324,7 @@ export function NotificationSettings() {
onClick={() => handleTest(config.id)}
disabled={testingId === config.id}
className={cn(
'inline-flex items-center gap-1.5 rounded-[10px] px-3 py-1.5 text-sm font-medium',
'inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
'hover:border-[rgba(255,255,255,0.12)] transition-all',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -362,7 +362,7 @@ export function NotificationSettings() {
{/* Inline add form */}
{addingChannel && (
<div className="glass-card-static p-5">
<div className="card-flat p-5">
<div className="flex items-center gap-3 mb-4">
{(() => {
const Icon = CHANNEL_ICONS[addingChannel]
@@ -375,7 +375,7 @@ export function NotificationSettings() {
{addingChannel === 'email' ? (
<div>
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Email Addresses
</label>
<Input
@@ -391,7 +391,7 @@ export function NotificationSettings() {
</div>
) : (
<div>
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Webhook URL
</label>
<Input
@@ -413,9 +413,9 @@ export function NotificationSettings() {
onClick={handleSaveNew}
disabled={isSaving}
className={cn(
'inline-flex items-center gap-2 rounded-[10px] px-5 py-2.5 text-sm font-semibold',
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
'hover:opacity-90 active:scale-[0.97] transition-all',
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
'bg-primary text-white',
'hover:brightness-110 active:scale-[0.98] transition-all',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>

View File

@@ -40,7 +40,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
<div className="glass-card-static w-full max-w-md p-6">
<div className="card-flat w-full max-w-md p-6">
<div className="flex items-center gap-2 text-amber-400 mb-4">
<AlertTriangle className="h-5 w-5" />
<h2 className="text-lg font-semibold font-heading text-foreground">Transfer Ownership</h2>
@@ -59,7 +59,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
value={targetUserId}
onChange={(e) => setTargetUserId(e.target.value)}
className={cn(
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
'mt-1 block w-full rounded-lg border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-hidden'
)}
>
@@ -76,7 +76,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
onChange={(e) => setPassword(e.target.value)}
required
className={cn(
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
'mt-1 block w-full rounded-lg border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-hidden'
)}
/>
@@ -89,8 +89,8 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
type="button"
onClick={onClose}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-medium',
'bg-white/[0.04] border border-brand-border text-foreground'
'rounded-lg px-4 py-2 text-sm font-medium',
'bg-input border border-border text-foreground'
)}
>
Cancel
@@ -99,8 +99,8 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
type="submit"
disabled={isSubmitting || !password}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-semibold',
'bg-amber-500 text-brand-dark hover:bg-amber-400',
'rounded-lg px-4 py-2 text-sm font-semibold',
'bg-amber-500 text-white hover:bg-amber-400',
'disabled:opacity-50'
)}
>

View File

@@ -44,7 +44,7 @@ export function AdminLayout() {
{mobileOpen && (
<div className="fixed inset-0 z-40 md:hidden">
<div
className="absolute inset-0 bg-card/80 backdrop-blur-xs"
className="absolute inset-0 bg-card/80"
onClick={() => setMobileOpen(false)}
/>
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">

View File

@@ -58,7 +58,7 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
btnBase,
'px-2',
p === page
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
? 'bg-primary text-white'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>

View File

@@ -39,7 +39,7 @@ function getFlowCountStyle(count: number) {
export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
if (data.domains.length === 0) {
return (
<div className="glass-card-static p-6">
<div className="card-flat p-6">
<div className="flex items-center gap-2 mb-2">
<MapPin size={16} className="text-foreground" />
<h3 className="font-heading text-sm font-semibold text-foreground">Domain Coverage</h3>
@@ -52,7 +52,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
}
return (
<div className="glass-card-static p-3 sm:p-5">
<div className="card-flat p-3 sm:p-5">
<div className="mb-4">
<div className="flex items-center gap-2 mb-1">
<MapPin size={16} className="text-foreground" />
@@ -67,25 +67,25 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
<table className="w-full">
<thead>
<tr className="border-b border-border">
<th className="px-3 py-2 text-left font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<th className="px-3 py-2 text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Domain
</th>
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Flows
</th>
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Sessions
</th>
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Resolution %
</th>
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Escalation %
</th>
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Guided %
</th>
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
title="Average time to resolve sessions in this domain. Green: &lt;10 min, Amber: 1020 min, Red: &gt;20 min. Lower is better.">
Avg Resolution
</th>
@@ -176,7 +176,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
</table>
</div>
<div className="flex flex-wrap gap-4 mt-3 text-[0.625rem] font-label text-muted-foreground">
<div className="flex flex-wrap gap-4 mt-3 text-[0.625rem] font-sans text-xs text-muted-foreground">
<span><span className="inline-block w-2 h-2 rounded-full bg-emerald-400 mr-1" />Good</span>
<span><span className="inline-block w-2 h-2 rounded-full bg-amber-400 mr-1" />Needs Improvement</span>
<span><span className="inline-block w-2 h-2 rounded-full bg-rose-500 mr-1" />Critical</span>

View File

@@ -110,7 +110,7 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
if (data.flows.length === 0) {
return (
<div className="glass-card-static p-3 sm:p-5">
<div className="card-flat p-3 sm:p-5">
<div className="flex items-center justify-center min-h-[200px]">
<p className="text-sm text-muted-foreground">
No flows found for this account. Create your first flow to start tracking quality.
@@ -130,7 +130,7 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
]
return (
<div className="glass-card-static p-3 sm:p-5">
<div className="card-flat p-3 sm:p-5">
<div className="mb-4">
<div className="flex items-center gap-2">
<h3 className="font-heading text-sm font-semibold text-foreground">
@@ -160,7 +160,7 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
return (
<th
key={col.key}
className="text-left py-2 px-2 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground cursor-pointer select-none hover:text-foreground transition-colors"
className="text-left py-2 px-2 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground cursor-pointer select-none hover:text-foreground transition-colors"
onClick={() => handleSort(col.key)}
title={col.title}
>
@@ -221,11 +221,11 @@ function FlowRow({
>
{flow.name}
</Link>
<span className={cn('font-label text-[0.5rem] uppercase tracking-wider ml-2 shrink-0', TYPE_LABELS[flow.tree_type]?.color || 'text-muted-foreground')}>
<span className={cn('font-sans text-xs text-[0.5rem] uppercase tracking-wider ml-2 shrink-0', TYPE_LABELS[flow.tree_type]?.color || 'text-muted-foreground')}>
{TYPE_LABELS[flow.tree_type]?.label || flow.tree_type}
</span>
{needsAttention && (
<span className="shrink-0 bg-amber-400/10 text-amber-400 font-label text-[0.625rem] px-1.5 py-0.5 rounded">
<span className="shrink-0 bg-amber-400/10 text-amber-400 font-sans text-xs text-[0.625rem] px-1.5 py-0.5 rounded">
Needs attention
</span>
)}

View File

@@ -19,7 +19,7 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
if (!hasActivity) {
return (
<div className="glass-card-static p-3 sm:p-5">
<div className="card-flat p-3 sm:p-5">
<div className="flex items-center justify-center min-h-[200px]">
<p className="text-sm text-muted-foreground">
No PSA activity data for this period. Link sessions to PSA tickets to see metrics.
@@ -33,19 +33,19 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
<div className="space-y-4">
{/* Row 1 — Metric cards */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="glass-card-static p-4">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Time Entries</p>
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.total_time_entries}</p>
<div className="card-flat p-4">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-text-muted">Time Entries</p>
<p className="text-accent-text font-heading text-2xl mt-1">{data.total_time_entries}</p>
<p className="text-xs text-muted-foreground mt-1">logged to PSA</p>
</div>
<div className="glass-card-static p-4">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Hours Logged</p>
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.total_hours_logged.toFixed(1)}</p>
<div className="card-flat p-4">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-text-muted">Hours Logged</p>
<p className="text-accent-text font-heading text-2xl mt-1">{data.total_hours_logged.toFixed(1)}</p>
<p className="text-xs text-muted-foreground mt-1">total hours tracked</p>
</div>
<div className="glass-card-static p-4">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Avg Hours/Session</p>
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.avg_hours_per_session.toFixed(2)}</p>
<div className="card-flat p-4">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-text-muted">Avg Hours/Session</p>
<p className="text-accent-text font-heading text-2xl mt-1">{data.avg_hours_per_session.toFixed(2)}</p>
<p className="text-xs text-muted-foreground mt-1">per resolved session</p>
</div>
</div>
@@ -55,7 +55,7 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
{/* Row 3 — Daily Trend Chart */}
{data.daily_trend.length > 0 && (
<div className="glass-card-static p-3 sm:p-5">
<div className="card-flat p-3 sm:p-5">
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
PSA Activity Trend
</h3>
@@ -129,7 +129,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
]
return (
<div className="glass-card-static p-3 sm:p-5">
<div className="card-flat p-3 sm:p-5">
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
Documentation Push Funnel
</h3>
@@ -139,7 +139,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
{steps.map((step, i) => (
<div key={step.label} className="flex items-center gap-2 flex-1">
<div className="bg-primary/5 border border-primary/20 rounded-lg p-3 text-center flex-1">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
{step.label}
</p>
<p className="text-lg font-heading text-foreground">{step.count}</p>
@@ -147,7 +147,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
{i < steps.length - 1 && (
<div className="flex flex-col items-center shrink-0 px-1">
<ArrowRight size={14} className="text-muted-foreground" />
<span className="text-[0.625rem] text-muted-foreground font-label">
<span className="text-[0.625rem] text-muted-foreground font-sans text-xs">
{funnelPct(steps[i].count, steps[i + 1].count)}
</span>
</div>
@@ -161,7 +161,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
{steps.map((step, i) => (
<div key={step.label} className="flex flex-col items-center gap-2 w-full">
<div className="bg-primary/5 border border-primary/20 rounded-lg p-3 text-center w-full">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
{step.label}
</p>
<p className="text-lg font-heading text-foreground">{step.count}</p>
@@ -169,7 +169,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
{i < steps.length - 1 && (
<div className="flex items-center gap-1">
<ArrowDown size={14} className="text-muted-foreground" />
<span className="text-[0.625rem] text-muted-foreground font-label">
<span className="text-[0.625rem] text-muted-foreground font-sans text-xs">
{funnelPct(steps[i].count, steps[i + 1].count)}
</span>
</div>

View File

@@ -29,7 +29,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
role === 'user'
? 'bg-primary/15 text-foreground'
: 'bg-white/[0.04] text-foreground border border-brand-border'
: 'bg-input text-foreground border border-border'
}`}
>
<MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
@@ -38,7 +38,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
{/* Suggested flows (assistant only) */}
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
<div className="space-y-1.5">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground">
Related Flows
</span>
{suggestedFlows.map(flow => (

View File

@@ -31,7 +31,7 @@ export function ChatSidebar({
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--glass-border)' }}>
<button
onClick={onNewChat}
className="w-full flex items-center justify-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-4 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
className="w-full flex items-center justify-center gap-2 bg-primary text-white font-semibold text-sm rounded-lg px-4 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all"
>
<Plus size={16} />
New Chat
@@ -42,7 +42,7 @@ export function ChatSidebar({
<div className="flex-1 overflow-y-auto py-2">
{pinnedChats.length > 0 && (
<div className="px-3 mb-1">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground">
Pinned
</span>
</div>
@@ -102,8 +102,8 @@ function ChatItem({
className={cn(
'group flex items-center gap-2 px-3 py-2.5 mx-1.5 rounded-lg cursor-pointer transition-colors',
isActive
? 'bg-primary/10 text-foreground'
: 'text-muted-foreground hover:bg-white/[0.04] hover:text-foreground'
? 'bg-accent-dim text-foreground'
: 'text-muted-foreground hover:bg-input hover:text-foreground'
)}
>
<MessageSquare size={14} className="shrink-0" />

View File

@@ -136,13 +136,13 @@ export function ConcludeSessionModal({
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-xs"
className="absolute inset-0 bg-black/60"
onClick={onClose}
/>
{/* Modal */}
<div
className="relative w-full max-w-2xl mx-4 glass-card-static overflow-hidden animate-in fade-in zoom-in-95 duration-200"
className="relative w-full max-w-2xl mx-4 card-flat overflow-hidden animate-in fade-in zoom-in-95 duration-200"
style={{
maxHeight: 'calc(100vh - 4rem)',
display: 'flex',
@@ -155,7 +155,7 @@ export function ConcludeSessionModal({
style={{ borderColor: 'var(--glass-border)' }}
>
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-primary/10 flex items-center justify-center">
<div className="w-9 h-9 rounded-xl bg-accent-dim flex items-center justify-center">
<ClipboardList size={18} className="text-primary" />
</div>
<div>
@@ -169,7 +169,7 @@ export function ConcludeSessionModal({
</div>
<button
onClick={onClose}
className="p-2 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
className="p-2 rounded-lg hover:bg-border text-muted-foreground hover:text-foreground transition-colors"
>
<X size={18} />
</button>
@@ -194,9 +194,9 @@ export function ConcludeSessionModal({
)}
<div
className={cn(
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-label font-medium transition-colors',
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-sans text-xs font-medium transition-colors',
step === s
? 'bg-gradient-brand text-brand-dark'
? 'bg-primary text-white'
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
? 'bg-primary/20 text-primary'
: 'bg-brand-border text-muted-foreground'
@@ -206,7 +206,7 @@ export function ConcludeSessionModal({
</div>
<span
className={cn(
'text-xs font-label',
'text-xs font-sans text-xs',
step === s ? 'text-foreground' : 'text-muted-foreground'
)}
>
@@ -233,8 +233,8 @@ export function ConcludeSessionModal({
className={cn(
'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left',
'hover:scale-[1.01] active:scale-[0.99]',
'bg-white/[0.02] border-brand-border',
'hover:border-white/[0.12] hover:bg-white/[0.04]'
'bg-card border-border',
'hover:border-border-hover hover:bg-input'
)}
>
<div className={cn('w-10 h-10 rounded-xl flex items-center justify-center', o.bg)}>
@@ -255,7 +255,7 @@ export function ConcludeSessionModal({
<div className="space-y-4">
{/* Selected outcome badge */}
<div className="flex items-center gap-2">
<div className={cn('px-3 py-1.5 rounded-lg flex items-center gap-2 text-xs font-label', selectedOutcome.bg, selectedOutcome.border, 'border')}>
<div className={cn('px-3 py-1.5 rounded-lg flex items-center gap-2 text-xs font-sans text-xs', selectedOutcome.bg, selectedOutcome.border, 'border')}>
<selectedOutcome.icon size={14} className={selectedOutcome.color} />
<span className={selectedOutcome.color}>{selectedOutcome.label}</span>
</div>
@@ -268,7 +268,7 @@ export function ConcludeSessionModal({
</div>
<div>
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground block mb-2">
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground block mb-2">
Additional Notes (optional)
</label>
<textarea
@@ -300,7 +300,7 @@ export function ConcludeSessionModal({
<div className="space-y-4">
{/* Outcome badge */}
{selectedOutcome && (
<div className={cn('px-3 py-1.5 rounded-lg inline-flex items-center gap-2 text-xs font-label', selectedOutcome.bg, selectedOutcome.border, 'border')}>
<div className={cn('px-3 py-1.5 rounded-lg inline-flex items-center gap-2 text-xs font-sans text-xs', selectedOutcome.bg, selectedOutcome.border, 'border')}>
<selectedOutcome.icon size={14} className={selectedOutcome.color} />
<span className={selectedOutcome.color}>{selectedOutcome.label}</span>
</div>
@@ -308,11 +308,11 @@ export function ConcludeSessionModal({
{/* Generated summary */}
<div
className="rounded-xl border p-5 bg-white/[0.02]"
className="rounded-xl border p-5 bg-card"
style={{ borderColor: 'var(--glass-border)' }}
>
<div className="flex items-center justify-between mb-3">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
<Sparkles size={10} className="text-primary" />
Generated Ticket Notes
</span>
@@ -335,7 +335,7 @@ export function ConcludeSessionModal({
<div />
<button
onClick={onClose}
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
className="px-4 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground bg-input border border-border hover:border-border-hover transition-all"
>
Cancel
</button>
@@ -346,14 +346,14 @@ export function ConcludeSessionModal({
<>
<button
onClick={() => setStep('select-outcome')}
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
className="px-4 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground bg-input border border-border hover:border-border-hover transition-all"
>
Back
</button>
<button
onClick={handleGenerate}
disabled={generating}
className="flex items-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-5 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
className="flex items-center gap-2 bg-primary text-white font-semibold text-sm rounded-lg px-5 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
>
{generating ? (
<>
@@ -376,7 +376,7 @@ export function ConcludeSessionModal({
{outcome === 'paused' && (
<button
onClick={handleResumeNew}
className="flex items-center gap-2 px-4 py-2.5 rounded-[10px] text-sm font-medium text-blue-400 bg-blue-400/10 border border-blue-400/20 hover:bg-blue-400/15 transition-all"
className="flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium text-blue-400 bg-blue-400/10 border border-blue-400/20 hover:bg-blue-400/15 transition-all"
>
<RefreshCw size={14} />
Resume in New Chat
@@ -387,10 +387,10 @@ export function ConcludeSessionModal({
<button
onClick={handleCopy}
className={cn(
'flex items-center gap-2 px-4 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
'flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-semibold transition-all',
copied
? 'bg-emerald-400/15 text-emerald-400 border border-emerald-400/30'
: 'bg-gradient-brand text-brand-dark hover:opacity-90 active:scale-[0.97]'
: 'bg-primary text-white hover:brightness-110 active:scale-[0.98]'
)}
>
{copied ? (
@@ -407,7 +407,7 @@ export function ConcludeSessionModal({
</button>
<button
onClick={onClose}
className="px-4 py-2.5 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
className="px-4 py-2.5 rounded-lg text-sm text-muted-foreground hover:text-foreground bg-input border border-border hover:border-border-hover transition-all"
>
Done
</button>

View File

@@ -18,7 +18,7 @@ export function SuggestedFlowCard({ flow }: SuggestedFlowCardProps) {
return (
<button
onClick={handleClick}
className="w-full text-left glass-card-static p-3 rounded-xl hover:border-white/[0.12] transition-colors group"
className="w-full text-left card-flat p-3 rounded-xl hover:border-border-hover transition-colors group"
>
<div className="flex items-start gap-2">
<Box size={14} className="text-primary mt-0.5 shrink-0" />
@@ -27,7 +27,7 @@ export function SuggestedFlowCard({ flow }: SuggestedFlowCardProps) {
<span className="text-[0.8125rem] font-medium text-foreground truncate">
{flow.tree_name}
</span>
<span className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground">
{flow.tree_type}
</span>
</div>

View File

@@ -5,37 +5,36 @@ interface BrandLogoProps {
className?: string
}
/**
* Brand logo mark: gradient cyan square with rounded corners
* containing a white lightning bolt.
*/
export function BrandLogo({ size = 'sm', className }: BrandLogoProps) {
const sizeClasses = size === 'sm' ? 'h-8 w-8' : 'h-20 w-20'
const strokeBase = size === 'sm' ? 1 : 2
const strokeThick = size === 'sm' ? 1.25 : 2.5
const dashArray = size === 'sm' ? '1 1.5' : '2 3'
const nodeR = size === 'sm' ? { outer: 2.5, inner: 2.75 } : { outer: 5, inner: 5.5 }
const hubR = size === 'sm' ? { glow: 5, solid: 3.5 } : { glow: 10, solid: 7 }
const vb = size === 'sm' ? '0 0 40 40' : '0 0 80 80'
const s = size === 'sm' ? 1 : 2
const gradId = size === 'sm' ? 'logoGradSm' : 'logoGradLg'
const gradEnd = String(40 * (size === 'sm' ? 1 : 2))
const dim = size === 'sm' ? 30 : 64
return (
<svg viewBox={vb} fill="none" className={cn(sizeClasses, className)}>
<defs>
<linearGradient id={gradId} x1="0" y1="0" x2={gradEnd} y2={gradEnd} gradientUnits="userSpaceOnUse">
<stop offset="0%" stopColor="#06b6d4" />
<stop offset="100%" stopColor="#22d3ee" />
</linearGradient>
</defs>
<circle cx={5 * s} cy={7 * s} r={nodeR.outer} fill={`url(#${gradId})`} opacity="0.5" />
<circle cx={5 * s} cy={15 * s} r={nodeR.inner} fill={`url(#${gradId})`} opacity="0.7" />
<circle cx={5 * s} cy={25 * s} r={nodeR.inner} fill={`url(#${gradId})`} opacity="0.7" />
<circle cx={5 * s} cy={33 * s} r={nodeR.outer} fill={`url(#${gradId})`} opacity="0.5" />
<path d={`M${7.5 * s} ${7 * s}L${14 * s} ${17 * s}`} stroke={`url(#${gradId})`} strokeWidth={strokeBase} strokeLinecap="round" strokeDasharray={dashArray} opacity="0.4" />
<path d={`M${7.75 * s} ${15 * s}L${14 * s} ${19 * s}`} stroke={`url(#${gradId})`} strokeWidth={strokeBase} strokeLinecap="round" opacity="0.5" />
<path d={`M${7.75 * s} ${25 * s}L${14 * s} ${21 * s}`} stroke={`url(#${gradId})`} strokeWidth={strokeBase} strokeLinecap="round" opacity="0.5" />
<path d={`M${7.5 * s} ${33 * s}L${14 * s} ${23 * s}`} stroke={`url(#${gradId})`} strokeWidth={strokeBase} strokeLinecap="round" strokeDasharray={dashArray} opacity="0.4" />
<circle cx={18 * s} cy={20 * s} r={hubR.glow} fill={`url(#${gradId})`} opacity="0.15" />
<circle cx={18 * s} cy={20 * s} r={hubR.solid} fill={`url(#${gradId})`} opacity="0.9" />
<path d={`M${21.5 * s} ${20 * s}H${35 * s}M${35 * s} ${20 * s}L${30 * s} ${15 * s}M${35 * s} ${20 * s}L${30 * s} ${25 * s}`} stroke={`url(#${gradId})`} strokeWidth={strokeThick} strokeLinecap="round" strokeLinejoin="round" />
<div
className={cn('shrink-0 flex items-center justify-center', className)}
style={{
width: dim,
height: dim,
borderRadius: size === 'sm' ? 8 : 14,
background: 'linear-gradient(135deg, #06b6d4, #22d3ee)',
}}
>
<svg
viewBox="0 0 24 24"
fill="none"
style={{ width: dim * 0.5, height: dim * 0.5 }}
>
<path
d="M13 2L4.5 13.5H12L11 22L19.5 10.5H12L13 2Z"
fill="white"
stroke="white"
strokeWidth="0.5"
strokeLinejoin="round"
/>
</svg>
</div>
)
}

View File

@@ -9,13 +9,12 @@ export function BrandWordmark({ size = 'sm', className }: BrandWordmarkProps) {
return (
<span
className={cn(
'font-heading font-bold tracking-tight',
'font-heading font-bold tracking-tight text-text-heading',
size === 'sm' ? 'text-xl' : 'text-3xl',
className
)}
>
<span className="text-foreground">Resolution</span>
<span className="text-gradient-brand">Flow</span>
ResolutionFlow
</span>
)
}

View File

@@ -49,7 +49,7 @@ export function ConfirmDialog({
'disabled:opacity-50 disabled:cursor-not-allowed',
confirmVariant === 'destructive'
? 'bg-red-400/10 text-red-400 hover:bg-red-400/20 border border-red-400/20'
: 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90'
: 'bg-primary text-white hover:brightness-110'
)}
>
{isLoading ? 'Processing...' : confirmLabel}

View File

@@ -71,7 +71,7 @@ export function ContextMenu({ position, items, onClose }: ContextMenuProps) {
left: position.x,
top: position.y,
}}
className="min-w-[200px] rounded-xl border border-border bg-card p-1 shadow-lg backdrop-blur-md"
className="min-w-[200px] rounded-xl border border-border bg-card p-1 shadow-lg"
>
{items.map((item) => (
<div key={item.id}>
@@ -87,7 +87,7 @@ export function ContextMenu({ position, items, onClose }: ContextMenuProps) {
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
item.variant === 'danger'
? 'text-rose-400 hover:bg-rose-500/10'
: 'text-foreground hover:bg-brand-border'
: 'text-foreground hover:bg-border'
)}
>
{item.icon && (

View File

@@ -65,7 +65,7 @@ export function CreateFlowDropdown({
<div className={cn('relative', className)}>
<button
onClick={() => setShowMenu(!showMenu)}
className="flex items-center gap-2 rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-primary/20 hover:opacity-90 transition-opacity"
className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-white hover:brightness-110 transition-opacity"
>
<Plus size={16} />
{label}
@@ -74,7 +74,7 @@ export function CreateFlowDropdown({
{showMenu && (
<>
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} />
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-xs">
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl">
{/* Troubleshooting */}
<Link
to="/trees/new"

View File

@@ -125,7 +125,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
>
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
className="absolute inset-0 bg-black/80"
onClick={onClose}
aria-hidden="true"
/>

View File

@@ -34,7 +34,7 @@ export function TagBadges({
}}
disabled={!onTagClick}
className={cn(
'rounded-full font-label transition-colors',
'rounded-full font-sans text-xs transition-colors',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
variant === 'default'
? 'bg-accent text-muted-foreground hover:bg-accent'
@@ -48,7 +48,7 @@ export function TagBadges({
{hiddenCount > 0 && (
<span
className={cn(
'rounded-full font-label',
'rounded-full font-sans text-xs',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
'bg-accent/50 text-muted-foreground'
)}

View File

@@ -110,7 +110,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
className="p-1.5 rounded-lg hover:bg-border text-muted-foreground hover:text-foreground transition-colors"
>
<X size={16} />
</button>
@@ -124,7 +124,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
msg.role === 'user'
? 'bg-primary/15 text-foreground'
: 'bg-white/[0.04] text-foreground border border-brand-border'
: 'bg-input text-foreground border border-border'
}`}
>
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
@@ -133,7 +133,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
))}
{loading && (
<div className="flex justify-start">
<div className="bg-white/[0.04] border border-brand-border rounded-xl px-3.5 py-2.5">
<div className="bg-input border border-border rounded-xl px-3.5 py-2.5">
<Loader2 size={16} className="animate-spin text-primary" />
</div>
</div>
@@ -142,7 +142,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
{/* Suggested flows */}
{suggestedFlows.length > 0 && (
<div className="space-y-2 pt-2">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground">
Related Flows
</span>
{suggestedFlows.map(flow => (
@@ -171,7 +171,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
<button
onClick={handleSend}
disabled={!input.trim() || loading || initializing}
className="bg-gradient-brand text-brand-dark p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
className="bg-primary text-white p-2.5 rounded-xl hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-40"
>
<Send size={16} />
</button>

View File

@@ -11,7 +11,7 @@ export function CopilotToggle({ isOpen, onToggle }: CopilotToggleProps) {
return (
<button
onClick={onToggle}
className="fixed bottom-6 right-6 z-40 bg-gradient-brand text-brand-dark p-3.5 rounded-full shadow-lg shadow-primary/30 hover:opacity-90 active:scale-[0.97] transition-all"
className="fixed bottom-6 right-6 z-40 bg-primary text-white p-3.5 rounded-full hover:brightness-110 active:scale-[0.98] transition-all"
title="Open AI Copilot"
>
<MessageCircle size={22} />

View File

@@ -29,7 +29,7 @@ export function ActiveFlowPilotSessions() {
if (loading) {
return (
<div className="glass-card-static">
<div className="card-flat">
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
</div>
@@ -43,7 +43,7 @@ export function ActiveFlowPilotSessions() {
}
return (
<div className="glass-card-static">
<div className="card-flat">
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -51,7 +51,7 @@ export function ActiveFlowPilotSessions() {
<div className="flex items-center gap-2">
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
{sessions.length > 0 && (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary/10 px-1.5 text-[0.625rem] font-bold text-primary">
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-accent-dim px-1.5 text-[0.625rem] font-bold text-primary">
{sessions.length}
</span>
)}
@@ -67,7 +67,7 @@ export function ActiveFlowPilotSessions() {
{sessions.length === 0 ? (
<div className="px-5 py-8 text-center">
<p className="text-sm text-muted-foreground">No active sessions</p>
<p className="mt-1 text-[0.6875rem] text-[#5a6170]">Start typing above to begin troubleshooting</p>
<p className="mt-1 text-[0.6875rem] text-text-muted">Start typing above to begin troubleshooting</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
@@ -75,13 +75,13 @@ export function ActiveFlowPilotSessions() {
<button
key={session.id}
onClick={() => navigate(`/pilot/${session.id}`)}
className="glass-card p-4 text-left"
className="card-interactive p-4 text-left"
>
<div className="flex items-start justify-between gap-2 mb-2">
<Sparkles size={14} className="shrink-0 text-primary mt-0.5" />
<span
className={cn(
'font-label text-[0.5625rem] uppercase px-1.5 py-0.5 rounded',
'font-sans text-xs text-[0.5625rem] uppercase px-1.5 py-0.5 rounded',
session.confidence_tier === 'guided' && 'bg-emerald-400/10 text-emerald-400',
session.confidence_tier === 'exploring' && 'bg-amber-400/10 text-amber-400',
session.confidence_tier === 'discovery' && 'bg-blue-400/10 text-blue-400',

View File

@@ -22,7 +22,7 @@ export function FiltersBar({ filters, activeFilter, onFilterChange }: FiltersBar
className={cn(
'shrink-0 rounded-lg border px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
activeFilter === f.id
? 'border-primary/30 bg-primary/10 text-primary'
? 'border-[#22d3ee]/30 bg-accent-dim text-primary'
: 'border-border bg-card text-muted-foreground hover:border-border/80 hover:text-foreground'
)}
>

View File

@@ -20,7 +20,7 @@ export function KnowledgeBaseCards() {
]
return (
<div className="glass-card-static">
<div className="card-flat">
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -38,11 +38,14 @@ export function KnowledgeBaseCards() {
<button
key={item.label}
onClick={() => navigate(item.href)}
className="flex flex-col items-center gap-2 py-5 hover:bg-[rgba(255,255,255,0.02)] transition-colors"
className="flex flex-col items-center gap-2 py-5 rounded-lg hover:bg-card-hover transition-all duration-350"
style={{ transition: 'transform 350ms cubic-bezier(0.34, 1.56, 0.64, 1), background 200ms ease' }}
onMouseEnter={e => { e.currentTarget.style.transform = 'translateY(-4px)' }}
onMouseLeave={e => { e.currentTarget.style.transform = 'translateY(0)' }}
>
<item.icon size={20} style={{ color: item.color }} />
<p className="font-heading text-xl font-extrabold text-foreground">{item.value}</p>
<p className="font-label text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
{item.label}
</p>
</button>

View File

@@ -69,11 +69,11 @@ export function OnboardingChecklist() {
}
return (
<div className="glass-card overflow-hidden fade-in" style={{ animationDelay: '150ms' }}>
<div className="card-interactive overflow-hidden fade-in" style={{ animationDelay: '150ms' }}>
{/* Progress bar */}
<div className="h-1 w-full bg-[rgba(255,255,255,0.04)]">
<div
className="h-full bg-gradient-brand transition-all duration-500 ease-out"
className="h-full bg-primary transition-all duration-500 ease-out"
style={{ width: `${progressPercent}%` }}
/>
</div>
@@ -82,15 +82,15 @@ export function OnboardingChecklist() {
{/* Header */}
<div className="flex items-center justify-between mb-3">
<div>
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Getting Started
</p>
<p className="text-sm text-foreground mt-0.5">
{isAllDone ? (
<span className="text-gradient-brand font-semibold">You're all set!</span>
<span className="text-accent-text font-semibold">You're all set!</span>
) : (
<span>
<span className="text-gradient-brand font-semibold">{completedCount}</span>
<span className="text-accent-text font-semibold">{completedCount}</span>
{' '}of {totalCount} complete
</span>
)}
@@ -126,11 +126,11 @@ export function OnboardingChecklist() {
className={cn(
'flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors',
done
? 'bg-gradient-brand border-transparent'
? 'bg-primary border-transparent'
: 'border-border'
)}
>
{done && <Check size={12} className="text-[#101114]" />}
{done && <Check size={12} className="text-white" />}
</span>
{/* Label */}

View File

@@ -17,7 +17,7 @@ interface OpenSessionsProps {
export function OpenSessions({ sessions }: OpenSessionsProps) {
return (
<div className="glass-card-static flex flex-col h-full">
<div className="card-flat flex flex-col h-full">
<div className="flex items-center justify-between px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
<h3 className="font-heading text-sm font-bold text-foreground">My Open Sessions</h3>
<Link to="/sessions" className="text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors">
@@ -46,13 +46,13 @@ export function OpenSessions({ sessions }: OpenSessionsProps) {
? `Step ${session.stepNumber} of ${session.totalSteps}`
: 'In progress'}
<span className="mx-1.5 text-[var(--text-dimmed)]">&middot;</span>
<span className="font-label text-[0.625rem]">{session.timeAgo}</span>
<span className="font-sans text-xs text-[0.625rem]">{session.timeAgo}</span>
</div>
</div>
<Link
to={getTreeNavigatePath(session.treeId, session.treeType)}
state={{ sessionId: session.id }}
className="shrink-0 rounded-lg bg-gradient-brand px-3 py-1 text-[0.6875rem] font-medium text-primary-foreground hover:opacity-90 transition-opacity"
className="shrink-0 rounded-lg border border-primary/40 px-3 py-1 text-[0.6875rem] font-medium text-primary hover:bg-primary/10 hover:border-primary/60 transition-colors"
>
Resume
</Link>

View File

@@ -28,7 +28,7 @@ export function PendingEscalations() {
return (
<div
className="glass-card-static overflow-hidden"
className="card-flat overflow-hidden"
style={{ borderColor: 'rgba(251, 191, 36, 0.2)' }}
>
<div
@@ -70,7 +70,7 @@ export function PendingEscalations() {
<div className="text-[0.6875rem] text-muted-foreground">
{esc.problem_domain || 'General'}
<span className="mx-1.5 text-[var(--text-dimmed)]">&middot;</span>
<span className="font-label text-[0.625rem]">{timeAgo(esc.created_at)}</span>
<span className="font-sans text-xs">{timeAgo(esc.created_at)}</span>
</div>
</div>
<button

View File

@@ -70,21 +70,21 @@ export function PerformanceCards() {
<button
key={card.label}
onClick={() => navigate(card.href)}
className={cn(
'glass-card p-4 text-left fade-in',
i === 0 && 'active-glow'
)}
style={{ animationDelay: `${400 + i * 60}ms` }}
className="card-interactive p-4 text-left fade-in"
style={{
animationDelay: `${400 + i * 60}ms`,
borderLeft: `3px solid ${card.iconColor}`,
}}
>
<div className="flex items-center justify-between mb-2">
<p className="font-label text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
{card.label}
</p>
<card.icon size={14} style={{ color: card.iconColor }} />
</div>
<p className={cn(
'font-heading text-2xl font-extrabold tracking-tight',
card.highlight ? 'text-gradient-brand' : 'text-foreground'
card.highlight ? 'text-accent-text' : 'text-foreground'
)}>
{card.value}
</p>

View File

@@ -28,7 +28,7 @@ export function PreparedSessions() {
}
return (
<div className="glass-card-static p-5 fade-in" style={{ animationDelay: '200ms' }}>
<div className="card-flat p-5 fade-in" style={{ animationDelay: '200ms' }}>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<ClipboardList className="h-4 w-4 text-cyan-400" />

View File

@@ -12,7 +12,7 @@ export function QuickActions() {
const navigate = useNavigate()
return (
<div className="glass-card-static flex flex-col h-full">
<div className="card-flat flex flex-col h-full">
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
<h3 className="font-heading text-sm font-bold text-foreground">Quick Actions</h3>
</div>
@@ -21,7 +21,7 @@ export function QuickActions() {
<button
key={label}
onClick={() => navigate(href)}
className="glass-card flex items-center gap-3 px-4 py-3 text-left"
className="card-interactive flex items-center gap-3 px-4 py-3 text-left"
>
<span
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"

View File

@@ -18,16 +18,16 @@ export function QuickStats({ stats }: QuickStatsProps) {
{stats.map((stat, i) => (
<div
key={stat.label}
className={cn('glass-card p-4 fade-in', i === 0 && 'active-glow')}
className={cn('card-interactive p-4 fade-in')}
style={{ animationDelay: `${50 + i * 30}ms` }}
>
<p className="font-label text-[0.625rem] font-medium uppercase tracking-widest text-muted-foreground">
<p className="font-sans text-xs text-[0.625rem] font-medium uppercase tracking-widest text-muted-foreground">
{stat.label}
</p>
<p
className={cn(
'mt-1 font-heading text-2xl font-extrabold tracking-tight',
stat.gradient && 'text-gradient-brand',
stat.gradient && 'text-accent-text',
stat.color
)}
style={stat.color && !stat.color.startsWith('text-') ? { color: stat.color } : undefined}

View File

@@ -24,7 +24,7 @@ const DEFAULT_ACTIVITIES: ActivityItem[] = [
export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivityProps) {
return (
<div className="glass-card-static">
<div className="card-flat">
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
<h3 className="font-heading text-sm font-bold text-foreground">Recent Activity</h3>
</div>
@@ -39,7 +39,7 @@ export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivi
}}
>
<span
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-[10px]"
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"
style={{ background: item.iconBg }}
>
<item.icon size={16} style={{ color: item.iconColor }} />
@@ -47,7 +47,7 @@ export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivi
<div className="flex-1 min-w-0 pt-0.5">
<p className="text-sm text-foreground">{item.description}</p>
</div>
<span className="shrink-0 font-label text-[0.625rem] text-muted-foreground pt-1">
<span className="shrink-0 font-sans text-xs text-[0.625rem] text-muted-foreground pt-1">
{item.timestamp}
</span>
</div>

View File

@@ -41,7 +41,7 @@ export function RecentFlowPilotSessions() {
if (sessions.length === 0) return null
return (
<div className="glass-card-static">
<div className="card-flat">
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -73,7 +73,7 @@ export function RecentFlowPilotSessions() {
{session.problem_summary || 'Session'}
</p>
</div>
<span className="shrink-0 font-label text-[0.625rem] text-muted-foreground">
<span className="shrink-0 font-sans text-xs text-muted-foreground">
{timeAgo(session.resolved_at || session.created_at)}
</span>
</button>

View File

@@ -19,12 +19,12 @@ export function SectionGroup({ title, count, defaultOpen = true, delay = 150, ch
onClick={() => setOpen(!open)}
className="flex w-full items-center gap-2 py-2"
>
<span className="h-2 w-2 shrink-0 rounded-full bg-gradient-brand" />
<span className="h-2 w-2 shrink-0 rounded-full bg-primary" />
<span className="font-heading text-[0.8125rem] font-bold uppercase tracking-[0.04em] text-foreground">
{title}
</span>
{count !== undefined && (
<span className="rounded-full bg-secondary px-2 py-0.5 font-label text-[0.6875rem] text-muted-foreground">
<span className="rounded-full bg-secondary px-2 py-0.5 font-sans text-xs text-muted-foreground">
{count}
</span>
)}

View File

@@ -21,7 +21,7 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
if (sessions.length === 0) return null
return (
<div className="glass-card-static fade-in" style={{ animationDelay: `${delay}ms` }}>
<div className="card-flat fade-in" style={{ animationDelay: `${delay}ms` }}>
<div className="flex items-center justify-between px-4 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
<h3 className="font-heading text-sm font-semibold text-foreground">Recent Sessions</h3>
<Link to="/sessions" className="text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors">
@@ -42,7 +42,7 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
'h-2 w-2 rounded-full',
session.status === 'completed' ? 'bg-green-500' :
session.status === 'in_progress' ? 'bg-amber-500' :
'bg-muted-foreground'
'bg-[#848b9b]'
)}
/>
@@ -59,7 +59,7 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
</span>
{/* Ticket */}
<span className="font-label text-[0.6875rem] text-muted-foreground truncate">
<span className="font-sans text-xs text-[0.6875rem] text-muted-foreground truncate">
{session.ticketNumber || '—'}
</span>

View File

@@ -32,7 +32,7 @@ export function StartSessionInput() {
}
return (
<div className="glass-card-static overflow-hidden">
<div className="card-flat overflow-hidden">
<div className="px-5 py-4 sm:px-6 sm:py-5">
<div className="relative">
<Sparkles
@@ -50,14 +50,14 @@ export function StartSessionInput() {
/>
</div>
<div className="mt-3 flex items-center justify-between">
<div className="flex items-center gap-1 rounded-lg bg-[rgba(255,255,255,0.04)] p-0.5">
<div className="flex items-center gap-1 rounded-lg bg-secondary p-0.5">
<button
type="button"
onClick={() => setMode('guided')}
className={cn(
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-all duration-150',
mode === 'guided'
? 'bg-primary/10 text-foreground'
? 'bg-accent-dim text-foreground tab-active-shadow'
: 'text-muted-foreground hover:text-foreground'
)}
>
@@ -68,9 +68,9 @@ export function StartSessionInput() {
type="button"
onClick={() => setMode('chat')}
className={cn(
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-all duration-150',
mode === 'chat'
? 'bg-primary/10 text-foreground'
? 'bg-accent-dim text-foreground tab-active-shadow'
: 'text-muted-foreground hover:text-foreground'
)}
>

View File

@@ -25,7 +25,7 @@ export function TeamSummary() {
]
return (
<div className="glass-card-static">
<div className="card-flat">
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -43,11 +43,14 @@ export function TeamSummary() {
<button
key={item.label}
onClick={() => navigate(item.href)}
className="flex flex-col items-center gap-2 py-5 hover:bg-[rgba(255,255,255,0.02)] transition-colors"
className="flex flex-col items-center gap-2 py-5 rounded-lg hover:bg-card-hover transition-all duration-350"
style={{ transition: 'transform 350ms cubic-bezier(0.34, 1.56, 0.64, 1), background 200ms ease' }}
onMouseEnter={e => { e.currentTarget.style.transform = 'translateY(-4px)' }}
onMouseLeave={e => { e.currentTarget.style.transform = 'translateY(0)' }}
>
<item.icon size={20} style={{ color: item.color }} />
<p className="font-heading text-xl font-extrabold text-foreground">{item.value}</p>
<p className="font-label text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
{item.label}
</p>
</button>

View File

@@ -49,7 +49,7 @@ export function TreeListItem({
<p className="font-heading text-sm font-semibold text-foreground truncate">{name}</p>
<div className="mt-0.5 flex items-center gap-2">
{tags.slice(0, 3).map(tag => (
<span key={tag} className="rounded border border-border bg-secondary px-1.5 py-px font-label text-[0.625rem] text-muted-foreground">
<span key={tag} className="rounded border border-border bg-secondary px-1.5 py-px font-sans text-xs text-[0.625rem] text-muted-foreground">
{tag}
</span>
))}
@@ -64,13 +64,13 @@ export function TreeListItem({
{category && (
<>
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: categoryColor }} />
<span className="font-label text-xs text-muted-foreground truncate">{category.name}</span>
<span className="font-sans text-xs text-muted-foreground truncate">{category.name}</span>
</>
)}
</div>
{/* Usage count */}
<div className="text-right font-label text-xs text-muted-foreground">
<div className="text-right font-sans text-xs text-muted-foreground">
{usageCount} uses
</div>

View File

@@ -34,7 +34,7 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
const days = useMemo(() => getWeekDays(), [])
return (
<div className="glass-card-static flex flex-col h-full">
<div className="card-flat flex flex-col h-full">
<div className="flex items-center gap-2 px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
<Calendar size={16} className="text-muted-foreground" />
<h3 className="font-heading text-sm font-bold text-foreground">This Week</h3>
@@ -56,7 +56,7 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
borderBottom: day.isToday ? '2px solid #06b6d4' : '1px solid var(--glass-border)',
}}
>
<span className={`font-label text-[0.625rem] uppercase tracking-widest ${day.isToday ? 'text-cyan-400' : 'text-muted-foreground'}`}>
<span className={`font-sans text-xs text-[0.625rem] uppercase tracking-widest ${day.isToday ? 'text-cyan-400' : 'text-muted-foreground'}`}>
{day.label}
</span>
<div className={`text-sm font-heading font-bold ${day.isToday ? 'text-foreground' : 'text-muted-foreground'}`}>
@@ -77,7 +77,7 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
}}
>
<div className="font-medium text-foreground truncate">{event.title}</div>
<div className="font-label text-[0.5625rem] text-muted-foreground">{event.time}</div>
<div className="font-sans text-xs text-[0.5625rem] text-muted-foreground">{event.time}</div>
</div>
))
)}

View File

@@ -50,7 +50,7 @@ export function AIPromptDialog({
/>
{/* Dialog */}
<div className="glass-card-static relative w-full max-w-lg p-6 shadow-xl">
<div className="card-flat relative w-full max-w-lg p-6 shadow-xl">
<div className="flex items-center gap-2 mb-4">
<Sparkles className="h-5 w-5 text-primary" />
<h2 className="text-lg font-heading font-semibold text-foreground">
@@ -83,14 +83,14 @@ export function AIPromptDialog({
<button
onClick={onClose}
disabled={isGenerating}
className="rounded-[10px] bg-white/[0.04] border border-brand-border px-4 py-2 text-sm text-foreground hover:border-white/[0.12] transition-colors disabled:opacity-50"
className="rounded-[10px] bg-input border border-border px-4 py-2 text-sm text-foreground hover:border-border-hover transition-colors disabled:opacity-50"
>
Cancel
</button>
<button
onClick={handleGenerate}
disabled={!prompt.trim() || isGenerating}
className="flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-brand-dark shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
>
{isGenerating ? (
<>

View File

@@ -50,7 +50,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
msg.role === 'user'
? 'bg-primary/15 text-foreground'
: 'bg-white/[0.04] text-foreground border border-brand-border'
: 'bg-input text-foreground border border-border'
}`}
>
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
@@ -59,7 +59,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-white/[0.04] border border-brand-border rounded-xl px-3.5 py-2.5">
<div className="bg-input border border-border rounded-xl px-3.5 py-2.5">
<Loader2 size={16} className="animate-spin text-primary" />
</div>
</div>
@@ -84,7 +84,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
<button
onClick={onSend}
disabled={!input.trim() || isLoading}
className="bg-gradient-brand text-brand-dark p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
className="bg-primary text-white p-2.5 rounded-xl hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-40"
>
<Send size={16} />
</button>

View File

@@ -65,7 +65,7 @@ export function EditorAIPanel({
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
className="p-1.5 rounded-lg hover:bg-border text-muted-foreground hover:text-foreground transition-colors"
>
<X size={16} />
</button>

View File

@@ -29,7 +29,7 @@ export function NodeSummary({ node, flowName, flowType, nodeCount }: NodeSummary
{flowName || 'Untitled Flow'}
</span>
</div>
<div className="mt-1 flex items-center gap-3 font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<div className="mt-1 flex items-center gap-3 font-sans text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span>{flowType || 'flow'}</span>
{nodeCount !== undefined && <span>{nodeCount} nodes</span>}
</div>
@@ -44,7 +44,7 @@ export function NodeSummary({ node, flowName, flowType, nodeCount }: NodeSummary
<div className="border-b px-3 py-2.5" style={{ borderColor: 'var(--glass-border)' }}>
<div className="flex items-center gap-2">
<Icon className={`h-3.5 w-3.5 ${colorClass}`} />
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span className="font-sans text-[0.625rem] uppercase tracking-widest text-muted-foreground">
{node.type}
</span>
</div>

View File

@@ -26,9 +26,9 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
const config = STATUS_CONFIG[s.status]
const StatusIcon = config.icon
return (
<div key={s.id} className="rounded-lg border border-border bg-white/[0.02] px-3 py-2">
<div key={s.id} className="rounded-lg border border-border bg-card px-3 py-2">
<div className="flex items-center justify-between">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span className="font-sans text-[0.625rem] uppercase tracking-widest text-muted-foreground">
{s.action_type.replace(/_/g, ' ')}
</span>
<span className={`flex items-center gap-1 text-xs ${config.color}`}>
@@ -39,7 +39,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
{s.target_node_id && (
<p className="mt-1 text-xs text-muted-foreground truncate">Node: {s.target_node_id}</p>
)}
<p className="mt-0.5 font-label text-[0.625rem] text-brand-text-muted">
<p className="mt-0.5 font-sans text-[0.625rem] text-text-muted">
{new Date(s.created_at).toLocaleDateString()}
</p>
</div>

View File

@@ -12,7 +12,7 @@ const STATUS_CONFIG = {
paused: { icon: Pause, color: 'text-amber-400', label: 'Paused' },
resolved: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Resolved' },
escalated: { icon: ArrowUpRight, color: 'text-amber-400', label: 'Escalated' },
abandoned: { icon: AlertCircle, color: 'text-[#5a6170]', label: 'Abandoned' },
abandoned: { icon: AlertCircle, color: 'text-text-muted', label: 'Abandoned' },
} as const
export function AISessionListItem({ session }: AISessionListItemProps) {
@@ -22,7 +22,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
return (
<Link
to={`/pilot/${session.id}`}
className="glass-card block p-4 transition-all"
className="card-interactive block p-4 transition-all"
>
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
@@ -31,7 +31,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
</p>
<div className="mt-1.5 flex items-center gap-3 flex-wrap">
{session.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
{session.problem_domain}
</span>
)}
@@ -42,7 +42,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
<span className="text-xs text-muted-foreground">
{session.step_count} steps
</span>
<span className="text-xs text-[#5a6170]">
<span className="text-xs text-text-muted">
{new Date(session.created_at).toLocaleDateString(undefined, {
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
})}
@@ -50,7 +50,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
</div>
</div>
{session.session_rating && (
<span className="font-label text-xs text-amber-400">
<span className="font-sans text-xs text-xs text-amber-400">
{'★'.repeat(session.session_rating)}
</span>
)}

View File

@@ -30,12 +30,12 @@ export function ConfidenceIndicator({ tier, score, className }: ConfidenceIndica
return (
<div className={cn('group relative inline-flex items-center gap-2', className)}>
<span className={cn('h-2 w-2 rounded-full', config.color)} />
<span className="font-label text-xs text-muted-foreground">{config.label}</span>
<span className="font-sans text-xs text-xs text-muted-foreground">{config.label}</span>
{/* Tooltip */}
<div className="pointer-events-none absolute left-0 top-full z-50 mt-2 w-56 rounded-lg border border-border bg-card p-3 opacity-0 shadow-lg transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
<p className="text-xs text-muted-foreground">{config.description}</p>
<p className="mt-1 font-label text-[0.625rem] text-[#5a6170]">
<p className="mt-1 font-sans text-xs text-[0.625rem] text-text-muted">
Confidence: {Math.round(score * 100)}%
</p>
</div>

View File

@@ -54,7 +54,7 @@ export function EscalateModal({ open, onClose, onEscalate, isProcessing, hasPsaT
placeholder="e.g. I've exhausted all networking diagnostics and suspect this is a firewall policy issue that requires senior admin access..."
rows={4}
/>
<p className="mt-1 text-[0.625rem] text-[#5a6170]">
<p className="mt-1 text-[0.625rem] text-text-muted">
Minimum 5 characters. This will be shown to the engineer who picks up.
</p>
</div>
@@ -70,7 +70,7 @@ export function EscalateModal({ open, onClose, onEscalate, isProcessing, hasPsaT
<button
onClick={handleSubmit}
disabled={!reason.trim() || reason.trim().length < 5 || isProcessing}
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-amber-500/90 px-4 py-2.5 min-h-[44px] text-sm font-semibold text-[#101114] hover:bg-amber-500 active:scale-[0.97] disabled:opacity-40 transition-all"
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-amber-500/90 px-4 py-2.5 min-h-[44px] text-sm font-semibold text-white hover:bg-amber-500 active:scale-[0.98] disabled:opacity-40 transition-all"
>
{isProcessing ? (
<Loader2 size={14} className="animate-spin" />

View File

@@ -80,7 +80,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
return (
<div className="space-y-3">
<div className="flex items-center justify-between px-1">
<h3 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170]">
<h3 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted">
Awaiting pickup ({sessions.length})
</h3>
<button
@@ -93,7 +93,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
</div>
{sessions.map((session) => (
<div key={session.id} className="glass-card p-3 sm:p-4 space-y-3">
<div key={session.id} className="card-interactive p-3 sm:p-4 space-y-3">
<div>
<p className="text-sm font-semibold text-foreground">
{session.problem_summary || 'Untitled session'}
@@ -107,7 +107,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{session.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
<span className="font-sans text-xs rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
{session.problem_domain}
</span>
)}
@@ -129,7 +129,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
<button
onClick={() => handlePickup(session.id)}
className="w-full min-h-[44px] rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
className="w-full min-h-[44px] rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
>
Pick Up Session
</button>

View File

@@ -70,8 +70,8 @@ export function FlowPilotActionBar({
<>
{/* Bottom bar — fixed to viewport bottom, works regardless of height chain */}
<div
className="fixed bottom-0 right-0 z-40 flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 border-t px-3 py-3 sm:px-5"
style={{ borderColor: 'var(--glass-border)', background: 'rgba(16, 17, 20, 0.95)', backdropFilter: 'blur(16px)', left: 'var(--sidebar-w, 0px)' }}
className="fixed bottom-0 right-0 z-40 flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 border-t border-border bg-card px-3 py-3 sm:px-5"
style={{ left: 'var(--sidebar-w, 0px)' }}
>
<div className="flex gap-2 sm:gap-3">
<button
@@ -118,7 +118,7 @@ export function FlowPilotActionBar({
{/* Resolve modal */}
{showResolve && (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="glass-card-static w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
<h3 className="font-heading text-lg font-semibold text-foreground mb-1">Resolve Session</h3>
<p className="text-sm text-muted-foreground mb-4">Summarize what fixed the issue. This will be included in the auto-generated documentation.</p>
<textarea
@@ -151,7 +151,7 @@ export function FlowPilotActionBar({
{/* Close/Abandon confirmation */}
{showAbandon && (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="glass-card-static w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
<h3 className="font-heading text-lg font-semibold text-foreground mb-1">Close Session</h3>
<p className="text-sm text-muted-foreground mb-4">
Are you sure you want to close this session? The session history will be kept but it won't count as resolved.

View File

@@ -100,7 +100,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
return (
<div className="flex items-center justify-center min-h-[50vh]">
<div className="text-center">
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10">
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-accent-dim">
<Sparkles size={24} className="text-primary animate-pulse" />
</div>
<p className="text-sm font-medium text-foreground">Analyzing your issue...</p>
@@ -126,7 +126,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
</p>
</div>
<div className="glass-card-static p-3 sm:p-5 space-y-4">
<div className="card-flat p-3 sm:p-5 space-y-4">
{/* Selected ticket card */}
{selectedTicket && selectedTicketId && (
<div className="rounded-xl border border-primary/20 bg-primary/5 p-4">
@@ -141,13 +141,13 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
{selectedTicket.company_name && <span>{selectedTicket.company_name}</span>}
{selectedTicket.priority_name && (
<>
<span className="text-[#5a6170]">&bull;</span>
<span className="text-text-muted">&bull;</span>
<span>{selectedTicket.priority_name}</span>
</>
)}
{selectedTicket.status_name && (
<>
<span className="text-[#5a6170]">&bull;</span>
<span className="text-text-muted">&bull;</span>
<span>{selectedTicket.status_name}</span>
</>
)}
@@ -190,7 +190,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
onClick={() => setShowLogs(!showLogs)}
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
showLogs
? 'bg-primary/10 text-primary border border-primary/20'
? 'bg-accent-dim text-primary border border-primary/20'
: 'bg-card/50 text-muted-foreground border border-border hover:text-foreground'
}`}
>
@@ -207,7 +207,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
psaConnection
? 'bg-card/50 text-muted-foreground border border-border hover:text-foreground hover:border-primary/20'
: 'bg-card/50 text-[#5a6170] border border-border opacity-50 cursor-not-allowed'
: 'bg-card/50 text-text-muted border border-border opacity-50 cursor-not-allowed'
}`}
title={!psaConnection ? 'Connect your PSA in Settings → Integrations' : 'Search for a ConnectWise ticket'}
>
@@ -236,13 +236,13 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
{/* Submit */}
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-[0.6875rem] text-[#5a6170] text-center sm:text-left">
<p className="text-[0.6875rem] text-text-muted text-center sm:text-left">
FlowPilot will analyze your input and guide you through diagnosis
</p>
<button
onClick={handleSubmit}
disabled={!hasContent}
className="w-full sm:w-auto min-h-[44px] rounded-lg bg-gradient-brand px-5 py-2.5 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 disabled:shadow-none transition-all whitespace-nowrap"
className="w-full sm:w-auto min-h-[44px] rounded-lg bg-primary text-white px-5 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all whitespace-nowrap"
>
{submitLabel}
</button>

View File

@@ -51,7 +51,7 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
? 'border-border/50 opacity-50'
: 'border-border focus-within:border-[rgba(6,182,212,0.3)]'
)}
style={{ background: 'rgba(16, 17, 20, 0.95)', backdropFilter: 'blur(16px)' }}
style={{ background: 'var(--color-bg-card)' }}
>
<textarea
ref={textareaRef}
@@ -70,7 +70,7 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
className={cn(
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg transition-all',
message.trim() && !isDisabled
? 'bg-gradient-brand text-[#101114] hover:opacity-90 active:scale-[0.97]'
? 'bg-primary text-white hover:brightness-110 active:scale-[0.98]'
: 'bg-[rgba(255,255,255,0.04)] text-muted-foreground cursor-not-allowed'
)}
>

View File

@@ -32,7 +32,7 @@ export function FlowPilotOptions({ options, onSelect, disabled }: FlowPilotOptio
'hover:border-[rgba(6,182,212,0.3)] hover:shadow-[0_0_20px_rgba(6,182,212,0.08)]',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40',
isSelected
? 'border-primary/40 bg-primary/10'
? 'border-primary/40 bg-accent-dim'
: 'border-border bg-card/50',
disabled && 'pointer-events-none opacity-60'
)}

View File

@@ -143,7 +143,7 @@ export function FlowPilotSession({
>
<div className="flex items-center gap-3 text-xs text-muted-foreground overflow-x-auto">
{session.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary shrink-0">
<span className="font-sans text-xs rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary shrink-0">
{session.problem_domain}
</span>
)}
@@ -177,7 +177,7 @@ export function FlowPilotSession({
) : null}
{session.problem_summary && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Problem</h4>
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Problem</h4>
<p className="text-sm text-foreground">{session.problem_summary}</p>
</div>
)}
@@ -238,7 +238,7 @@ export function FlowPilotSession({
{/* Problem summary */}
{session.problem_summary && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
Problem
</h4>
<p className="text-sm text-foreground">{session.problem_summary}</p>
@@ -248,10 +248,10 @@ export function FlowPilotSession({
{/* Domain */}
{session.problem_domain && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
Domain
</h4>
<span className="font-label rounded-md bg-primary/10 px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
{session.problem_domain}
</span>
</div>
@@ -259,7 +259,7 @@ export function FlowPilotSession({
{/* Confidence */}
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
Confidence
</h4>
<ConfidenceIndicator
@@ -271,7 +271,7 @@ export function FlowPilotSession({
{/* Matched flow */}
{session.matched_flow_id && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
Matched flow
</h4>
<div className="flex items-center gap-2">
@@ -330,13 +330,12 @@ export function FlowPilotSession({
{/* Paused banner */}
{session.status === 'paused' && onResume && (
<div
className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between border-t px-3 py-3 sm:px-5"
style={{ borderColor: 'var(--glass-border)', background: 'rgba(16, 17, 20, 0.8)', backdropFilter: 'blur(12px)' }}
className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between border-t border-border bg-card px-3 py-3 sm:px-5"
>
<span className="text-sm text-muted-foreground">Session paused</span>
<button
onClick={onResume}
className="flex items-center gap-2 rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
className="flex items-center gap-2 rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
>
<Play size={14} />
Resume Session

View File

@@ -62,7 +62,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
return (
<button
onClick={() => setIsCollapsed(false)}
className="w-full text-left glass-card-static p-3 sm:p-4 opacity-70 hover:opacity-90 transition-opacity"
className="w-full text-left card-flat p-3 sm:p-4 opacity-70 hover:opacity-90 transition-opacity"
>
<div className="flex items-center gap-3">
<Icon size={16} className="shrink-0 text-muted-foreground" />
@@ -76,14 +76,14 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
// Expanded completed step
if (!isCurrentStep && !isCollapsed) {
return (
<div className="glass-card-static p-3 sm:p-4 opacity-80">
<div className="card-flat p-3 sm:p-4 opacity-80">
<button
onClick={() => setIsCollapsed(true)}
className="mb-2 flex w-full items-center justify-between text-left"
>
<div className="flex items-center gap-2">
<Icon size={16} className="text-muted-foreground" />
<span className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground">
Step {step.step_order + 1}
</span>
</div>
@@ -98,7 +98,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
return (
<div
className={cn(
'glass-card-static p-3 sm:p-4 lg:p-5',
'card-flat p-3 sm:p-4 lg:p-5',
isResolutionSuggestion && 'border-emerald-500/30'
)}
>
@@ -113,7 +113,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
<div className="flex items-start gap-3 mb-4">
<span className={cn(
'mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg',
isResolutionSuggestion ? 'bg-emerald-500/10 text-emerald-400' : 'bg-primary/10 text-primary'
isResolutionSuggestion ? 'bg-emerald-500/10 text-emerald-400' : 'bg-accent-dim text-primary'
)}>
<Icon size={14} />
</span>
@@ -180,7 +180,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
window.open('/script-builder?from=flowpilot', '_blank')
onRespond({ action_result: { success: true, details: 'Opened Script Builder' } })
}}
className="flex-1 min-h-[44px] rounded-lg bg-gradient-brand px-4 py-2.5 text-sm font-semibold text-[#101114] hover:opacity-90 active:scale-[0.97] transition-all"
className="flex-1 min-h-[44px] rounded-lg bg-primary text-white px-4 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
>
Open Script Builder
</button>
@@ -191,7 +191,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
<div className="flex flex-col gap-2 sm:flex-row">
<button
onClick={() => handleActionComplete(true)}
className="flex-1 min-h-[44px] rounded-lg bg-primary/10 border border-primary/20 px-4 py-2.5 text-sm font-medium text-primary hover:bg-primary/20 transition-colors"
className="flex-1 min-h-[44px] rounded-lg bg-accent-dim border border-primary/20 px-4 py-2.5 text-sm font-medium text-primary hover:bg-primary/20 transition-colors"
>
I've completed this action
</button>
@@ -208,7 +208,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
{!isResolutionSuggestion && step.allow_skip && (
<button
onClick={handleSkip}
className="flex items-center gap-1.5 text-xs text-[#5a6170] hover:text-muted-foreground transition-colors"
className="flex items-center gap-1.5 text-xs text-text-muted hover:text-muted-foreground transition-colors"
>
<SkipForward size={12} />
I can't check this right now

View File

@@ -66,7 +66,7 @@ export function InSessionScriptGenerator({
<div className="mt-3 rounded-xl border border-primary/20 bg-primary/5 p-3 sm:p-4 space-y-3">
<div className="flex items-center gap-2">
<Terminal size={14} className="text-primary" />
<span className="font-label text-[0.625rem] uppercase tracking-wider text-primary">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-primary">
Script Generator
</span>
</div>
@@ -106,7 +106,7 @@ export function InSessionScriptGenerator({
<button
onClick={handleGenerate}
disabled={isGenerating}
className="w-full min-h-[44px] flex items-center justify-center gap-2 rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 transition-all"
className="w-full min-h-[44px] flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
>
{isGenerating ? (
<Loader2 size={14} className="animate-spin" />
@@ -141,7 +141,7 @@ export function InSessionScriptGenerator({
<div className="flex flex-col gap-2 pt-1 sm:flex-row">
<button
onClick={() => handleContinue(true)}
className="flex-1 min-h-[44px] rounded-lg bg-primary/10 border border-primary/20 px-4 py-2 text-sm font-medium text-primary hover:bg-primary/20 transition-colors"
className="flex-1 min-h-[44px] rounded-lg bg-accent-dim border border-primary/20 px-4 py-2 text-sm font-medium text-primary hover:bg-primary/20 transition-colors"
>
Script worked continue
</button>

View File

@@ -24,12 +24,12 @@ export function ProposalCard({ proposal, isSelected, onClick }: ProposalCardProp
className={`w-full text-left rounded-xl border p-3 space-y-2 transition-all ${
isSelected
? 'border-primary/30 bg-primary/5'
: 'border-[rgba(255,255,255,0.06)] bg-[rgba(24,26,31,0.55)] hover:border-[rgba(255,255,255,0.12)]'
: 'border-[rgba(255,255,255,0.06)] bg-card hover:border-[rgba(255,255,255,0.12)]'
}`}
>
<div className="flex items-start justify-between gap-2">
<p className="text-sm font-semibold text-foreground line-clamp-2">{proposal.title}</p>
<span className={`shrink-0 flex items-center gap-1 rounded-md border px-1.5 py-0.5 font-label text-[0.5625rem] uppercase tracking-wider ${typeConfig.color}`}>
<span className={`shrink-0 flex items-center gap-1 rounded-md border px-1.5 py-0.5 font-sans text-xs text-[0.5625rem] uppercase tracking-wider ${typeConfig.color}`}>
<TypeIcon size={10} />
{typeConfig.label}
</span>
@@ -41,7 +41,7 @@ export function ProposalCard({ proposal, isSelected, onClick }: ProposalCardProp
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{proposal.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
<span className="font-sans text-xs rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
{proposal.problem_domain}
</span>
)}

View File

@@ -67,7 +67,7 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
)}
<div className="mt-3 flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
{proposal.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
{proposal.problem_domain}
</span>
)}
@@ -89,8 +89,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Content */}
<div className="flex-1 overflow-y-auto p-6 space-y-5">
{/* Source session link */}
<div className="glass-card-static p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Source Session</h4>
<div className="card-flat p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Source Session</h4>
<Link
to={`/pilot/${proposal.source_session_id}`}
target="_blank"
@@ -105,8 +105,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{proposal.proposed_diff && (() => {
const diff = proposal.proposed_diff as { diff_description?: string; new_nodes?: Array<{ title?: string; question?: string; description?: string }> }
return (
<div className="glass-card-static border-l-2 border-l-amber-500 p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Proposed Changes</h4>
<div className="card-flat border-l-2 border-l-amber-500 p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Proposed Changes</h4>
{diff.diff_description && (
<p className="text-sm text-foreground">{diff.diff_description}</p>
)}
@@ -126,10 +126,10 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
})()}
{/* Flow data preview */}
<div className="glass-card-static p-4">
<div className="card-flat p-4">
<button
onClick={() => setShowFlowData(!showFlowData)}
className="flex items-center gap-1.5 font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] hover:text-foreground transition-colors"
className="flex items-center gap-1.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted hover:text-foreground transition-colors"
>
{showFlowData ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
Flow Data (JSON)
@@ -143,8 +143,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Supporting sessions */}
{proposal.supporting_session_ids.length > 1 && (
<div className="glass-card-static p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">
<div className="card-flat p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">
Supporting Sessions ({proposal.supporting_session_ids.length})
</h4>
<div className="space-y-1">
@@ -164,8 +164,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Review info (for already-reviewed proposals) */}
{proposal.reviewed_at && (
<div className="glass-card-static p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Review</h4>
<div className="card-flat p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Review</h4>
<p className="text-sm text-foreground">
<span className="capitalize">{proposal.status}</span> on{' '}
{new Date(proposal.reviewed_at).toLocaleString()}
@@ -180,8 +180,7 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Review actions bar */}
{canReview && (
<div
className="border-t px-5 py-3 space-y-3"
style={{ borderColor: 'var(--glass-border)', background: 'rgba(16, 17, 20, 0.8)', backdropFilter: 'blur(12px)' }}
className="border-t border-border bg-card px-5 py-3 space-y-3"
>
{/* Notes input */}
<input

View File

@@ -38,7 +38,7 @@ export function SessionBriefing({
const pkg = escalationPackage
return (
<div className="glass-card-static border-l-2 border-l-amber-500 p-5 space-y-4">
<div className="card-flat border-l-2 border-l-amber-500 p-5 space-y-4">
<div>
<h3 className="font-heading text-base font-semibold text-foreground">
Escalation from {originalEngineerName || 'another engineer'}
@@ -51,7 +51,7 @@ export function SessionBriefing({
{/* Problem */}
{pkg.problem_summary && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Problem</h4>
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Problem</h4>
<p className="text-sm text-foreground">{pkg.problem_summary}</p>
</div>
)}
@@ -59,7 +59,7 @@ export function SessionBriefing({
{/* Escalation reason */}
{pkg.escalation_reason && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Why escalated</h4>
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Why escalated</h4>
<p className="text-sm text-amber-400">{pkg.escalation_reason}</p>
</div>
)}
@@ -69,7 +69,7 @@ export function SessionBriefing({
<div>
<button
onClick={() => setShowSteps(!showSteps)}
className="flex items-center gap-1.5 font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] hover:text-foreground transition-colors"
className="flex items-center gap-1.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted hover:text-foreground transition-colors"
>
{showSteps ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
Steps taken ({pkg.steps_tried.length})
@@ -92,7 +92,7 @@ export function SessionBriefing({
{/* Remaining hypotheses */}
{pkg.remaining_hypotheses && pkg.remaining_hypotheses.length > 0 && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Remaining hypotheses</h4>
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Remaining hypotheses</h4>
<ul className="space-y-1">
{pkg.remaining_hypotheses.map((h, i) => (
<li key={i} className="text-sm text-foreground flex items-start gap-2">
@@ -107,7 +107,7 @@ export function SessionBriefing({
{/* Suggested next steps */}
{pkg.suggested_next_steps && pkg.suggested_next_steps.length > 0 && (
<div>
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Suggested next steps</h4>
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Suggested next steps</h4>
<ul className="space-y-1">
{pkg.suggested_next_steps.map((s, i) => (
<li key={i} className="text-sm text-foreground flex items-start gap-2">
@@ -125,7 +125,7 @@ export function SessionBriefing({
<button
onClick={onContinue}
disabled={isProcessing}
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-gradient-brand px-4 py-2.5 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 transition-all"
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
>
<ArrowRight size={14} />
Continue Where They Left Off
@@ -160,7 +160,7 @@ export function SessionBriefing({
<button
onClick={() => freshContext.trim() && onFresh(freshContext.trim())}
disabled={!freshContext.trim() || isProcessing}
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 transition-all"
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
>
<ArrowRight size={14} />
Start Diagnosis

View File

@@ -107,9 +107,9 @@ export function SessionDocView({
)}
{/* Header */}
<div className="glass-card-static p-3 sm:p-4 lg:p-5">
<div className="card-flat p-3 sm:p-4 lg:p-5">
<div className="flex items-start gap-3">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-accent-dim text-primary">
<FileText size={16} />
</span>
<div className="flex-1">
@@ -117,7 +117,7 @@ export function SessionDocView({
<p className="mt-1 text-sm text-muted-foreground">{documentation.problem_summary}</p>
<div className="mt-2 flex items-center gap-3 flex-wrap">
{documentation.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
{documentation.problem_domain}
</span>
)}
@@ -137,14 +137,14 @@ export function SessionDocView({
{/* Outcome */}
{(documentation.resolution_summary || documentation.escalation_reason) && (
<div className={`glass-card-static p-4 border-l-2 ${documentation.resolution_summary ? 'border-l-emerald-500' : 'border-l-amber-500'}`}>
<div className={`card-flat p-4 border-l-2 ${documentation.resolution_summary ? 'border-l-emerald-500' : 'border-l-amber-500'}`}>
<div className="flex items-center gap-2 mb-1">
{documentation.resolution_summary ? (
<CheckCircle2 size={14} className="text-emerald-400" />
) : (
<ArrowUpRight size={14} className="text-amber-400" />
)}
<span className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground">
{documentation.resolution_summary ? 'Resolved' : 'Escalated'}
</span>
</div>
@@ -155,8 +155,8 @@ export function SessionDocView({
)}
{/* Intake summary */}
<div className="glass-card-static p-3 sm:p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground mb-2">
<div className="card-flat p-3 sm:p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground mb-2">
Original intake
</h4>
<p className="text-sm text-foreground whitespace-pre-wrap">{documentation.intake_summary}</p>
@@ -164,13 +164,13 @@ export function SessionDocView({
{/* Diagnostic steps */}
<div className="space-y-2">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground px-1">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground px-1">
Diagnostic trail
</h4>
{documentation.diagnostic_steps.map((step) => (
<div key={step.step_number} className="glass-card-static p-3 sm:p-4">
<div key={step.step_number} className="card-flat p-3 sm:p-4">
<div className="flex items-start gap-3">
<span className="font-label flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-card text-[0.625rem] text-muted-foreground border border-border">
<span className="font-sans text-xs flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-card text-[0.625rem] text-muted-foreground border border-border">
{step.step_number}
</span>
<div className="flex-1">
@@ -189,7 +189,7 @@ export function SessionDocView({
{/* Rating */}
{onRate && (
<div className="glass-card-static p-3 sm:p-4 text-center">
<div className="card-flat p-3 sm:p-4 text-center">
<p className="text-sm text-muted-foreground mb-2">How helpful was this session?</p>
<div className="flex items-center justify-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (

View File

@@ -36,7 +36,7 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
return (
<div className="rounded-xl border border-primary/20 bg-primary/5 p-3 space-y-2">
<div className="flex items-start justify-between">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170]">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted">
Linked Ticket
</h4>
{ticketUrl && (
@@ -68,13 +68,13 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
)}
{ticket?.priority && (
<>
<span className="text-[#5a6170]">&bull;</span>
<span className="text-text-muted">&bull;</span>
<span>{ticket.priority}</span>
</>
)}
{ticket?.status && (
<>
<span className="text-[#5a6170]">&bull;</span>
<span className="text-text-muted">&bull;</span>
<span>{ticket.status}</span>
</>
)}
@@ -82,7 +82,7 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
{configs && configs.length > 0 && (
<div className="border-t border-border/50 pt-2 mt-2">
<p className="font-label text-[0.5625rem] uppercase tracking-wider text-[#5a6170] mb-1">
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-wider text-text-muted mb-1">
Devices
</p>
<div className="space-y-0.5">
@@ -90,11 +90,11 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
<div key={i} className="flex items-center gap-1.5 text-xs text-muted-foreground">
<Cpu size={10} />
<span>{cfg.device_identifier}</span>
{cfg.type && <span className="text-[#5a6170]">({cfg.type})</span>}
{cfg.type && <span className="text-text-muted">({cfg.type})</span>}
</div>
))}
{configs.length > 3 && (
<p className="text-[0.625rem] text-[#5a6170]">
<p className="text-[0.625rem] text-text-muted">
+{configs.length - 3} more
</p>
)}

View File

@@ -36,7 +36,7 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
return (
<div className="flex items-center gap-1.5 py-1">
<Loader2 size={10} className="animate-spin text-muted-foreground" />
<span className="text-[0.625rem] text-muted-foreground font-label">Loading similar sessions</span>
<span className="text-[0.625rem] text-muted-foreground font-sans text-xs">Loading similar sessions</span>
</div>
)
}
@@ -47,20 +47,20 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
return (
<div className="space-y-2">
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Similar Past Sessions
</h4>
{sessions.map((session) => (
<Link
key={session.id}
to={`/pilot/${session.id}`}
className="glass-card p-3 block hover:border-[rgba(255,255,255,0.12)] transition-all"
className="card-interactive p-3 block hover:border-[rgba(255,255,255,0.12)] transition-all"
>
<div className="flex items-start justify-between gap-2">
<p className="text-xs text-foreground line-clamp-2">
{session.problem_summary || 'Untitled session'}
</p>
<span className="text-[0.625rem] font-label text-primary shrink-0">
<span className="text-[0.625rem] font-sans text-xs text-primary shrink-0">
{Math.round(session.similarity * 100)}%
</span>
</div>
@@ -71,13 +71,13 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
)}
<div className="flex items-center gap-2 mt-1.5">
{session.problem_domain && (
<span className="text-[0.5rem] font-label uppercase tracking-wider text-muted-foreground/70">
<span className="text-[0.5rem] font-sans text-xs uppercase tracking-wider text-muted-foreground/70">
{session.problem_domain}
</span>
)}
<span
className={cn(
'text-[0.5rem] font-label uppercase',
'text-[0.5rem] font-sans text-xs uppercase',
session.status === 'resolved'
? 'text-emerald-400'
: session.status === 'escalated'

View File

@@ -11,7 +11,7 @@ export function GuideCard({ guide }: GuideCardProps) {
return (
<Link
to={`/guides/${guide.slug}`}
className="glass-card block rounded-2xl p-5 transition-all"
className="card-interactive block rounded-lg p-5 transition-all"
>
<div className="flex items-start gap-3.5">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10">
@@ -24,7 +24,7 @@ export function GuideCard({ guide }: GuideCardProps) {
<p className="text-xs text-muted-foreground leading-relaxed">
{guide.summary}
</p>
<span className="mt-2 inline-block font-label text-[0.625rem] uppercase tracking-widest text-primary">
<span className="mt-2 inline-block font-sans text-[0.625rem] uppercase tracking-widest text-primary">
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
</span>
</div>

View File

@@ -18,7 +18,7 @@ export function GuideSection({ section, index }: GuideSectionProps) {
<ol className="space-y-3 pl-8">
{section.steps.map((step, i) => (
<li key={i} className="relative">
<span className="absolute -left-6 top-0.5 font-label text-[0.625rem] text-muted-foreground">
<span className="absolute -left-6 top-0.5 font-sans text-[0.625rem] text-muted-foreground">
{i + 1}.
</span>
<p

View File

@@ -55,7 +55,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
return (
<div
className={cn(
'glass-card-static border-l-4 p-4 transition-all',
'card-flat border-l-4 p-4 transition-all',
confidenceColor(node.confidence_score),
node.user_approved && 'opacity-75',
)}
@@ -66,17 +66,17 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
{node.node_type}
</span>
<span className={cn('font-label text-[0.625rem]', confidenceTextColor(node.confidence_score))}>
<span className={cn('font-sans text-xs text-[0.625rem]', confidenceTextColor(node.confidence_score))}>
{confidenceLabel(node.confidence_score)} ({Math.round(node.confidence_score * 100)}%)
</span>
{node.user_approved && (
<span className="font-label text-[0.625rem] text-emerald-400">Approved</span>
<span className="font-sans text-xs text-[0.625rem] text-emerald-400">Approved</span>
)}
{node.user_edited && (
<span className="font-label text-[0.625rem] text-blue-400">Edited</span>
<span className="font-sans text-xs text-[0.625rem] text-blue-400">Edited</span>
)}
</div>
@@ -92,7 +92,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<button
onClick={() => handleAction('edit', { content: { ...node.content, question: editContent, content: editContent } })}
disabled={busy}
className="px-3 py-1.5 text-xs font-medium rounded-md bg-gradient-brand text-[#101114] hover:opacity-90"
className="px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-white hover:brightness-110"
>
Save
</button>
@@ -143,7 +143,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<button
onClick={() => handleAction('regenerate')}
disabled={busy}
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-primary/10 transition-colors"
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-accent-dim transition-colors"
title="Regenerate"
>
<RotateCcw size={14} />
@@ -151,7 +151,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<button
onClick={() => handleAction('insert_after', { content: { question: 'New node', type: node.node_type } })}
disabled={busy}
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-primary/10 transition-colors"
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-accent-dim transition-colors"
title="Insert after"
>
<Plus size={14} />
@@ -182,7 +182,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<div className="mt-2 space-y-1 pl-3 border-l border-border">
{options.map((opt, i) => (
<p key={i} className="text-xs text-muted-foreground">
{opt.label} {opt.next_node_id && <span className="text-[#5a6170]"> {opt.next_node_id}</span>}
{opt.label} {opt.next_node_id && <span className="text-text-muted"> {opt.next_node_id}</span>}
</p>
))}
</div>
@@ -192,7 +192,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
{/* Source excerpt */}
{node.source_excerpt && (
<p className="mt-2 text-xs text-[#5a6170] italic truncate" title={node.source_excerpt}>
<p className="mt-2 text-xs text-text-muted italic truncate" title={node.source_excerpt}>
Source: {node.source_excerpt}
</p>
)}

View File

@@ -30,7 +30,7 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
return (
<div className="flex flex-col h-full gap-4">
{/* Header */}
<div className="glass-card-static p-4 flex flex-wrap items-center justify-between gap-3">
<div className="card-flat p-4 flex flex-wrap items-center justify-between gap-3">
<div>
<h2 className="text-lg font-heading font-semibold text-foreground">{flowTitle}</h2>
{flowDescription && (
@@ -88,10 +88,10 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
</div>
{/* Nodes panel */}
<div className="flex flex-col glass-card-static overflow-hidden">
<div className="flex flex-col card-flat overflow-hidden">
<div className="flex items-center gap-2 px-4 py-3 border-b" style={{ borderColor: 'var(--glass-border)' }}>
<span className="text-sm font-medium text-foreground">Generated Flow</span>
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground ml-auto">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground ml-auto">
{kbImport.target_type === 'troubleshooting' ? 'Troubleshooting' : 'Project'}
</span>
</div>
@@ -119,7 +119,7 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
onClick={onDiscard}
disabled={loading}
className={cn(
'px-4 py-2.5 rounded-[10px] text-sm font-medium transition-colors',
'px-4 py-2.5 rounded-lg text-sm font-medium transition-colors',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-muted-foreground',
'hover:text-foreground hover:border-[rgba(255,255,255,0.12)]',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -131,9 +131,9 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
onClick={() => onCommit()}
disabled={loading || nodes.length === 0}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
'hover:opacity-90 active:scale-[0.97]',
'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-semibold transition-all',
'bg-primary text-white',
'hover:brightness-110 active:scale-[0.98]',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>

View File

@@ -24,11 +24,11 @@ export function SourcePanel({ sourceText, sourceFormat, highlightExcerpt }: Sour
}, [sourceText, highlightExcerpt])
return (
<div className="glass-card-static flex flex-col h-full">
<div className="card-flat flex flex-col h-full">
<div className="flex items-center gap-2 px-4 py-3 border-b" style={{ borderColor: 'var(--glass-border)' }}>
<FileText size={16} className="text-muted-foreground" />
<span className="text-sm font-medium text-foreground">Source Document</span>
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground ml-auto">
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground ml-auto">
{sourceFormat}
</span>
</div>

View File

@@ -31,9 +31,9 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
<button
onClick={onViewFlow}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
'hover:opacity-90 active:scale-[0.97]'
'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-semibold transition-all',
'bg-primary text-white',
'hover:brightness-110 active:scale-[0.98]'
)}
>
View Flow
@@ -42,7 +42,7 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
<button
onClick={onConvertAnother}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-medium transition-colors',
'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-medium transition-colors',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
'hover:border-[rgba(255,255,255,0.12)]'
)}

View File

@@ -82,7 +82,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
<div className="max-w-3xl mx-auto space-y-6">
{/* Quota info */}
{quota && (
<div className="glass-card-static p-4 flex items-center justify-between">
<div className="card-flat p-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<Sparkles size={18} className="text-primary" />
<div>
@@ -110,9 +110,9 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
<button
onClick={() => setMode('paste')}
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-[10px] text-sm font-medium transition-colors',
'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors',
mode === 'paste'
? 'bg-primary/10 text-foreground border border-primary/30'
? 'bg-accent-dim text-foreground border border-primary/30'
: 'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
)}
>
@@ -123,9 +123,9 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
<button
onClick={() => setMode('file')}
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-[10px] text-sm font-medium transition-colors',
'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors',
mode === 'file'
? 'bg-primary/10 text-foreground border border-primary/30'
? 'bg-accent-dim text-foreground border border-primary/30'
: 'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
)}
>
@@ -136,11 +136,11 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
</div>
{/* Content area */}
<div className="glass-card-static p-5 space-y-4">
<div className="card-flat p-5 space-y-4">
{mode === 'paste' ? (
<>
<div>
<label htmlFor="kb-title" className="block font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-1.5">
<label htmlFor="kb-title" className="block font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-1.5">
Title (optional)
</label>
<Input
@@ -152,7 +152,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
/>
</div>
<div>
<label htmlFor="kb-content" className="block font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-1.5">
<label htmlFor="kb-content" className="block font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-1.5">
KB Article Content
</label>
<Textarea
@@ -223,7 +223,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
{/* Target type selector */}
<div>
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-2">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-2">
Target Flow Type
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
@@ -232,7 +232,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
key={t.value}
onClick={() => setTargetType(t.value)}
className={cn(
'glass-card-static p-4 text-left transition-all',
'card-flat p-4 text-left transition-all',
targetType === t.value
? 'border-primary/30 bg-primary/5'
: 'hover:border-[rgba(255,255,255,0.12)]'
@@ -250,9 +250,9 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
onClick={handleSubmit}
disabled={!canSubmit || loading || (quota != null && !quota.can_convert)}
className={cn(
'w-full flex items-center justify-center gap-2 px-6 py-3 rounded-[10px] text-sm font-semibold transition-all',
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
'hover:opacity-90 active:scale-[0.97]',
'w-full flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold transition-all',
'bg-primary text-white',
'hover:brightness-110 active:scale-[0.98]',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>

View File

@@ -1,6 +1,6 @@
import { useEffect, useState, useCallback } from 'react'
import { useLocation, useNavigate, Link } from 'react-router-dom'
import { Menu, X, LayoutGrid, Clock, Network, AlertTriangle, Code2, Wand2, BarChart3, Settings, LogOut, Shield, Library } from 'lucide-react'
import { Menu, X, LayoutGrid, Clock, AlertTriangle, GitBranch, Code2, Wand2, BarChart3, Settings, LogOut, Shield, Layers } from 'lucide-react'
import { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
@@ -16,7 +16,7 @@ export function AppLayout() {
const navigate = useNavigate()
const { user, logout } = useAuthStore()
const { effectiveRole } = usePermissions()
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
const sidebarPinned = useUserPreferencesStore(s => s.sidebarPinned)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
// Close mobile menu on route change
@@ -54,8 +54,8 @@ export function AppLayout() {
{ path: '/', label: 'Dashboard', icon: LayoutGrid },
{ path: '/sessions', label: 'Active Sessions', icon: Clock },
{ path: '/escalations', label: 'Escalations', icon: AlertTriangle },
{ path: '/trees', label: 'Flows', icon: Network },
{ path: '/step-library', label: 'Step Library', icon: Library },
{ path: '/trees', label: 'Flows', icon: GitBranch },
{ path: '/step-library', label: 'Step Library', icon: Layers },
{ path: '/scripts', label: 'Scripts', icon: Code2 },
{ path: '/script-builder', label: 'Script Builder', icon: Wand2 },
{ path: '/analytics', label: 'Analytics', icon: BarChart3 },
@@ -64,46 +64,21 @@ export function AppLayout() {
return (
<>
{/* Atmosphere orbs — ambient light behind glass */}
<div
className="pointer-events-none fixed z-0"
style={{
top: '-120px',
right: '-80px',
width: '600px',
height: '600px',
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(6, 182, 212, 0.15) 0%, rgba(6, 182, 212, 0.04) 40%, transparent 70%)',
filter: 'blur(60px)',
}}
/>
<div
className="pointer-events-none fixed z-0"
style={{
bottom: '-100px',
left: '-60px',
width: '500px',
height: '500px',
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(99, 102, 241, 0.08) 0%, rgba(99, 102, 241, 0.02) 40%, transparent 70%)',
filter: 'blur(50px)',
}}
/>
<div
className={cn('app-shell relative z-1', sidebarCollapsed && 'app-shell--collapsed')}
className={cn('app-shell relative z-1', sidebarPinned && 'app-shell--pinned')}
data-testid="app-shell"
>
{/* Top Bar - spans full width */}
<TopBar />
{/* Sidebar - desktop only */}
<div className="hidden md:block">
{/* Sidebar - desktop only, must fill grid row */}
<div className="hidden md:flex md:flex-col md:min-h-0 md:h-full">
<Sidebar />
</div>
{/* Mobile hamburger - overlaid on topbar */}
<button
type="button"
onClick={() => setMobileMenuOpen(true)}
className="fixed left-4 top-3.5 z-50 rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors md:hidden"
aria-label="Open menu"
@@ -119,15 +94,17 @@ export function AppLayout() {
onClick={() => setMobileMenuOpen(false)}
aria-hidden="true"
/>
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-[var(--sidebar-bg)] shadow-2xl animate-slide-in-left">
<div className="flex h-14 items-center justify-between border-b border-border px-4">
<nav
className="absolute inset-y-0 left-0 w-72 shadow-2xl animate-slide-in-left"
style={{ background: 'var(--color-bg-sidebar)', borderRight: '1px solid var(--color-border-default)' }}
>
<div className="flex h-14 items-center justify-between px-4" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
<Link to="/" className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-brand">
<BrandLogo size="sm" className="h-4 w-4" />
</div>
<span className="text-sm font-heading font-bold">ResolutionFlow</span>
<BrandLogo size="sm" />
<span className="text-sm font-heading font-bold text-text-heading">ResolutionFlow</span>
</Link>
<button
type="button"
onClick={() => setMobileMenuOpen(false)}
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground"
aria-label="Close menu"
@@ -138,7 +115,7 @@ export function AppLayout() {
<div className="flex flex-col p-3">
{/* User info */}
<div className="mb-3 border-b border-border pb-3 px-3">
<div className="mb-3 pb-3 px-3" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
<p className="text-sm font-medium text-foreground">{user?.name || user?.email}</p>
{effectiveRole && effectiveRole !== 'engineer' && (
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
@@ -162,8 +139,8 @@ export function AppLayout() {
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
isActive
? 'bg-[var(--sidebar-active)] text-foreground'
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
? 'bg-accent-dim text-foreground'
: 'text-muted-foreground hover:bg-input hover:text-foreground'
)}
>
<Icon size={18} />
@@ -174,10 +151,11 @@ export function AppLayout() {
</div>
{/* Logout */}
<div className="mt-3 border-t border-border pt-3">
<div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--color-border-default)' }}>
<button
type="button"
onClick={handleLogout}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground transition-colors"
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-muted-foreground hover:bg-input hover:text-foreground transition-colors"
>
<LogOut size={18} />
Logout

View File

@@ -329,7 +329,7 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
placeholder="Search flows, ask a question, navigate…"
className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-hidden"
/>
<kbd className="rounded border border-border bg-background px-1.5 py-0.5 font-label text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1.5 py-0.5 font-mono text-[0.625rem] text-muted-foreground">
ESC
</kbd>
</div>
@@ -354,7 +354,7 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
<div key={group.type}>
{/* Section label */}
<div className="px-3 pt-2 pb-1">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-sans text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
{group.label}
</span>
</div>
@@ -451,11 +451,11 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
{flatItems.length > 0 && (
<div className="flex items-center gap-4 border-t border-border px-4 py-2">
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
<kbd className="rounded border border-border bg-background px-1 py-px font-mono"></kbd>
Navigate
</span>
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
<kbd className="rounded border border-border bg-background px-1 py-px font-mono"></kbd>
Open
</span>
</div>

View File

@@ -48,7 +48,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
title={label}
>
{isActive && (
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-gradient-brand" />
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-primary" />
)}
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} style={iconColor ? { color: iconColor } : undefined} />
{badge !== undefined && badge !== 0 && badge !== 'dot' && (
@@ -76,7 +76,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
>
{/* Active indicator bar */}
{isActive && !isParentDimmed && (
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-gradient-brand" />
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-primary" />
)}
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} style={iconColor ? { color: iconColor } : undefined} />
@@ -87,7 +87,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
badge === 'dot' ? (
<span className="ml-auto h-1.5 w-1.5 shrink-0 rounded-full bg-brand-gradient-from" />
) : (
<span className="ml-auto shrink-0 rounded-full bg-card border border-border px-2 text-[0.6875rem] font-label text-muted-foreground">
<span className="ml-auto shrink-0 rounded-full bg-card border border-border px-2 text-[0.6875rem] font-mono text-muted-foreground">
{badge}
</span>
)
@@ -117,7 +117,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
>
<span className="truncate">{child.label}</span>
{child.count !== undefined && (
<span className="ml-auto shrink-0 rounded-full bg-card border border-border px-2 text-[0.6875rem] font-label text-muted-foreground">
<span className="ml-auto shrink-0 rounded-full bg-card border border-border px-2 text-[0.6875rem] font-mono text-muted-foreground">
{child.count}
</span>
)}

View File

@@ -144,11 +144,11 @@ export function QuickLaunch({ open, onClose }: QuickLaunchProps) {
<div className="flex items-center gap-4 border-t border-border px-4 py-2">
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
<kbd className="rounded border border-border bg-background px-1 py-px font-mono"></kbd>
Navigate
</span>
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
<kbd className="rounded border border-border bg-background px-1 py-px font-mono"></kbd>
Open
</span>
</div>

View File

@@ -1,175 +1,497 @@
import { useCallback, useEffect, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { useCallback, useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'
import { Link, useLocation } from 'react-router-dom'
import type { LucideIcon } from 'lucide-react'
import {
LayoutGrid, Network, Clock, FileOutput, BarChart3, TrendingUp,
Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText, ListChecks,
BookOpen, Code2, Library, AlertTriangle, Wand2,
LayoutGrid, Clock, AlertTriangle, GitBranch, Layers, Code2, Wand2,
ListChecks, Download, BarChart3, Rocket,
Settings, Pin, PinOff,
Zap, Database, HelpCircle,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { sidebarApi } from '@/api'
import type { SidebarStatsResponse } from '@/api/sidebar'
import { NavItem } from './NavItem'
import { prefetchForRoute } from '@/lib/routePrefetch'
// Semantic icon colors — each nav item gets a unique color for visual landmarks
const NAV_COLORS = {
dashboard: '#22d3ee', // cyan-400
flows: '#a78bfa', // violet-400
sessions: '#34d399', // emerald-400
exports: '#60a5fa', // blue-400
stepLib: '#fb923c', // orange-400
scripts: '#2dd4bf', // teal-400
scriptBuilder: '#e879f9', // fuchsia-400
analytics: '#38bdf8', // sky-400
guides: '#a3e635', // lime-400
feedback: '#818cf8', // indigo-400
} as const
/* ── Types ──────────────────────────────────────────── */
interface NavSubItem {
href: string
label: string
count?: number
}
interface NavEntry {
href: string
icon: LucideIcon
label: string
shortLabel: string
badge?: number
matchPaths?: string[]
children?: NavSubItem[]
}
interface NavSection {
title: string
items: NavEntry[]
}
/* ── Sidebar component ──────────────────────────────── */
export function Sidebar() {
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar)
const location = useLocation()
const sidebarPinned = useUserPreferencesStore(s => s.sidebarPinned)
const toggleSidebarPinned = useUserPreferencesStore(s => s.toggleSidebarPinned)
const [stats, setStats] = useState<SidebarStatsResponse | null>(null)
const [flyoutIndex, setFlyoutIndex] = useState<string | null>(null)
const flyoutTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
const sidebarRef = useRef<HTMLElement>(null)
/* ── Stats fetching ───────────────────────────────── */
const refreshStats = useCallback(() => {
sidebarApi.getStats().then(setStats).catch(() => {})
}, [])
// Fetch sidebar stats — refreshes on navigation
useEffect(() => {
refreshStats()
}, [location.pathname, refreshStats])
useEffect(() => { refreshStats() }, [location.pathname, refreshStats])
// Refresh when sessions are created or completed
useEffect(() => {
window.addEventListener('session-changed', refreshStats)
return () => window.removeEventListener('session-changed', refreshStats)
}, [refreshStats])
const handleSidebarWheel = (e: React.WheelEvent<HTMLElement>) => {
const sidebar = e.currentTarget
const canSidebarScroll = sidebar.scrollHeight > sidebar.clientHeight
const atTop = sidebar.scrollTop <= 0
const atBottom = sidebar.scrollTop + sidebar.clientHeight >= sidebar.scrollHeight - 1
/* ── Navigation data ──────────────────────────────── */
// If sidebar can't consume wheel movement, forward it to main content scroller.
if (!canSidebarScroll || (e.deltaY < 0 && atTop) || (e.deltaY > 0 && atBottom)) {
const main = document.querySelector('.main-content') as HTMLElement | null
if (main) {
main.scrollTop += e.deltaY
e.preventDefault()
}
}
}
/* ── Grouped nav: 5 top-level icons (Sentry-style) ── */
return (
<nav
className="sidebar flex flex-col border-r"
style={{
background: 'rgba(16, 17, 20, 0.5)',
backdropFilter: 'var(--glass-blur-light)',
WebkitBackdropFilter: 'var(--glass-blur-light)',
borderColor: 'var(--glass-border)',
}}
onWheel={handleSidebarWheel}
>
{sidebarCollapsed ? (
<>
{/* Collapsed: icon-only nav */}
<div className="flex flex-col items-center px-1.5 py-3 space-y-1">
<NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} collapsed />
<NavItem href="/sessions" icon={Clock} label="Active Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} collapsed />
<NavItem href="/escalations" icon={AlertTriangle} label="Escalations" badge={stats?.escalation_count || undefined} iconColor="#fbbf24" collapsed />
<NavItem href="/trees" icon={Network} label="Flows" matchPaths={['/trees', '/flows', '/my-trees']} iconColor={NAV_COLORS.flows} collapsed />
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} collapsed />
<NavItem href="/scripts" icon={Code2} label="Scripts" iconColor={NAV_COLORS.scripts} collapsed />
<NavItem href="/script-builder" icon={Wand2} label="Script Builder" iconColor={NAV_COLORS.scriptBuilder} collapsed />
<NavItem href="/review-queue" icon={ListChecks} label="Review Queue" iconColor="#fbbf24" collapsed />
<NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} collapsed />
<NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} collapsed />
<NavItem href="/analytics/flowpilot" icon={TrendingUp} label="FlowPilot Analytics" iconColor="#2dd4bf" collapsed />
<NavItem href="/guides" icon={BookOpen} label="User Guides" iconColor={NAV_COLORS.guides} collapsed />
<NavItem href="/feedback" icon={MessageSquareText} label="Feedback" iconColor={NAV_COLORS.feedback} collapsed />
</div>
</>
) : (
<>
{/* Navigation */}
<div className="px-3 py-2 space-y-0.5">
{/* Dashboard */}
<NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} />
{/* Resolve */}
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
Resolve
</div>
<NavItem href="/sessions" icon={Clock} label="Active Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} matchPaths={['/sessions']} />
<NavItem href="/escalations" icon={AlertTriangle} label="Escalations" badge={stats?.escalation_count || undefined} iconColor="#fbbf24" />
{/* Knowledge */}
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
Knowledge
</div>
<NavItem
href="/trees"
icon={Network}
label="Flows"
badge={stats?.tree_counts.total || undefined}
iconColor={NAV_COLORS.flows}
matchPaths={['/trees', '/flows', '/my-trees']}
children={[
const railGroups: NavEntry[] = [
{
href: '/', icon: LayoutGrid, label: 'Home', shortLabel: 'Home',
matchPaths: ['/'],
},
{
href: '/sessions', icon: Zap, label: 'Work', shortLabel: 'Work',
badge: (stats?.active_count || 0) + (stats?.escalation_count || 0) || undefined,
matchPaths: ['/sessions', '/escalations', '/pilot'],
children: [
{ href: '/sessions', label: 'Active Sessions', count: stats?.active_count || undefined },
{ href: '/escalations', label: 'Escalations', count: stats?.escalation_count || undefined },
],
},
{
href: '/trees', icon: Database, label: 'Knowledge', shortLabel: 'Know',
badge: stats?.tree_counts.total || undefined,
matchPaths: ['/trees', '/flows', '/my-trees', '/step-library', '/scripts', '/script-builder', '/review-queue'],
children: [
{ href: '/trees', label: 'All Flows', count: stats?.tree_counts.total || undefined },
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined },
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
{ href: '/trees?type=maintenance', label: 'Maintenance', count: stats?.tree_counts.maintenance || undefined },
]}
/>
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} />
<NavItem href="/scripts" icon={Code2} label="Scripts" iconColor={NAV_COLORS.scripts} />
<NavItem href="/script-builder" icon={Wand2} label="Script Builder" iconColor={NAV_COLORS.scriptBuilder} />
<NavItem href="/review-queue" icon={ListChecks} label="Review Queue" iconColor="#fbbf24" />
{ href: '/step-library', label: 'Step Library' },
{ href: '/scripts', label: 'Scripts' },
{ href: '/script-builder', label: 'Script Builder' },
{ href: '/review-queue', label: 'Review Queue' },
],
},
{
href: '/analytics', icon: BarChart3, label: 'Insights', shortLabel: 'Data',
matchPaths: ['/analytics', '/shares'],
children: [
{ href: '/shares', label: 'Exports' },
{ href: '/analytics', label: 'Analytics' },
{ href: '/analytics/flowpilot', label: 'FlowPilot Analytics' },
],
},
{
href: '/guides', icon: HelpCircle, label: 'Help', shortLabel: 'Help',
matchPaths: ['/guides', '/feedback'],
children: [
{ href: '/guides', label: 'User Guides' },
{ href: '/feedback', label: 'Feedback' },
],
},
]
{/* Insights */}
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
Insights
</div>
<NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} />
<NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} />
<NavItem href="/analytics/flowpilot" icon={TrendingUp} label="FlowPilot Analytics" iconColor="#2dd4bf" />
</div>
</>
/* Pinned mode still uses the detailed section layout */
const sections: NavSection[] = [
{
title: 'RESOLVE',
items: [
{ href: '/', icon: LayoutGrid, label: 'Dashboard', shortLabel: 'Dash' },
{ href: '/sessions', icon: Clock, label: 'Active Sessions', shortLabel: 'Sessions', badge: stats?.active_count || undefined, matchPaths: ['/sessions'] },
{ href: '/escalations', icon: AlertTriangle, label: 'Escalations', shortLabel: 'Escal', badge: stats?.escalation_count || undefined },
],
},
{
title: 'KNOWLEDGE',
items: [
{
href: '/trees', icon: GitBranch, label: 'Flows', shortLabel: 'Flows',
badge: stats?.tree_counts.total || undefined,
matchPaths: ['/trees', '/flows', '/my-trees'],
children: [
{ href: '/trees', label: 'All Flows' },
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined },
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
{ href: '/trees?type=maintenance', label: 'Maintenance', count: stats?.tree_counts.maintenance || undefined },
],
},
{ href: '/step-library', icon: Layers, label: 'Step Library', shortLabel: 'Steps' },
{ href: '/scripts', icon: Code2, label: 'Scripts', shortLabel: 'Scripts' },
{ href: '/script-builder', icon: Wand2, label: 'Script Builder', shortLabel: 'Builder' },
{ href: '/review-queue', icon: ListChecks, label: 'Review Queue', shortLabel: 'Review' },
],
},
{
title: 'INSIGHTS',
items: [
{ href: '/shares', icon: Download, label: 'Exports', shortLabel: 'Export' },
{ href: '/analytics', icon: BarChart3, label: 'Analytics', shortLabel: 'Stats' },
{ href: '/analytics/flowpilot', icon: Rocket, label: 'FlowPilot Analytics', shortLabel: 'FPilot' },
],
},
]
const footerItems: NavEntry[] = [
{ href: '/account', icon: Settings, label: 'Account', shortLabel: 'Acct' },
]
/* ── Active detection ─────────────────────────────── */
const isActive = (item: NavEntry) => {
if (item.matchPaths) return item.matchPaths.some(p =>
p === '/' ? location.pathname === '/' : location.pathname.startsWith(p)
)
if (item.href === '/') return location.pathname === '/'
return location.pathname.startsWith(item.href)
}
const isChildActive = (child: NavSubItem) => {
const fullPath = location.pathname + location.search
return fullPath === child.href || fullPath.startsWith(child.href + '&')
}
/* ── Flyout management ────────────────────────────── */
const openFlyout = (key: string) => {
if (flyoutTimeout.current) clearTimeout(flyoutTimeout.current)
setFlyoutIndex(key)
}
const closeFlyout = () => {
flyoutTimeout.current = setTimeout(() => setFlyoutIndex(null), 400)
}
const keepFlyout = () => {
if (flyoutTimeout.current) clearTimeout(flyoutTimeout.current)
}
/* ── Drawer resize ───────────────────────────────── */
const [drawerWidth, setDrawerWidth] = useState(240)
const isResizing = useRef(false)
const handleResizeStart = (e: ReactPointerEvent) => {
e.preventDefault()
isResizing.current = true
const startX = e.clientX
const startWidth = drawerWidth
const onMove = (ev: globalThis.PointerEvent) => {
if (!isResizing.current) return
const newWidth = Math.max(180, Math.min(400, startWidth + (ev.clientX - startX)))
setDrawerWidth(newWidth)
}
const onUp = () => {
isResizing.current = false
document.removeEventListener('pointermove', onMove)
document.removeEventListener('pointerup', onUp)
}
document.addEventListener('pointermove', onMove)
document.addEventListener('pointerup', onUp)
}
/* Close flyout on Escape */
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') setFlyoutIndex(null)
}
document.addEventListener('keydown', handleKey)
return () => document.removeEventListener('keydown', handleKey)
}, [])
/* ── Wheel forwarding (when sidebar can't scroll) ── */
const handleWheel = (e: React.WheelEvent<HTMLElement>) => {
const el = e.currentTarget
const canScroll = el.scrollHeight > el.clientHeight
const atTop = el.scrollTop <= 0
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1
if (!canScroll || (e.deltaY < 0 && atTop) || (e.deltaY > 0 && atBottom)) {
const main = document.querySelector('.main-content') as HTMLElement | null
if (main) { main.scrollTop += e.deltaY; e.preventDefault() }
}
}
/* ── Render helpers ───────────────────────────────── */
const renderRailItem = (item: NavEntry, key: string) => {
const active = isActive(item)
const Icon = item.icon
const hasChildren = item.children && item.children.length > 0
return (
<div
key={key}
className="relative w-full"
onMouseEnter={() => hasChildren && !sidebarPinned ? openFlyout(key) : undefined}
>
<Link
to={item.href}
onMouseEnter={() => prefetchForRoute(item.href)}
onFocus={() => hasChildren && !sidebarPinned ? openFlyout(key) : undefined}
className={cn(
'group relative flex flex-col items-center justify-center rounded-lg px-2 py-3 transition-all duration-150',
active
? 'bg-accent-dim text-accent-text'
: 'text-text-rail-label hover:text-foreground'
)}
title={item.label}
>
<span className="relative">
<Icon size={24} strokeWidth={1.6} className={active ? 'opacity-100' : 'opacity-60 group-hover:opacity-85'} />
{item.badge !== undefined && item.badge > 0 && (
<span className="absolute -right-2 -top-1.5 flex h-4 min-w-[16px] items-center justify-center rounded-full bg-primary px-1 text-[0.5rem] font-bold text-background">
{item.badge > 99 ? '99+' : item.badge}
</span>
)}
</span>
<span className="mt-1.5 text-[0.625rem] font-sans font-medium leading-tight truncate max-w-[64px]">
{item.shortLabel}
</span>
</Link>
{/* Flyout rendered as drawer below */}
</div>
)
}
const renderPinnedItem = (item: NavEntry, key: string) => {
const active = isActive(item)
const Icon = item.icon
const fullPath = location.pathname + location.search
const activeChild = item.children?.find(c => fullPath === c.href || fullPath.startsWith(c.href + '&'))
const isParentDimmed = !!activeChild && active
return (
<div key={key} className="group/nav">
<Link
to={item.href}
onMouseEnter={() => prefetchForRoute(item.href)}
className={cn(
'group relative flex items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium transition-all duration-150',
active
? isParentDimmed
? 'bg-[rgba(34,211,238,0.05)] text-foreground/70'
: 'bg-accent-dim text-foreground'
: 'text-muted-foreground hover:bg-input hover:text-foreground'
)}
>
{active && !isParentDimmed && (
<div
className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2"
style={{ background: 'var(--color-accent)', borderRadius: '0 3px 3px 0' }}
/>
)}
<Icon size={18} className={cn('shrink-0', active ? 'opacity-100' : 'opacity-70')} />
<span className="truncate">{item.label}</span>
{item.badge !== undefined && item.badge > 0 && (
<span className="ml-auto shrink-0 rounded-full px-2 text-[0.6875rem] font-mono text-text-muted"
style={{ background: 'var(--color-bg-card)', border: '1px solid var(--color-border-default)' }}>
{item.badge}
</span>
)}
</Link>
{/* Sub-items for pinned mode */}
{item.children && item.children.length > 0 && (
<div className={cn(
'mt-0.5 space-y-0.5 overflow-hidden transition-all duration-200',
active || activeChild
? 'max-h-40 opacity-100'
: 'max-h-0 opacity-0 group-hover/nav:max-h-40 group-hover/nav:opacity-100'
)}>
{item.children.map(child => {
const childActive = isChildActive(child)
return (
<Link
key={child.href}
to={child.href}
className={cn(
'flex items-center gap-2 rounded-lg pl-9 pr-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
childActive
? 'bg-accent-dim text-foreground'
: 'text-muted-foreground hover:bg-input hover:text-foreground'
)}
>
<span className="truncate">{child.label}</span>
{child.count !== undefined && (
<span className="ml-auto shrink-0 rounded-full px-2 text-[0.6875rem] font-mono text-text-muted"
style={{ background: 'var(--color-bg-card)', border: '1px solid var(--color-border-default)' }}>
{child.count}
</span>
)}
</Link>
)
})}
</div>
)}
</div>
)
}
/* ── Find active flyout group for drawer ── */
const activeFlyoutGroup = flyoutIndex && !sidebarPinned
? railGroups.find((_, i) => `rail-${i}` === flyoutIndex) ||
footerItems.find((_, i) => `footer-${i}` === flyoutIndex)
: null
/* ── Main render ──────────────────────────────────── */
if (sidebarPinned) {
return (
<nav
ref={sidebarRef}
className="sidebar flex flex-col h-full"
style={{ background: 'var(--color-bg-sidebar)', borderRight: '1px solid var(--color-border-default)' }}
onWheel={handleWheel}
>
{/* Pinned sidebar content */}
<div className="px-3 py-2 space-y-0.5">
{sections.map((section, si) => (
<div key={section.title}>
{si > 0 && (
<div className="font-mono text-[0.5625rem] uppercase tracking-[0.12em] text-text-muted px-3 pt-3 pb-1">
{section.title}
</div>
)}
{section.items.map((item, ii) => renderPinnedItem(item, `${si}-${ii}`))}
</div>
))}
</div>
{/* Spacer — pushes footer to bottom */}
<div className="flex-1" />
{/* Footer */}
<div
className={cn(
"border-t",
sidebarCollapsed ? "px-1.5 py-2 flex flex-col items-center" : "px-3 py-2 space-y-0.5"
)}
style={{ borderColor: 'var(--glass-border)' }}
>
{!sidebarCollapsed && (
<>
<NavItem href="/guides" icon={BookOpen} label="User Guides" iconColor={NAV_COLORS.guides} />
<NavItem href="/feedback" icon={MessageSquareText} label="Feedback" iconColor={NAV_COLORS.feedback} />
<NavItem href="/account" icon={Settings} label="Account" />
</>
)}
<div className="px-3 pt-2 pb-4 space-y-0.5" style={{ borderTop: '1px solid var(--color-border-default)' }}>
{footerItems.map((item, i) => renderPinnedItem(item, `footer-${i}`))}
<button
onClick={toggleSidebar}
className={cn(
"flex w-full items-center rounded-lg text-[0.8125rem] font-medium text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground transition-colors",
sidebarCollapsed ? "justify-center p-2.5" : "gap-3 px-3 py-2"
)}
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
type="button"
onClick={toggleSidebarPinned}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium text-muted-foreground hover:bg-input hover:text-foreground transition-colors"
title="Unpin sidebar"
>
{sidebarCollapsed ? <PanelLeftOpen size={20} /> : <PanelLeftClose size={18} />}
{!sidebarCollapsed && <span>Collapse</span>}
<PinOff size={18} className="shrink-0" />
<span>Unpin</span>
</button>
</div>
</nav>
)
}
/* Icon Rail (default) — 5 grouped icons, Sentry-style */
return (
<div
className="flex h-full"
onMouseLeave={closeFlyout}
>
{/* Rail */}
<nav
ref={sidebarRef}
className="sidebar flex flex-col items-center h-full shrink-0"
style={{ background: 'var(--color-bg-sidebar)', borderRight: '1px solid var(--color-border-default)', width: '72px' }}
onWheel={handleWheel}
>
{/* Grouped nav items */}
<div className="flex flex-col items-center w-full px-1 pt-4 space-y-1.5">
{railGroups.map((item, i) => renderRailItem(item, `rail-${i}`))}
</div>
<div className="flex-1" />
{/* Footer: Account + Pin */}
<div className="flex flex-col items-center w-full px-1 pb-5 pt-3 space-y-1.5" style={{ borderTop: '1px solid var(--color-border-default)' }}>
{footerItems.map((item, i) => renderRailItem(item, `footer-${i}`))}
<button
type="button"
onClick={toggleSidebarPinned}
className="flex flex-col items-center justify-center rounded-lg px-2 py-3 text-text-rail-label hover:text-muted-foreground transition-colors"
title="Pin sidebar"
>
<Pin size={22} strokeWidth={1.6} className="opacity-60 hover:opacity-85" />
<span className="mt-1.5 text-[0.625rem] font-sans font-medium leading-tight">Pin</span>
</button>
</div>
</nav>
{/* Drawer panel — fixed position, full height, resizable, overlays main content */}
{activeFlyoutGroup && activeFlyoutGroup.children && (
<div
className="fixed bottom-0 z-50 flex"
style={{ top: 'var(--topbar-h)', left: '72px' }}
onMouseEnter={keepFlyout}
onMouseLeave={closeFlyout}
>
<div
className="flex flex-col h-full overflow-y-auto py-4 px-2"
style={{
width: drawerWidth,
background: 'var(--color-bg-sidebar)',
borderRight: '1px solid var(--color-border-default)',
boxShadow: '4px 0 12px rgba(0,0,0,0.2)',
}}
>
{/* Drawer header */}
<div className="px-3 mb-3">
<h3 className="text-[0.6875rem] font-mono uppercase tracking-[0.12em] text-[#fbbf24]">
{activeFlyoutGroup.label}
</h3>
</div>
{/* Drawer items */}
<div className="space-y-0.5 px-1">
{activeFlyoutGroup.children.map(child => (
<Link
key={child.href}
to={child.href}
className={cn(
'flex items-center justify-between rounded-md px-3 py-2 text-[0.8125rem] transition-colors',
isChildActive(child)
? 'bg-accent-dim text-accent-text'
: 'text-muted-foreground hover:bg-input hover:text-foreground'
)}
>
<span>{child.label}</span>
{child.count !== undefined && (
<span className="text-[0.6875rem] font-mono text-text-muted">{child.count}</span>
)}
</Link>
))}
</div>
</div>
{/* Resize handle */}
<div
className="w-1 cursor-col-resize hover:bg-primary/20 active:bg-primary/30 transition-colors shrink-0"
onPointerDown={handleResizeStart}
title="Drag to resize"
/>
</div>
)}
</div>
)
}

View File

@@ -35,7 +35,7 @@ export function TopBar() {
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [userMenuOpen])
// K / Ctrl+K global shortcut
// Cmd+K / Ctrl+K global shortcut
const handleGlobalKeyDown = useCallback((e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault()
@@ -55,12 +55,10 @@ export function TopBar() {
return (
<>
<header
className="topbar relative z-10 flex items-center gap-4 border-b px-4"
className="topbar relative z-10 flex items-center gap-4 px-4 pl-14 md:pl-4"
style={{
background: 'rgba(16, 17, 20, 0.6)',
backdropFilter: 'var(--glass-blur-strong)',
WebkitBackdropFilter: 'var(--glass-blur-strong)',
borderColor: 'var(--glass-border)',
background: 'var(--color-bg-sidebar)',
borderBottom: '1px solid var(--color-border-default)',
}}
>
{/* Logo area */}
@@ -68,10 +66,9 @@ export function TopBar() {
to="/"
className="flex items-center gap-2.5 pr-4 transition-all duration-200"
>
<BrandLogo size="sm" className="h-7 w-7 shrink-0" />
<span className="text-sm font-heading font-bold tracking-tight whitespace-nowrap">
<span className="text-foreground">Resolution</span>
<span className="text-gradient-brand">Flow</span>
<BrandLogo size="sm" />
<span className="text-sm font-heading font-bold tracking-tight whitespace-nowrap text-text-heading">
ResolutionFlow
</span>
</Link>
@@ -85,16 +82,27 @@ export function TopBar() {
style={{ maxWidth: '480px' }}
>
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
<div className="w-full rounded-lg border border-border bg-card py-2 pl-9 pr-14 text-[0.8125rem] text-muted-foreground cursor-pointer hover:border-primary/30 transition-colors">
Search flows, sessions, tags
<div
className="w-full rounded-md py-2 pl-9 pr-14 text-[0.8125rem] text-muted-foreground cursor-pointer transition-colors"
style={{
background: 'var(--color-bg-card)',
border: '1px solid var(--color-border-default)',
}}
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.borderColor = 'var(--color-border-hover)' }}
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.borderColor = 'var(--color-border-default)' }}
>
Search flows, sessions, tags...
</div>
<span className="absolute right-3 top-1/2 -translate-y-1/2 rounded border border-border bg-background px-1.5 py-0.5 font-label text-[0.625rem] text-muted-foreground">
{navigator.platform?.toLowerCase().includes('mac') ? '⌘K' : 'Ctrl+K'}
<span
className="absolute right-3 top-1/2 -translate-y-1/2 rounded px-1.5 py-0.5 font-mono text-[0.625rem] text-text-muted"
style={{ background: 'var(--color-bg-page)', border: '1px solid var(--color-border-default)' }}
>
{navigator.platform?.toLowerCase().includes('mac') ? '\u2318K' : 'Ctrl+K'}
</span>
</button>
<button
onClick={() => setCommandPaletteOpen(true)}
className="sm:hidden rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors"
className="sm:hidden rounded-lg p-2 text-muted-foreground hover:text-foreground transition-colors"
title="Search"
>
<Search size={18} />
@@ -125,15 +133,19 @@ export function TopBar() {
<div className="relative ml-2" ref={menuRef}>
<button
onClick={() => setUserMenuOpen(!userMenuOpen)}
className="flex h-8 w-8 items-center justify-center rounded-[10px] bg-gradient-brand text-xs font-heading font-bold text-primary-foreground hover:opacity-90 transition-opacity"
className="flex h-8 w-8 items-center justify-center rounded-[10px] text-xs font-heading font-bold text-white hover:opacity-90 transition-opacity"
style={{ background: 'linear-gradient(135deg, #06b6d4, #22d3ee)' }}
title={user?.name || user?.email || 'User'}
>
{initials}
</button>
{userMenuOpen && (
<div className="absolute right-0 z-50 mt-2 w-56 rounded-lg border border-border bg-card p-1 shadow-xl animate-scale-in">
<div className="border-b border-border px-3 py-2.5 mb-1">
<div
className="absolute right-0 z-50 mt-2 w-56 rounded-lg p-1 shadow-xl animate-scale-in"
style={{ background: 'var(--color-bg-card)', border: '1px solid var(--color-border-default)' }}
>
<div className="px-3 py-2.5 mb-1" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
<p className="text-sm font-medium text-foreground truncate">{user?.name || user?.email}</p>
{effectiveRole && effectiveRole !== 'engineer' && (
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
@@ -145,7 +157,7 @@ export function TopBar() {
<Link
to="/account"
onClick={() => setUserMenuOpen(false)}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-input hover:text-foreground"
>
<Settings size={14} />
Account
@@ -154,18 +166,18 @@ export function TopBar() {
<Link
to="/admin"
onClick={() => setUserMenuOpen(false)}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-input hover:text-foreground"
>
<Shield size={14} />
Admin Panel
</Link>
)}
<div className="border-t border-border mt-1 pt-1">
<div className="mt-1 pt-1" style={{ borderTop: '1px solid var(--color-border-default)' }}>
<button
onClick={handleLogout}
className={cn(
'flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm',
'text-muted-foreground hover:bg-accent hover:text-foreground'
'text-muted-foreground hover:bg-input hover:text-foreground'
)}
>
<LogOut size={14} />

View File

@@ -102,7 +102,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
<div
className={cn(
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border',
'bg-card backdrop-blur-xs py-1 shadow-lg'
'bg-card py-1 shadow-lg'
)}
>
{isLoading ? (

View File

@@ -74,7 +74,7 @@ export function ExportFlowModal({ treeId, treeName, onClose }: ExportFlowModalPr
{/* Body */}
<div className="px-5 py-4">
<p className="text-sm text-muted-foreground">
Export <span className="font-medium text-foreground">{treeName}</span> as a <code className="text-xs font-label">.rfflow</code> file (JSON format).
Export <span className="font-medium text-foreground">{treeName}</span> as a <code className="text-xs font-sans text-xs">.rfflow</code> file (JSON format).
</p>
</div>

View File

@@ -175,7 +175,7 @@ export function FolderEditModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div className="absolute inset-0 bg-black/80 backdrop-blur-xs" onClick={onClose} />
<div className="absolute inset-0 bg-black/80" onClick={onClose} />
{/* Modal */}
<div className="relative z-10 w-full max-w-md bg-card border border-border rounded-2xl p-6 shadow-lg">

View File

@@ -162,7 +162,7 @@ function FolderItem({
<div
className={cn(
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border',
'bg-card backdrop-blur-xs py-1 shadow-lg'
'bg-card py-1 shadow-lg'
)}
>
<button
@@ -362,7 +362,7 @@ export function FolderSidebar({
{/* Mobile backdrop */}
{mobileOpen && (
<div
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-xs md:hidden"
className="fixed inset-0 z-40 bg-black/80 md:hidden"
onClick={onMobileClose}
aria-hidden="true"
/>
@@ -461,7 +461,7 @@ export function FolderSidebar({
<div
className={cn(
'fixed z-50 w-44 rounded-md border border-border',
'bg-card backdrop-blur-xs py-1 shadow-lg'
'bg-card py-1 shadow-lg'
)}
style={{ left: contextMenu.x, top: contextMenu.y }}
onClick={(e) => e.stopPropagation()}

View File

@@ -122,7 +122,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
'flex flex-col items-center justify-center rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors cursor-pointer',
isDragging
? 'border-primary/50 bg-primary/5'
: 'border-border hover:border-white/[0.12]'
: 'border-border hover:border-border-hover'
)}
onClick={() => fileInputRef.current?.click()}
onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }}
@@ -175,7 +175,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
<div className="space-y-2 text-xs">
<div className="flex items-center gap-2">
<span className="text-muted-foreground">Type:</span>
<span className="rounded bg-primary/10 px-2 py-0.5 font-label text-primary">
<span className="rounded bg-accent-dim px-2 py-0.5 font-sans text-xs text-primary">
{TYPE_LABELS[parsed.flow.tree_type] || parsed.flow.tree_type}
</span>
</div>
@@ -198,7 +198,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
<div className="flex flex-wrap items-center gap-1.5">
<span className="text-muted-foreground">Tags:</span>
{parsed.flow.tags.map((tag) => (
<span key={tag} className="rounded bg-card border border-border px-2 py-0.5 font-label text-foreground">
<span key={tag} className="rounded bg-card border border-border px-2 py-0.5 font-sans text-xs text-foreground">
{tag}
</span>
))}

View File

@@ -115,7 +115,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
className="absolute inset-0 bg-black/80"
onClick={onClose}
/>

View File

@@ -46,7 +46,7 @@ export function TreeGridView({
</span>
)}
{tree.tree_type === 'maintenance' && (
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400">
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wide text-amber-400">
<Wrench className="h-3 w-3" />
Maintenance
</span>
@@ -68,7 +68,7 @@ export function TreeGridView({
</span>
)}
{tree.category_info && (
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
<span className="rounded-full bg-primary/10 border border-primary/20 px-2 py-0.5 text-xs text-primary">
{tree.category_info.name}
</span>
)}
@@ -163,8 +163,8 @@ export function TreeGridView({
type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn(
'rounded-md bg-gradient-brand px-3 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90'
'rounded-md border border-primary/40 px-3 py-2 text-sm font-medium text-primary',
'hover:bg-primary/10 hover:border-primary/60 transition-colors'
)}
>
Start Session

View File

@@ -46,7 +46,7 @@ export function TreeListView({
</span>
)}
{tree.tree_type === 'maintenance' && (
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
<Wrench className="h-3 w-3" />
Maintenance
</span>
@@ -74,7 +74,7 @@ export function TreeListView({
{/* Center: Category and Tags */}
<div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}>
{tree.category_info && (
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground whitespace-nowrap">
<span className="rounded-full bg-primary/10 border border-primary/20 px-2 py-0.5 text-xs text-primary whitespace-nowrap">
{tree.category_info.name}
</span>
)}
@@ -166,8 +166,8 @@ export function TreeListView({
type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn(
'rounded-md bg-gradient-brand px-3 py-1.5 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 whitespace-nowrap'
'rounded-md border border-primary/40 px-3 py-1.5 text-sm font-medium text-primary',
'hover:bg-primary/10 hover:border-primary/60 transition-colors whitespace-nowrap'
)}
>
Start

View File

@@ -148,7 +148,7 @@ export function TreeTableView({
</span>
)}
{tree.tree_type === 'maintenance' && (
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
<Wrench className="h-3 w-3" />
Maintenance
</span>
@@ -176,7 +176,7 @@ export function TreeTableView({
</td>
<td className="hidden lg:table-cell px-4 py-3">
{tree.category_info && (
<span className="inline-block rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
<span className="inline-block rounded-full bg-primary/10 border border-primary/20 px-2 py-0.5 text-xs text-primary">
{tree.category_info.name}
</span>
)}
@@ -270,8 +270,8 @@ export function TreeTableView({
type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn(
'rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 whitespace-nowrap'
'rounded-md border border-primary/40 px-3 py-1.5 text-xs font-medium text-primary',
'hover:bg-primary/10 hover:border-primary/60 transition-colors whitespace-nowrap'
)}
>
Start

View File

@@ -96,7 +96,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={cn(
"flex items-center gap-1.5 px-3 py-2 font-label text-[0.6875rem] uppercase tracking-wide transition-colors",
"flex items-center gap-1.5 px-3 py-2 font-sans text-[0.6875rem] uppercase tracking-wide transition-colors",
activeTab === tab.id
? "border-b-2 border-primary text-foreground"
: "text-muted-foreground hover:text-foreground"
@@ -112,7 +112,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
<div className="p-6">
{activeTab === 'manual' && (
<div className="space-y-2">
<label className="font-label text-[0.6875rem] uppercase tracking-wide text-muted-foreground">
<label className="font-sans text-[0.6875rem] uppercase tracking-wide text-muted-foreground">
Server names (one per line)
</label>
<textarea
@@ -192,7 +192,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
<button
onClick={handleLaunch}
disabled={isLaunching || targets.length === 0}
className="rounded-lg bg-gradient-brand px-4 py-2 text-[0.875rem] font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-50"
className="rounded-lg bg-primary px-4 py-2 text-[0.875rem] font-medium text-white hover:brightness-110 disabled:opacity-50"
>
{isLaunching ? 'Launching\u2026' : targets.length > 0 ? `Launch ${targets.length} Sessions` : 'Launch'}
</button>

View File

@@ -86,7 +86,7 @@ export function BatchStatusCard({ session, targetLabel, treeId, batchId }: Batch
<div className="flex items-center gap-2 mt-0.5">
{isComplete && session?.outcome && (
<span className={cn(
"font-label text-[0.625rem] uppercase tracking-wide rounded-full px-2 py-0.5",
"font-sans text-[0.625rem] uppercase tracking-wide rounded-full px-2 py-0.5",
OUTCOME_COLORS[session.outcome] ?? OUTCOME_COLORS.unresolved
)}>
{OUTCOME_LABELS[session.outcome] ?? session.outcome}
@@ -132,7 +132,7 @@ export function BatchStatusCard({ session, targetLabel, treeId, batchId }: Batch
{isNotStarted && (
<button
onClick={handleStart}
className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-3 py-1.5 text-[0.8125rem] font-medium text-white shadow-xs shadow-primary/20 hover:opacity-90 transition-opacity"
className="flex items-center gap-1.5 rounded-lg border border-primary/40 px-3 py-1.5 text-[0.8125rem] font-medium text-primary hover:bg-primary/10 hover:border-primary/60 transition-colors"
>
<Play className="h-3.5 w-3.5" />
Start

View File

@@ -226,7 +226,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
className={cn(
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
treeId
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90'
? 'bg-primary text-white hover:brightness-110'
: 'cursor-not-allowed border border-border bg-card text-muted-foreground opacity-50'
)}
>

View File

@@ -64,7 +64,7 @@ export function FallbackSteps({
key={fbStep.id}
className={cn(
'rounded-lg border p-3 transition-colors',
'bg-white/[0.02] border-border/50',
'bg-card border-border/50',
isCompleted && 'border-emerald-500/30 bg-emerald-500/5'
)}
>

View File

@@ -212,7 +212,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
}
return (
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4 bg-black/60 backdrop-blur-xs">
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4 bg-black/60">
<div className="w-full max-w-full rounded-t-2xl border border-border bg-background shadow-xl sm:max-w-lg sm:rounded-2xl">
{/* Header */}
<div className="border-b border-border px-6 py-4">

View File

@@ -89,7 +89,7 @@ export function PrepareSessionModal({
return (
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
<div className="absolute inset-0 bg-background/60 backdrop-blur-xs" onClick={onClose} />
<div className="absolute inset-0 bg-background/60" onClick={onClose} />
<div className="relative w-full max-w-full rounded-t-2xl border border-border bg-card shadow-2xl sm:max-w-lg sm:rounded-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-5 py-4">
@@ -160,7 +160,7 @@ export function PrepareSessionModal({
{/* Intake form fields */}
{intakeFields.length > 0 && (
<div className="space-y-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Variables (optional can be filled later)
</h4>
{Array.from(grouped.entries()).map(([groupName, fields]) => (
@@ -217,7 +217,7 @@ export function PrepareSessionModal({
<div className="flex items-center justify-end gap-3 border-t border-border px-5 py-4">
<button
onClick={onClose}
className="rounded-[10px] px-4 py-2 text-sm text-muted-foreground hover:text-foreground"
className="rounded-lg px-4 py-2 text-sm text-muted-foreground hover:text-foreground"
>
Cancel
</button>
@@ -225,8 +225,8 @@ export function PrepareSessionModal({
onClick={handleSubmit}
disabled={isSubmitting}
className={cn(
'rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114]',
'shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]',
'rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-white',
'hover:brightness-110 active:scale-[0.98]',
'disabled:opacity-40'
)}
>

Some files were not shown because too many files have changed in this diff Show More