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` | | Repository / directory / database / Docker | `patherly` / `patherly_postgres` |
| Backend, frontend UI, production URLs | **ResolutionFlow** | | 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) - **Design system:** [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) — THE source of truth for all design decisions
- **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 - **Design aesthetic:** Flat, high-contrast dark theme (Sentry/PostHog-inspired). No glass morphism, no gradients on surfaces, no ambient effects. Light mode planned.
- **Logo:** Inline SVG in `BrandLogo.tsx` (decision-tree icon with cyan gradient). Wordmark: "Resolution" in `text-foreground` + "Flow" in `text-gradient-brand` - **Accent color:** Cyan (#22d3ee / #06b6d4). Used sparingly — ≤5% of the UI.
- **Brand assets:** `brand-assets/` (source SVGs + brand-guide.html), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon) - **Fonts:** IBM Plex Sans (`font-sans`, body), Bricolage Grotesque (`font-heading`, headings), JetBrains Mono (`font-mono`, code) — loaded via Google Fonts
- **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) - **Logo:** 30px gradient square (cyan) + "ResolutionFlow" in Bricolage Grotesque 700
- **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) - **Layout:** Icon rail sidebar (72px default) with hover flyout panels. Pinnable to full 260px sidebar. See [DESIGN-SYSTEM.md](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) - **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. - **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:** **Component styling rules:**
- Primary buttons: `bg-gradient-brand` (cyan `135deg`) with `shadow-lg shadow-primary/20`, hover `opacity-0.9`, active `scale(0.97)` - Primary buttons: solid `accent` background (#22d3ee), white text, 5px radius
- Secondary buttons: `bg-[rgba(255,255,255,0.04)]` with `border-[rgba(255,255,255,0.06)]`, hover brightens border - Ghost buttons: transparent with 1px `border-default`, hover `bg-elevated`
- Active nav items: `bg-primary/10` background + 3px left cyan gradient accent bar - Cards: `bg-card` with 1px `border-default`, 8px radius. NO shadows, NO blur, NO gradients.
- Stat values: use `text-gradient-brand` for highlighted metrics - Badges: pill-shaped (20px radius), semantic dim background + matching text color
- Status colors: emerald-400 (success), amber-400 (in-progress), rose-500 (error/critical) - Active nav: `accent-dim` background + `accent-text` color + 3px left accent bar
- Category dots: 8px colored circles using the category color palette - Stat cards: 3px colored left border (accent/success/warning by position)
- Tags/badges: `font-label` (JetBrains Mono), small rounded chips with `bg-card border-border` - Code blocks: `bg-code` with JetBrains Mono, material-inspired syntax highlighting
- Cards: `.glass-card` (interactive) or `.glass-card-static` (non-interactive) — semi-transparent bg with `backdrop-filter: blur(16px)`, `border-radius: 16px` - Status colors: green/`#34d399` (success), amber/`#fbbf24` (warning), red/`#f87171` (danger) — ONLY for semantic meaning
- Section labels: `font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground` - 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 ## Implementation Principles
@@ -97,7 +97,7 @@ When adding new pages/components: use "ResolutionFlow" branding, ice-cyan gradie
### Frontend ### Frontend
- **Framework:** React 19 + Vite + TypeScript - **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) - **State:** Zustand (with immer + zundo for undo/redo)
- **Routing:** React Router v7 - **Routing:** React Router v7
- **API Client:** Axios with token refresh interceptor - **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 **Source of truth:** [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) — always read this before making visual or UI decisions.
- **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 - **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.
- **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]` - **Backgrounds:** `bg-page` (`#0c0d10`), `bg-sidebar` (`#0f1118`), `bg-card` (`#14161d`), `bg-elevated` (`#1c1f2a`)
- **Inputs:** `border-border bg-card text-foreground placeholder:text-muted-foreground` + focus: `focus:border-[rgba(6,182,212,0.3)]` - **Cards:** `bg-card` with 1px `border-default` (`#1e2130`), 8px radius. No shadows, no blur, no gradients. Hover: `border-hover` (`#2a2f3d`)
- **Text:** `text-foreground` (`#f8fafc`) → `text-muted-foreground` (`#8891a0`) → `text-[#5a6170]` (dim, for section labels/timestamps) - **Buttons:** Primary: solid `accent` (#22d3ee), white text, 5px radius. Ghost: transparent + 1px border, hover `bg-elevated`
- **Borders:** `var(--glass-border)` (`rgba(255,255,255,0.06)`) default, `rgba(255,255,255,0.12)` on hover - **Inputs:** `bg-input` (`#191c25`) with 1px `border-default`, 5px radius. Focus: `border-color: accent` + `box-shadow: 0 0 0 2px accent-dim`
- **Hover states:** Border brightens to `rgba(255,255,255,0.12)`, shadow upgrades to `--shadow-float-hover` - **Text:** `text-heading` (`#f0f2f5`) → `text-primary` (`#e2e5eb`) → `text-secondary` (`#848b9b`) → `text-muted` (`#4f5666`)
- **Active/selected:** `bg-primary/10 text-foreground` or cyan gradient accent bar - **Borders:** `border-default` (`#1e2130`), `border-hover` (`#2a2f3d`)
- **Functional colors:** emerald-400 (success), rose-500 (error), amber-400 (warning), blue-400 (info). Always pair with icons, not color alone. - **Functional colors:** `#34d399` (success), `#fbbf24` (warning), `#f87171` (danger) — each with `-dim` variant at 10% opacity
- **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` - **Accent:** Cyan `#22d3ee` — used sparingly (≤5% of UI). `accent-dim` = `rgba(34,211,238,0.10)`, `accent-text` = `#67e8f9`
- **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. - **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` | | GitHub Issues | `gh issue list --state open` |
| Bugs & Fixes | CLAUDE.md → Critical Lessons Learned section | | Bugs & Fixes | CLAUDE.md → Critical Lessons Learned section |
| Feature Specs | [04-FEATURE-SPECIFICATIONS.md](04-FEATURE-SPECIFICATIONS.md) | | 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 | | 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", "@sentry/vite-plugin": "^5.1.1",
"@stripe/stripe-js": "^8.7.0", "@stripe/stripe-js": "^8.7.0",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
"@types/react-syntax-highlighter": "^15.5.13",
"@xyflow/react": "^12.10.0", "@xyflow/react": "^12.10.0",
"axios": "^1.13.4", "axios": "^1.13.4",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@@ -51,6 +50,7 @@
"@types/node": "^24.10.9", "@types/node": "^24.10.9",
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"@vitest/coverage-v8": "^4.0.18", "@vitest/coverage-v8": "^4.0.18",
"eslint": "^9.39.1", "eslint": "^9.39.1",
@@ -3021,6 +3021,7 @@
"version": "15.5.13", "version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,7 +44,7 @@ export function AdminLayout() {
{mobileOpen && ( {mobileOpen && (
<div className="fixed inset-0 z-40 md:hidden"> <div className="fixed inset-0 z-40 md:hidden">
<div <div
className="absolute inset-0 bg-card/80 backdrop-blur-xs" className="absolute inset-0 bg-card/80"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
/> />
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl"> <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, btnBase,
'px-2', 'px-2',
p === page 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' : '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) { export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
if (data.domains.length === 0) { if (data.domains.length === 0) {
return ( return (
<div className="glass-card-static p-6"> <div className="card-flat p-6">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<MapPin size={16} className="text-foreground" /> <MapPin size={16} className="text-foreground" />
<h3 className="font-heading text-sm font-semibold text-foreground">Domain Coverage</h3> <h3 className="font-heading text-sm font-semibold text-foreground">Domain Coverage</h3>
@@ -52,7 +52,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
} }
return ( 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="mb-4">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<MapPin size={16} className="text-foreground" /> <MapPin size={16} className="text-foreground" />
@@ -67,25 +67,25 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-border"> <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 Domain
</th> </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 Flows
</th> </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 Sessions
</th> </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 % Resolution %
</th> </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 % Escalation %
</th> </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 % Guided %
</th> </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."> 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 Avg Resolution
</th> </th>
@@ -176,7 +176,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
</table> </table>
</div> </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-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-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> <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) { if (data.flows.length === 0) {
return ( 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]"> <div className="flex items-center justify-center min-h-[200px]">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
No flows found for this account. Create your first flow to start tracking quality. 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 ( 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="mb-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<h3 className="font-heading text-sm font-semibold text-foreground"> <h3 className="font-heading text-sm font-semibold text-foreground">
@@ -160,7 +160,7 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
return ( return (
<th <th
key={col.key} 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)} onClick={() => handleSort(col.key)}
title={col.title} title={col.title}
> >
@@ -221,11 +221,11 @@ function FlowRow({
> >
{flow.name} {flow.name}
</Link> </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} {TYPE_LABELS[flow.tree_type]?.label || flow.tree_type}
</span> </span>
{needsAttention && ( {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 Needs attention
</span> </span>
)} )}

View File

@@ -19,7 +19,7 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
if (!hasActivity) { if (!hasActivity) {
return ( 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]"> <div className="flex items-center justify-center min-h-[200px]">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
No PSA activity data for this period. Link sessions to PSA tickets to see metrics. 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"> <div className="space-y-4">
{/* Row 1 — Metric cards */} {/* Row 1 — Metric cards */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="glass-card-static p-4"> <div className="card-flat p-4">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Time Entries</p> <p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-text-muted">Time Entries</p>
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.total_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> <p className="text-xs text-muted-foreground mt-1">logged to PSA</p>
</div> </div>
<div className="glass-card-static p-4"> <div className="card-flat p-4">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Hours Logged</p> <p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-text-muted">Hours Logged</p>
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.total_hours_logged.toFixed(1)}</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> <p className="text-xs text-muted-foreground mt-1">total hours tracked</p>
</div> </div>
<div className="glass-card-static p-4"> <div className="card-flat p-4">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Avg Hours/Session</p> <p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-text-muted">Avg Hours/Session</p>
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.avg_hours_per_session.toFixed(2)}</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> <p className="text-xs text-muted-foreground mt-1">per resolved session</p>
</div> </div>
</div> </div>
@@ -55,7 +55,7 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
{/* Row 3 — Daily Trend Chart */} {/* Row 3 — Daily Trend Chart */}
{data.daily_trend.length > 0 && ( {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"> <h3 className="font-heading text-sm font-semibold text-foreground mb-4">
PSA Activity Trend PSA Activity Trend
</h3> </h3>
@@ -129,7 +129,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
] ]
return ( 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"> <h3 className="font-heading text-sm font-semibold text-foreground mb-4">
Documentation Push Funnel Documentation Push Funnel
</h3> </h3>
@@ -139,7 +139,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
{steps.map((step, i) => ( {steps.map((step, i) => (
<div key={step.label} className="flex items-center gap-2 flex-1"> <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"> <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} {step.label}
</p> </p>
<p className="text-lg font-heading text-foreground">{step.count}</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 && ( {i < steps.length - 1 && (
<div className="flex flex-col items-center shrink-0 px-1"> <div className="flex flex-col items-center shrink-0 px-1">
<ArrowRight size={14} className="text-muted-foreground" /> <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)} {funnelPct(steps[i].count, steps[i + 1].count)}
</span> </span>
</div> </div>
@@ -161,7 +161,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
{steps.map((step, i) => ( {steps.map((step, i) => (
<div key={step.label} className="flex flex-col items-center gap-2 w-full"> <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"> <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} {step.label}
</p> </p>
<p className="text-lg font-heading text-foreground">{step.count}</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 && ( {i < steps.length - 1 && (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<ArrowDown size={14} className="text-muted-foreground" /> <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)} {funnelPct(steps[i].count, steps[i + 1].count)}
</span> </span>
</div> </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 ${ className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
role === 'user' role === 'user'
? 'bg-primary/15 text-foreground' ? '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" /> <MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
@@ -38,7 +38,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
{/* Suggested flows (assistant only) */} {/* Suggested flows (assistant only) */}
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && ( {role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
<div className="space-y-1.5"> <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 Related Flows
</span> </span>
{suggestedFlows.map(flow => ( {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)' }}> <div className="px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--glass-border)' }}>
<button <button
onClick={onNewChat} 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} /> <Plus size={16} />
New Chat New Chat
@@ -42,7 +42,7 @@ export function ChatSidebar({
<div className="flex-1 overflow-y-auto py-2"> <div className="flex-1 overflow-y-auto py-2">
{pinnedChats.length > 0 && ( {pinnedChats.length > 0 && (
<div className="px-3 mb-1"> <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 Pinned
</span> </span>
</div> </div>
@@ -102,8 +102,8 @@ function ChatItem({
className={cn( className={cn(
'group flex items-center gap-2 px-3 py-2.5 mx-1.5 rounded-lg cursor-pointer transition-colors', 'group flex items-center gap-2 px-3 py-2.5 mx-1.5 rounded-lg cursor-pointer transition-colors',
isActive isActive
? 'bg-primary/10 text-foreground' ? 'bg-accent-dim text-foreground'
: 'text-muted-foreground hover:bg-white/[0.04] hover:text-foreground' : 'text-muted-foreground hover:bg-input hover:text-foreground'
)} )}
> >
<MessageSquare size={14} className="shrink-0" /> <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"> <div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */} {/* Backdrop */}
<div <div
className="absolute inset-0 bg-black/60 backdrop-blur-xs" className="absolute inset-0 bg-black/60"
onClick={onClose} onClick={onClose}
/> />
{/* Modal */} {/* Modal */}
<div <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={{ style={{
maxHeight: 'calc(100vh - 4rem)', maxHeight: 'calc(100vh - 4rem)',
display: 'flex', display: 'flex',
@@ -155,7 +155,7 @@ export function ConcludeSessionModal({
style={{ borderColor: 'var(--glass-border)' }} style={{ borderColor: 'var(--glass-border)' }}
> >
<div className="flex items-center gap-3"> <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" /> <ClipboardList size={18} className="text-primary" />
</div> </div>
<div> <div>
@@ -169,7 +169,7 @@ export function ConcludeSessionModal({
</div> </div>
<button <button
onClick={onClose} 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} /> <X size={18} />
</button> </button>
@@ -194,9 +194,9 @@ export function ConcludeSessionModal({
)} )}
<div <div
className={cn( 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 step === s
? 'bg-gradient-brand text-brand-dark' ? 'bg-primary text-white'
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step)) : (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
? 'bg-primary/20 text-primary' ? 'bg-primary/20 text-primary'
: 'bg-brand-border text-muted-foreground' : 'bg-brand-border text-muted-foreground'
@@ -206,7 +206,7 @@ export function ConcludeSessionModal({
</div> </div>
<span <span
className={cn( className={cn(
'text-xs font-label', 'text-xs font-sans text-xs',
step === s ? 'text-foreground' : 'text-muted-foreground' step === s ? 'text-foreground' : 'text-muted-foreground'
)} )}
> >
@@ -233,8 +233,8 @@ export function ConcludeSessionModal({
className={cn( className={cn(
'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left', 'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left',
'hover:scale-[1.01] active:scale-[0.99]', 'hover:scale-[1.01] active:scale-[0.99]',
'bg-white/[0.02] border-brand-border', 'bg-card border-border',
'hover:border-white/[0.12] hover:bg-white/[0.04]' 'hover:border-border-hover hover:bg-input'
)} )}
> >
<div className={cn('w-10 h-10 rounded-xl flex items-center justify-center', o.bg)}> <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"> <div className="space-y-4">
{/* Selected outcome badge */} {/* Selected outcome badge */}
<div className="flex items-center gap-2"> <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} /> <selectedOutcome.icon size={14} className={selectedOutcome.color} />
<span className={selectedOutcome.color}>{selectedOutcome.label}</span> <span className={selectedOutcome.color}>{selectedOutcome.label}</span>
</div> </div>
@@ -268,7 +268,7 @@ export function ConcludeSessionModal({
</div> </div>
<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) Additional Notes (optional)
</label> </label>
<textarea <textarea
@@ -300,7 +300,7 @@ export function ConcludeSessionModal({
<div className="space-y-4"> <div className="space-y-4">
{/* Outcome badge */} {/* Outcome badge */}
{selectedOutcome && ( {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} /> <selectedOutcome.icon size={14} className={selectedOutcome.color} />
<span className={selectedOutcome.color}>{selectedOutcome.label}</span> <span className={selectedOutcome.color}>{selectedOutcome.label}</span>
</div> </div>
@@ -308,11 +308,11 @@ export function ConcludeSessionModal({
{/* Generated summary */} {/* Generated summary */}
<div <div
className="rounded-xl border p-5 bg-white/[0.02]" className="rounded-xl border p-5 bg-card"
style={{ borderColor: 'var(--glass-border)' }} style={{ borderColor: 'var(--glass-border)' }}
> >
<div className="flex items-center justify-between mb-3"> <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" /> <Sparkles size={10} className="text-primary" />
Generated Ticket Notes Generated Ticket Notes
</span> </span>
@@ -335,7 +335,7 @@ export function ConcludeSessionModal({
<div /> <div />
<button <button
onClick={onClose} 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 Cancel
</button> </button>
@@ -346,14 +346,14 @@ export function ConcludeSessionModal({
<> <>
<button <button
onClick={() => setStep('select-outcome')} 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 Back
</button> </button>
<button <button
onClick={handleGenerate} onClick={handleGenerate}
disabled={generating} 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 ? ( {generating ? (
<> <>
@@ -376,7 +376,7 @@ export function ConcludeSessionModal({
{outcome === 'paused' && ( {outcome === 'paused' && (
<button <button
onClick={handleResumeNew} 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} /> <RefreshCw size={14} />
Resume in New Chat Resume in New Chat
@@ -387,10 +387,10 @@ export function ConcludeSessionModal({
<button <button
onClick={handleCopy} onClick={handleCopy}
className={cn( 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 copied
? 'bg-emerald-400/15 text-emerald-400 border border-emerald-400/30' ? '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 ? ( {copied ? (
@@ -407,7 +407,7 @@ export function ConcludeSessionModal({
</button> </button>
<button <button
onClick={onClose} 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 Done
</button> </button>

View File

@@ -18,7 +18,7 @@ export function SuggestedFlowCard({ flow }: SuggestedFlowCardProps) {
return ( return (
<button <button
onClick={handleClick} 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"> <div className="flex items-start gap-2">
<Box size={14} className="text-primary mt-0.5 shrink-0" /> <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"> <span className="text-[0.8125rem] font-medium text-foreground truncate">
{flow.tree_name} {flow.tree_name}
</span> </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} {flow.tree_type}
</span> </span>
</div> </div>

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ export function ConfirmDialog({
'disabled:opacity-50 disabled:cursor-not-allowed', 'disabled:opacity-50 disabled:cursor-not-allowed',
confirmVariant === 'destructive' confirmVariant === 'destructive'
? 'bg-red-400/10 text-red-400 hover:bg-red-400/20 border border-red-400/20' ? '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} {isLoading ? 'Processing...' : confirmLabel}

View File

@@ -71,7 +71,7 @@ export function ContextMenu({ position, items, onClose }: ContextMenuProps) {
left: position.x, left: position.x,
top: position.y, 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) => ( {items.map((item) => (
<div key={item.id}> <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', 'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
item.variant === 'danger' item.variant === 'danger'
? 'text-rose-400 hover:bg-rose-500/10' ? 'text-rose-400 hover:bg-rose-500/10'
: 'text-foreground hover:bg-brand-border' : 'text-foreground hover:bg-border'
)} )}
> >
{item.icon && ( {item.icon && (

View File

@@ -65,7 +65,7 @@ export function CreateFlowDropdown({
<div className={cn('relative', className)}> <div className={cn('relative', className)}>
<button <button
onClick={() => setShowMenu(!showMenu)} 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} /> <Plus size={16} />
{label} {label}
@@ -74,7 +74,7 @@ export function CreateFlowDropdown({
{showMenu && ( {showMenu && (
<> <>
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} /> <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 */} {/* Troubleshooting */}
<Link <Link
to="/trees/new" to="/trees/new"

View File

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

View File

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

View File

@@ -110,7 +110,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
</div> </div>
<button <button
onClick={onClose} 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} /> <X size={16} />
</button> </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 ${ className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
msg.role === 'user' msg.role === 'user'
? 'bg-primary/15 text-foreground' ? '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" /> <MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
@@ -133,7 +133,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
))} ))}
{loading && ( {loading && (
<div className="flex justify-start"> <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" /> <Loader2 size={16} className="animate-spin text-primary" />
</div> </div>
</div> </div>
@@ -142,7 +142,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
{/* Suggested flows */} {/* Suggested flows */}
{suggestedFlows.length > 0 && ( {suggestedFlows.length > 0 && (
<div className="space-y-2 pt-2"> <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 Related Flows
</span> </span>
{suggestedFlows.map(flow => ( {suggestedFlows.map(flow => (
@@ -171,7 +171,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
<button <button
onClick={handleSend} onClick={handleSend}
disabled={!input.trim() || loading || initializing} 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} /> <Send size={16} />
</button> </button>

View File

@@ -11,7 +11,7 @@ export function CopilotToggle({ isOpen, onToggle }: CopilotToggleProps) {
return ( return (
<button <button
onClick={onToggle} 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" title="Open AI Copilot"
> >
<MessageCircle size={22} /> <MessageCircle size={22} />

View File

@@ -29,7 +29,7 @@ export function ActiveFlowPilotSessions() {
if (loading) { if (loading) {
return ( return (
<div className="glass-card-static"> <div className="card-flat">
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}> <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> <h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
</div> </div>
@@ -43,7 +43,7 @@ export function ActiveFlowPilotSessions() {
} }
return ( return (
<div className="glass-card-static"> <div className="card-flat">
<div <div
className="flex items-center justify-between px-5 py-3" className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }} style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -51,7 +51,7 @@ export function ActiveFlowPilotSessions() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3> <h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
{sessions.length > 0 && ( {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} {sessions.length}
</span> </span>
)} )}
@@ -67,7 +67,7 @@ export function ActiveFlowPilotSessions() {
{sessions.length === 0 ? ( {sessions.length === 0 ? (
<div className="px-5 py-8 text-center"> <div className="px-5 py-8 text-center">
<p className="text-sm text-muted-foreground">No active sessions</p> <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>
) : ( ) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4"> <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 <button
key={session.id} key={session.id}
onClick={() => navigate(`/pilot/${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"> <div className="flex items-start justify-between gap-2 mb-2">
<Sparkles size={14} className="shrink-0 text-primary mt-0.5" /> <Sparkles size={14} className="shrink-0 text-primary mt-0.5" />
<span <span
className={cn( 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 === 'guided' && 'bg-emerald-400/10 text-emerald-400',
session.confidence_tier === 'exploring' && 'bg-amber-400/10 text-amber-400', session.confidence_tier === 'exploring' && 'bg-amber-400/10 text-amber-400',
session.confidence_tier === 'discovery' && 'bg-blue-400/10 text-blue-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( className={cn(
'shrink-0 rounded-lg border px-3 py-1.5 text-[0.8125rem] font-medium transition-colors', 'shrink-0 rounded-lg border px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
activeFilter === f.id 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' : 'border-border bg-card text-muted-foreground hover:border-border/80 hover:text-foreground'
)} )}
> >

View File

@@ -20,7 +20,7 @@ export function KnowledgeBaseCards() {
] ]
return ( return (
<div className="glass-card-static"> <div className="card-flat">
<div <div
className="flex items-center justify-between px-5 py-3" className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }} style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -38,11 +38,14 @@ export function KnowledgeBaseCards() {
<button <button
key={item.label} key={item.label}
onClick={() => navigate(item.href)} 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 }} /> <item.icon size={20} style={{ color: item.color }} />
<p className="font-heading text-xl font-extrabold text-foreground">{item.value}</p> <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} {item.label}
</p> </p>
</button> </button>

View File

@@ -69,11 +69,11 @@ export function OnboardingChecklist() {
} }
return ( 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 */} {/* Progress bar */}
<div className="h-1 w-full bg-[rgba(255,255,255,0.04)]"> <div className="h-1 w-full bg-[rgba(255,255,255,0.04)]">
<div <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}%` }} style={{ width: `${progressPercent}%` }}
/> />
</div> </div>
@@ -82,15 +82,15 @@ export function OnboardingChecklist() {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<div> <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 Getting Started
</p> </p>
<p className="text-sm text-foreground mt-0.5"> <p className="text-sm text-foreground mt-0.5">
{isAllDone ? ( {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>
<span className="text-gradient-brand font-semibold">{completedCount}</span> <span className="text-accent-text font-semibold">{completedCount}</span>
{' '}of {totalCount} complete {' '}of {totalCount} complete
</span> </span>
)} )}
@@ -126,11 +126,11 @@ export function OnboardingChecklist() {
className={cn( className={cn(
'flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors', 'flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors',
done done
? 'bg-gradient-brand border-transparent' ? 'bg-primary border-transparent'
: 'border-border' : 'border-border'
)} )}
> >
{done && <Check size={12} className="text-[#101114]" />} {done && <Check size={12} className="text-white" />}
</span> </span>
{/* Label */} {/* Label */}

View File

@@ -17,7 +17,7 @@ interface OpenSessionsProps {
export function OpenSessions({ sessions }: OpenSessionsProps) { export function OpenSessions({ sessions }: OpenSessionsProps) {
return ( 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)' }}> <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> <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"> <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}` ? `Step ${session.stepNumber} of ${session.totalSteps}`
: 'In progress'} : 'In progress'}
<span className="mx-1.5 text-[var(--text-dimmed)]">&middot;</span> <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>
</div> </div>
<Link <Link
to={getTreeNavigatePath(session.treeId, session.treeType)} to={getTreeNavigatePath(session.treeId, session.treeType)}
state={{ sessionId: session.id }} 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 Resume
</Link> </Link>

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ export function PreparedSessions() {
} }
return ( 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 justify-between mb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ClipboardList className="h-4 w-4 text-cyan-400" /> <ClipboardList className="h-4 w-4 text-cyan-400" />

View File

@@ -12,7 +12,7 @@ export function QuickActions() {
const navigate = useNavigate() const navigate = useNavigate()
return ( 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)' }}> <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> <h3 className="font-heading text-sm font-bold text-foreground">Quick Actions</h3>
</div> </div>
@@ -21,7 +21,7 @@ export function QuickActions() {
<button <button
key={label} key={label}
onClick={() => navigate(href)} 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 <span
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg" 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) => ( {stats.map((stat, i) => (
<div <div
key={stat.label} 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` }} 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} {stat.label}
</p> </p>
<p <p
className={cn( className={cn(
'mt-1 font-heading text-2xl font-extrabold tracking-tight', 'mt-1 font-heading text-2xl font-extrabold tracking-tight',
stat.gradient && 'text-gradient-brand', stat.gradient && 'text-accent-text',
stat.color stat.color
)} )}
style={stat.color && !stat.color.startsWith('text-') ? { color: stat.color } : undefined} 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) { export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivityProps) {
return ( return (
<div className="glass-card-static"> <div className="card-flat">
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}> <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> <h3 className="font-heading text-sm font-bold text-foreground">Recent Activity</h3>
</div> </div>
@@ -39,7 +39,7 @@ export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivi
}} }}
> >
<span <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 }} style={{ background: item.iconBg }}
> >
<item.icon size={16} style={{ color: item.iconColor }} /> <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"> <div className="flex-1 min-w-0 pt-0.5">
<p className="text-sm text-foreground">{item.description}</p> <p className="text-sm text-foreground">{item.description}</p>
</div> </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} {item.timestamp}
</span> </span>
</div> </div>

View File

@@ -41,7 +41,7 @@ export function RecentFlowPilotSessions() {
if (sessions.length === 0) return null if (sessions.length === 0) return null
return ( return (
<div className="glass-card-static"> <div className="card-flat">
<div <div
className="flex items-center justify-between px-5 py-3" className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }} style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -73,7 +73,7 @@ export function RecentFlowPilotSessions() {
{session.problem_summary || 'Session'} {session.problem_summary || 'Session'}
</p> </p>
</div> </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)} {timeAgo(session.resolved_at || session.created_at)}
</span> </span>
</button> </button>

View File

@@ -19,12 +19,12 @@ export function SectionGroup({ title, count, defaultOpen = true, delay = 150, ch
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
className="flex w-full items-center gap-2 py-2" 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"> <span className="font-heading text-[0.8125rem] font-bold uppercase tracking-[0.04em] text-foreground">
{title} {title}
</span> </span>
{count !== undefined && ( {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} {count}
</span> </span>
)} )}

View File

@@ -21,7 +21,7 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
if (sessions.length === 0) return null if (sessions.length === 0) return null
return ( 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)' }}> <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> <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"> <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', 'h-2 w-2 rounded-full',
session.status === 'completed' ? 'bg-green-500' : session.status === 'completed' ? 'bg-green-500' :
session.status === 'in_progress' ? 'bg-amber-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> </span>
{/* Ticket */} {/* 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 || '—'} {session.ticketNumber || '—'}
</span> </span>

View File

@@ -32,7 +32,7 @@ export function StartSessionInput() {
} }
return ( 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="px-5 py-4 sm:px-6 sm:py-5">
<div className="relative"> <div className="relative">
<Sparkles <Sparkles
@@ -50,14 +50,14 @@ export function StartSessionInput() {
/> />
</div> </div>
<div className="mt-3 flex items-center justify-between"> <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 <button
type="button" type="button"
onClick={() => setMode('guided')} onClick={() => setMode('guided')}
className={cn( 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' mode === 'guided'
? 'bg-primary/10 text-foreground' ? 'bg-accent-dim text-foreground tab-active-shadow'
: 'text-muted-foreground hover:text-foreground' : 'text-muted-foreground hover:text-foreground'
)} )}
> >
@@ -68,9 +68,9 @@ export function StartSessionInput() {
type="button" type="button"
onClick={() => setMode('chat')} onClick={() => setMode('chat')}
className={cn( 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' mode === 'chat'
? 'bg-primary/10 text-foreground' ? 'bg-accent-dim text-foreground tab-active-shadow'
: 'text-muted-foreground hover:text-foreground' : 'text-muted-foreground hover:text-foreground'
)} )}
> >

View File

@@ -25,7 +25,7 @@ export function TeamSummary() {
] ]
return ( return (
<div className="glass-card-static"> <div className="card-flat">
<div <div
className="flex items-center justify-between px-5 py-3" className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--glass-border)' }} style={{ borderBottom: '1px solid var(--glass-border)' }}
@@ -43,11 +43,14 @@ export function TeamSummary() {
<button <button
key={item.label} key={item.label}
onClick={() => navigate(item.href)} 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 }} /> <item.icon size={20} style={{ color: item.color }} />
<p className="font-heading text-xl font-extrabold text-foreground">{item.value}</p> <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} {item.label}
</p> </p>
</button> </button>

View File

@@ -49,7 +49,7 @@ export function TreeListItem({
<p className="font-heading text-sm font-semibold text-foreground truncate">{name}</p> <p className="font-heading text-sm font-semibold text-foreground truncate">{name}</p>
<div className="mt-0.5 flex items-center gap-2"> <div className="mt-0.5 flex items-center gap-2">
{tags.slice(0, 3).map(tag => ( {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} {tag}
</span> </span>
))} ))}
@@ -64,13 +64,13 @@ export function TreeListItem({
{category && ( {category && (
<> <>
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: categoryColor }} /> <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> </div>
{/* Usage count */} {/* 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 {usageCount} uses
</div> </div>

View File

@@ -34,7 +34,7 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
const days = useMemo(() => getWeekDays(), []) const days = useMemo(() => getWeekDays(), [])
return ( 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)' }}> <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" /> <Calendar size={16} className="text-muted-foreground" />
<h3 className="font-heading text-sm font-bold text-foreground">This Week</h3> <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)', 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} {day.label}
</span> </span>
<div className={`text-sm font-heading font-bold ${day.isToday ? 'text-foreground' : 'text-muted-foreground'}`}> <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-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> </div>
)) ))
)} )}

View File

@@ -50,7 +50,7 @@ export function AIPromptDialog({
/> />
{/* Dialog */} {/* 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"> <div className="flex items-center gap-2 mb-4">
<Sparkles className="h-5 w-5 text-primary" /> <Sparkles className="h-5 w-5 text-primary" />
<h2 className="text-lg font-heading font-semibold text-foreground"> <h2 className="text-lg font-heading font-semibold text-foreground">
@@ -83,14 +83,14 @@ export function AIPromptDialog({
<button <button
onClick={onClose} onClick={onClose}
disabled={isGenerating} 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 Cancel
</button> </button>
<button <button
onClick={handleGenerate} onClick={handleGenerate}
disabled={!prompt.trim() || isGenerating} 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 ? ( {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 ${ className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
msg.role === 'user' msg.role === 'user'
? 'bg-primary/15 text-foreground' ? '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" /> <MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
@@ -59,7 +59,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
))} ))}
{isLoading && ( {isLoading && (
<div className="flex justify-start"> <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" /> <Loader2 size={16} className="animate-spin text-primary" />
</div> </div>
</div> </div>
@@ -84,7 +84,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
<button <button
onClick={onSend} onClick={onSend}
disabled={!input.trim() || isLoading} 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} /> <Send size={16} />
</button> </button>

View File

@@ -65,7 +65,7 @@ export function EditorAIPanel({
</div> </div>
<button <button
onClick={onClose} 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} /> <X size={16} />
</button> </button>

View File

@@ -29,7 +29,7 @@ export function NodeSummary({ node, flowName, flowType, nodeCount }: NodeSummary
{flowName || 'Untitled Flow'} {flowName || 'Untitled Flow'}
</span> </span>
</div> </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> <span>{flowType || 'flow'}</span>
{nodeCount !== undefined && <span>{nodeCount} nodes</span>} {nodeCount !== undefined && <span>{nodeCount} nodes</span>}
</div> </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="border-b px-3 py-2.5" style={{ borderColor: 'var(--glass-border)' }}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Icon className={`h-3.5 w-3.5 ${colorClass}`} /> <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} {node.type}
</span> </span>
</div> </div>

View File

@@ -26,9 +26,9 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
const config = STATUS_CONFIG[s.status] const config = STATUS_CONFIG[s.status]
const StatusIcon = config.icon const StatusIcon = config.icon
return ( 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"> <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, ' ')} {s.action_type.replace(/_/g, ' ')}
</span> </span>
<span className={`flex items-center gap-1 text-xs ${config.color}`}> <span className={`flex items-center gap-1 text-xs ${config.color}`}>
@@ -39,7 +39,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
{s.target_node_id && ( {s.target_node_id && (
<p className="mt-1 text-xs text-muted-foreground truncate">Node: {s.target_node_id}</p> <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()} {new Date(s.created_at).toLocaleDateString()}
</p> </p>
</div> </div>

View File

@@ -12,7 +12,7 @@ const STATUS_CONFIG = {
paused: { icon: Pause, color: 'text-amber-400', label: 'Paused' }, paused: { icon: Pause, color: 'text-amber-400', label: 'Paused' },
resolved: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Resolved' }, resolved: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Resolved' },
escalated: { icon: ArrowUpRight, color: 'text-amber-400', label: 'Escalated' }, 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 } as const
export function AISessionListItem({ session }: AISessionListItemProps) { export function AISessionListItem({ session }: AISessionListItemProps) {
@@ -22,7 +22,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
return ( return (
<Link <Link
to={`/pilot/${session.id}`} 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 items-start justify-between gap-3">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@@ -31,7 +31,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
</p> </p>
<div className="mt-1.5 flex items-center gap-3 flex-wrap"> <div className="mt-1.5 flex items-center gap-3 flex-wrap">
{session.problem_domain && ( {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} {session.problem_domain}
</span> </span>
)} )}
@@ -42,7 +42,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{session.step_count} steps {session.step_count} steps
</span> </span>
<span className="text-xs text-[#5a6170]"> <span className="text-xs text-text-muted">
{new Date(session.created_at).toLocaleDateString(undefined, { {new Date(session.created_at).toLocaleDateString(undefined, {
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
})} })}
@@ -50,7 +50,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
</div> </div>
</div> </div>
{session.session_rating && ( {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)} {'★'.repeat(session.session_rating)}
</span> </span>
)} )}

View File

@@ -30,12 +30,12 @@ export function ConfidenceIndicator({ tier, score, className }: ConfidenceIndica
return ( return (
<div className={cn('group relative inline-flex items-center gap-2', className)}> <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={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 */} {/* 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"> <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="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)}% Confidence: {Math.round(score * 100)}%
</p> </p>
</div> </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..." placeholder="e.g. I've exhausted all networking diagnostics and suspect this is a firewall policy issue that requires senior admin access..."
rows={4} 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. Minimum 5 characters. This will be shown to the engineer who picks up.
</p> </p>
</div> </div>
@@ -70,7 +70,7 @@ export function EscalateModal({ open, onClose, onEscalate, isProcessing, hasPsaT
<button <button
onClick={handleSubmit} onClick={handleSubmit}
disabled={!reason.trim() || reason.trim().length < 5 || isProcessing} 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 ? ( {isProcessing ? (
<Loader2 size={14} className="animate-spin" /> <Loader2 size={14} className="animate-spin" />

View File

@@ -80,7 +80,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between px-1"> <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}) Awaiting pickup ({sessions.length})
</h3> </h3>
<button <button
@@ -93,7 +93,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
</div> </div>
{sessions.map((session) => ( {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> <div>
<p className="text-sm font-semibold text-foreground"> <p className="text-sm font-semibold text-foreground">
{session.problem_summary || 'Untitled session'} {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"> <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{session.problem_domain && ( {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} {session.problem_domain}
</span> </span>
)} )}
@@ -129,7 +129,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
<button <button
onClick={() => handlePickup(session.id)} 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 Pick Up Session
</button> </button>

View File

@@ -70,8 +70,8 @@ export function FlowPilotActionBar({
<> <>
{/* Bottom bar — fixed to viewport bottom, works regardless of height chain */} {/* Bottom bar — fixed to viewport bottom, works regardless of height chain */}
<div <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" 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={{ borderColor: 'var(--glass-border)', background: 'rgba(16, 17, 20, 0.95)', backdropFilter: 'blur(16px)', left: 'var(--sidebar-w, 0px)' }} style={{ left: 'var(--sidebar-w, 0px)' }}
> >
<div className="flex gap-2 sm:gap-3"> <div className="flex gap-2 sm:gap-3">
<button <button
@@ -118,7 +118,7 @@ export function FlowPilotActionBar({
{/* Resolve modal */} {/* Resolve modal */}
{showResolve && ( {showResolve && (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm"> <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> <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> <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 <textarea
@@ -151,7 +151,7 @@ export function FlowPilotActionBar({
{/* Close/Abandon confirmation */} {/* Close/Abandon confirmation */}
{showAbandon && ( {showAbandon && (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm"> <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> <h3 className="font-heading text-lg font-semibold text-foreground mb-1">Close Session</h3>
<p className="text-sm text-muted-foreground mb-4"> <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. 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 ( return (
<div className="flex items-center justify-center min-h-[50vh]"> <div className="flex items-center justify-center min-h-[50vh]">
<div className="text-center"> <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" /> <Sparkles size={24} className="text-primary animate-pulse" />
</div> </div>
<p className="text-sm font-medium text-foreground">Analyzing your issue...</p> <p className="text-sm font-medium text-foreground">Analyzing your issue...</p>
@@ -126,7 +126,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
</p> </p>
</div> </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 */} {/* Selected ticket card */}
{selectedTicket && selectedTicketId && ( {selectedTicket && selectedTicketId && (
<div className="rounded-xl border border-primary/20 bg-primary/5 p-4"> <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.company_name && <span>{selectedTicket.company_name}</span>}
{selectedTicket.priority_name && ( {selectedTicket.priority_name && (
<> <>
<span className="text-[#5a6170]">&bull;</span> <span className="text-text-muted">&bull;</span>
<span>{selectedTicket.priority_name}</span> <span>{selectedTicket.priority_name}</span>
</> </>
)} )}
{selectedTicket.status_name && ( {selectedTicket.status_name && (
<> <>
<span className="text-[#5a6170]">&bull;</span> <span className="text-text-muted">&bull;</span>
<span>{selectedTicket.status_name}</span> <span>{selectedTicket.status_name}</span>
</> </>
)} )}
@@ -190,7 +190,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
onClick={() => setShowLogs(!showLogs)} onClick={() => setShowLogs(!showLogs)}
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${ className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
showLogs 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' : '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 ${ className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
psaConnection psaConnection
? 'bg-card/50 text-muted-foreground border border-border hover:text-foreground hover:border-primary/20' ? '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'} 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 */} {/* Submit */}
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-between"> <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 FlowPilot will analyze your input and guide you through diagnosis
</p> </p>
<button <button
onClick={handleSubmit} onClick={handleSubmit}
disabled={!hasContent} 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} {submitLabel}
</button> </button>

View File

@@ -51,7 +51,7 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
? 'border-border/50 opacity-50' ? 'border-border/50 opacity-50'
: 'border-border focus-within:border-[rgba(6,182,212,0.3)]' : '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 <textarea
ref={textareaRef} ref={textareaRef}
@@ -70,7 +70,7 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
className={cn( className={cn(
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg transition-all', 'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg transition-all',
message.trim() && !isDisabled 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' : '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)]', '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', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40',
isSelected isSelected
? 'border-primary/40 bg-primary/10' ? 'border-primary/40 bg-accent-dim'
: 'border-border bg-card/50', : 'border-border bg-card/50',
disabled && 'pointer-events-none opacity-60' 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"> <div className="flex items-center gap-3 text-xs text-muted-foreground overflow-x-auto">
{session.problem_domain && ( {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} {session.problem_domain}
</span> </span>
)} )}
@@ -177,7 +177,7 @@ export function FlowPilotSession({
) : null} ) : null}
{session.problem_summary && ( {session.problem_summary && (
<div> <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> <p className="text-sm text-foreground">{session.problem_summary}</p>
</div> </div>
)} )}
@@ -238,7 +238,7 @@ export function FlowPilotSession({
{/* Problem summary */} {/* Problem summary */}
{session.problem_summary && ( {session.problem_summary && (
<div> <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 Problem
</h4> </h4>
<p className="text-sm text-foreground">{session.problem_summary}</p> <p className="text-sm text-foreground">{session.problem_summary}</p>
@@ -248,10 +248,10 @@ export function FlowPilotSession({
{/* Domain */} {/* Domain */}
{session.problem_domain && ( {session.problem_domain && (
<div> <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 Domain
</h4> </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} {session.problem_domain}
</span> </span>
</div> </div>
@@ -259,7 +259,7 @@ export function FlowPilotSession({
{/* Confidence */} {/* Confidence */}
<div> <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 Confidence
</h4> </h4>
<ConfidenceIndicator <ConfidenceIndicator
@@ -271,7 +271,7 @@ export function FlowPilotSession({
{/* Matched flow */} {/* Matched flow */}
{session.matched_flow_id && ( {session.matched_flow_id && (
<div> <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 Matched flow
</h4> </h4>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -330,13 +330,12 @@ export function FlowPilotSession({
{/* Paused banner */} {/* Paused banner */}
{session.status === 'paused' && onResume && ( {session.status === 'paused' && onResume && (
<div <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" 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"
style={{ borderColor: 'var(--glass-border)', background: 'rgba(16, 17, 20, 0.8)', backdropFilter: 'blur(12px)' }}
> >
<span className="text-sm text-muted-foreground">Session paused</span> <span className="text-sm text-muted-foreground">Session paused</span>
<button <button
onClick={onResume} 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} /> <Play size={14} />
Resume Session Resume Session

View File

@@ -62,7 +62,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
return ( return (
<button <button
onClick={() => setIsCollapsed(false)} 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"> <div className="flex items-center gap-3">
<Icon size={16} className="shrink-0 text-muted-foreground" /> <Icon size={16} className="shrink-0 text-muted-foreground" />
@@ -76,14 +76,14 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
// Expanded completed step // Expanded completed step
if (!isCurrentStep && !isCollapsed) { if (!isCurrentStep && !isCollapsed) {
return ( 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 <button
onClick={() => setIsCollapsed(true)} onClick={() => setIsCollapsed(true)}
className="mb-2 flex w-full items-center justify-between text-left" className="mb-2 flex w-full items-center justify-between text-left"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Icon size={16} className="text-muted-foreground" /> <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} Step {step.step_order + 1}
</span> </span>
</div> </div>
@@ -98,7 +98,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
return ( return (
<div <div
className={cn( 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' 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"> <div className="flex items-start gap-3 mb-4">
<span className={cn( <span className={cn(
'mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg', '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} /> <Icon size={14} />
</span> </span>
@@ -180,7 +180,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
window.open('/script-builder?from=flowpilot', '_blank') window.open('/script-builder?from=flowpilot', '_blank')
onRespond({ action_result: { success: true, details: 'Opened Script Builder' } }) 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 Open Script Builder
</button> </button>
@@ -191,7 +191,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
<div className="flex flex-col gap-2 sm:flex-row"> <div className="flex flex-col gap-2 sm:flex-row">
<button <button
onClick={() => handleActionComplete(true)} 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 I've completed this action
</button> </button>
@@ -208,7 +208,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
{!isResolutionSuggestion && step.allow_skip && ( {!isResolutionSuggestion && step.allow_skip && (
<button <button
onClick={handleSkip} 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} /> <SkipForward size={12} />
I can't check this right now 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="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"> <div className="flex items-center gap-2">
<Terminal size={14} className="text-primary" /> <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 Script Generator
</span> </span>
</div> </div>
@@ -106,7 +106,7 @@ export function InSessionScriptGenerator({
<button <button
onClick={handleGenerate} onClick={handleGenerate}
disabled={isGenerating} 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 ? ( {isGenerating ? (
<Loader2 size={14} className="animate-spin" /> <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"> <div className="flex flex-col gap-2 pt-1 sm:flex-row">
<button <button
onClick={() => handleContinue(true)} 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 Script worked continue
</button> </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 ${ className={`w-full text-left rounded-xl border p-3 space-y-2 transition-all ${
isSelected isSelected
? 'border-primary/30 bg-primary/5' ? '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"> <div className="flex items-start justify-between gap-2">
<p className="text-sm font-semibold text-foreground line-clamp-2">{proposal.title}</p> <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} /> <TypeIcon size={10} />
{typeConfig.label} {typeConfig.label}
</span> </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"> <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{proposal.problem_domain && ( {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} {proposal.problem_domain}
</span> </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"> <div className="mt-3 flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
{proposal.problem_domain && ( {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} {proposal.problem_domain}
</span> </span>
)} )}
@@ -89,8 +89,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Content */} {/* Content */}
<div className="flex-1 overflow-y-auto p-6 space-y-5"> <div className="flex-1 overflow-y-auto p-6 space-y-5">
{/* Source session link */} {/* Source session link */}
<div className="glass-card-static p-4"> <div className="card-flat p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Source Session</h4> <h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Source Session</h4>
<Link <Link
to={`/pilot/${proposal.source_session_id}`} to={`/pilot/${proposal.source_session_id}`}
target="_blank" target="_blank"
@@ -105,8 +105,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{proposal.proposed_diff && (() => { {proposal.proposed_diff && (() => {
const diff = proposal.proposed_diff as { diff_description?: string; new_nodes?: Array<{ title?: string; question?: string; description?: string }> } const diff = proposal.proposed_diff as { diff_description?: string; new_nodes?: Array<{ title?: string; question?: string; description?: string }> }
return ( return (
<div className="glass-card-static border-l-2 border-l-amber-500 p-4"> <div className="card-flat 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> <h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Proposed Changes</h4>
{diff.diff_description && ( {diff.diff_description && (
<p className="text-sm text-foreground">{diff.diff_description}</p> <p className="text-sm text-foreground">{diff.diff_description}</p>
)} )}
@@ -126,10 +126,10 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
})()} })()}
{/* Flow data preview */} {/* Flow data preview */}
<div className="glass-card-static p-4"> <div className="card-flat p-4">
<button <button
onClick={() => setShowFlowData(!showFlowData)} 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} />} {showFlowData ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
Flow Data (JSON) Flow Data (JSON)
@@ -143,8 +143,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Supporting sessions */} {/* Supporting sessions */}
{proposal.supporting_session_ids.length > 1 && ( {proposal.supporting_session_ids.length > 1 && (
<div className="glass-card-static p-4"> <div className="card-flat p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2"> <h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">
Supporting Sessions ({proposal.supporting_session_ids.length}) Supporting Sessions ({proposal.supporting_session_ids.length})
</h4> </h4>
<div className="space-y-1"> <div className="space-y-1">
@@ -164,8 +164,8 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Review info (for already-reviewed proposals) */} {/* Review info (for already-reviewed proposals) */}
{proposal.reviewed_at && ( {proposal.reviewed_at && (
<div className="glass-card-static p-4"> <div className="card-flat p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Review</h4> <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"> <p className="text-sm text-foreground">
<span className="capitalize">{proposal.status}</span> on{' '} <span className="capitalize">{proposal.status}</span> on{' '}
{new Date(proposal.reviewed_at).toLocaleString()} {new Date(proposal.reviewed_at).toLocaleString()}
@@ -180,8 +180,7 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
{/* Review actions bar */} {/* Review actions bar */}
{canReview && ( {canReview && (
<div <div
className="border-t px-5 py-3 space-y-3" className="border-t border-border bg-card px-5 py-3 space-y-3"
style={{ borderColor: 'var(--glass-border)', background: 'rgba(16, 17, 20, 0.8)', backdropFilter: 'blur(12px)' }}
> >
{/* Notes input */} {/* Notes input */}
<input <input

View File

@@ -38,7 +38,7 @@ export function SessionBriefing({
const pkg = escalationPackage const pkg = escalationPackage
return ( 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> <div>
<h3 className="font-heading text-base font-semibold text-foreground"> <h3 className="font-heading text-base font-semibold text-foreground">
Escalation from {originalEngineerName || 'another engineer'} Escalation from {originalEngineerName || 'another engineer'}
@@ -51,7 +51,7 @@ export function SessionBriefing({
{/* Problem */} {/* Problem */}
{pkg.problem_summary && ( {pkg.problem_summary && (
<div> <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> <p className="text-sm text-foreground">{pkg.problem_summary}</p>
</div> </div>
)} )}
@@ -59,7 +59,7 @@ export function SessionBriefing({
{/* Escalation reason */} {/* Escalation reason */}
{pkg.escalation_reason && ( {pkg.escalation_reason && (
<div> <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> <p className="text-sm text-amber-400">{pkg.escalation_reason}</p>
</div> </div>
)} )}
@@ -69,7 +69,7 @@ export function SessionBriefing({
<div> <div>
<button <button
onClick={() => setShowSteps(!showSteps)} 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} />} {showSteps ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
Steps taken ({pkg.steps_tried.length}) Steps taken ({pkg.steps_tried.length})
@@ -92,7 +92,7 @@ export function SessionBriefing({
{/* Remaining hypotheses */} {/* Remaining hypotheses */}
{pkg.remaining_hypotheses && pkg.remaining_hypotheses.length > 0 && ( {pkg.remaining_hypotheses && pkg.remaining_hypotheses.length > 0 && (
<div> <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"> <ul className="space-y-1">
{pkg.remaining_hypotheses.map((h, i) => ( {pkg.remaining_hypotheses.map((h, i) => (
<li key={i} className="text-sm text-foreground flex items-start gap-2"> <li key={i} className="text-sm text-foreground flex items-start gap-2">
@@ -107,7 +107,7 @@ export function SessionBriefing({
{/* Suggested next steps */} {/* Suggested next steps */}
{pkg.suggested_next_steps && pkg.suggested_next_steps.length > 0 && ( {pkg.suggested_next_steps && pkg.suggested_next_steps.length > 0 && (
<div> <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"> <ul className="space-y-1">
{pkg.suggested_next_steps.map((s, i) => ( {pkg.suggested_next_steps.map((s, i) => (
<li key={i} className="text-sm text-foreground flex items-start gap-2"> <li key={i} className="text-sm text-foreground flex items-start gap-2">
@@ -125,7 +125,7 @@ export function SessionBriefing({
<button <button
onClick={onContinue} onClick={onContinue}
disabled={isProcessing} 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} /> <ArrowRight size={14} />
Continue Where They Left Off Continue Where They Left Off
@@ -160,7 +160,7 @@ export function SessionBriefing({
<button <button
onClick={() => freshContext.trim() && onFresh(freshContext.trim())} onClick={() => freshContext.trim() && onFresh(freshContext.trim())}
disabled={!freshContext.trim() || isProcessing} 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} /> <ArrowRight size={14} />
Start Diagnosis Start Diagnosis

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
return ( return (
<div className="flex items-center gap-1.5 py-1"> <div className="flex items-center gap-1.5 py-1">
<Loader2 size={10} className="animate-spin text-muted-foreground" /> <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> </div>
) )
} }
@@ -47,20 +47,20 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
return ( return (
<div className="space-y-2"> <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 Similar Past Sessions
</h4> </h4>
{sessions.map((session) => ( {sessions.map((session) => (
<Link <Link
key={session.id} key={session.id}
to={`/pilot/${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"> <div className="flex items-start justify-between gap-2">
<p className="text-xs text-foreground line-clamp-2"> <p className="text-xs text-foreground line-clamp-2">
{session.problem_summary || 'Untitled session'} {session.problem_summary || 'Untitled session'}
</p> </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)}% {Math.round(session.similarity * 100)}%
</span> </span>
</div> </div>
@@ -71,13 +71,13 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
)} )}
<div className="flex items-center gap-2 mt-1.5"> <div className="flex items-center gap-2 mt-1.5">
{session.problem_domain && ( {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} {session.problem_domain}
</span> </span>
)} )}
<span <span
className={cn( className={cn(
'text-[0.5rem] font-label uppercase', 'text-[0.5rem] font-sans text-xs uppercase',
session.status === 'resolved' session.status === 'resolved'
? 'text-emerald-400' ? 'text-emerald-400'
: session.status === 'escalated' : session.status === 'escalated'

View File

@@ -11,7 +11,7 @@ export function GuideCard({ guide }: GuideCardProps) {
return ( return (
<Link <Link
to={`/guides/${guide.slug}`} 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 items-start gap-3.5">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10"> <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"> <p className="text-xs text-muted-foreground leading-relaxed">
{guide.summary} {guide.summary}
</p> </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'} {guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
</span> </span>
</div> </div>

View File

@@ -18,7 +18,7 @@ export function GuideSection({ section, index }: GuideSectionProps) {
<ol className="space-y-3 pl-8"> <ol className="space-y-3 pl-8">
{section.steps.map((step, i) => ( {section.steps.map((step, i) => (
<li key={i} className="relative"> <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}. {i + 1}.
</span> </span>
<p <p

View File

@@ -55,7 +55,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
return ( return (
<div <div
className={cn( 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), confidenceColor(node.confidence_score),
node.user_approved && 'opacity-75', 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 items-start justify-between gap-3">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1"> <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} {node.node_type}
</span> </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)}%) {confidenceLabel(node.confidence_score)} ({Math.round(node.confidence_score * 100)}%)
</span> </span>
{node.user_approved && ( {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 && ( {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> </div>
@@ -92,7 +92,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<button <button
onClick={() => handleAction('edit', { content: { ...node.content, question: editContent, content: editContent } })} onClick={() => handleAction('edit', { content: { ...node.content, question: editContent, content: editContent } })}
disabled={busy} 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 Save
</button> </button>
@@ -143,7 +143,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<button <button
onClick={() => handleAction('regenerate')} onClick={() => handleAction('regenerate')}
disabled={busy} 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" title="Regenerate"
> >
<RotateCcw size={14} /> <RotateCcw size={14} />
@@ -151,7 +151,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
<button <button
onClick={() => handleAction('insert_after', { content: { question: 'New node', type: node.node_type } })} onClick={() => handleAction('insert_after', { content: { question: 'New node', type: node.node_type } })}
disabled={busy} 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" title="Insert after"
> >
<Plus size={14} /> <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"> <div className="mt-2 space-y-1 pl-3 border-l border-border">
{options.map((opt, i) => ( {options.map((opt, i) => (
<p key={i} className="text-xs text-muted-foreground"> <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> </p>
))} ))}
</div> </div>
@@ -192,7 +192,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
{/* Source excerpt */} {/* Source excerpt */}
{node.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} Source: {node.source_excerpt}
</p> </p>
)} )}

View File

@@ -30,7 +30,7 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
return ( return (
<div className="flex flex-col h-full gap-4"> <div className="flex flex-col h-full gap-4">
{/* Header */} {/* 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> <div>
<h2 className="text-lg font-heading font-semibold text-foreground">{flowTitle}</h2> <h2 className="text-lg font-heading font-semibold text-foreground">{flowTitle}</h2>
{flowDescription && ( {flowDescription && (
@@ -88,10 +88,10 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
</div> </div>
{/* Nodes panel */} {/* 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)' }}> <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="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'} {kbImport.target_type === 'troubleshooting' ? 'Troubleshooting' : 'Project'}
</span> </span>
</div> </div>
@@ -119,7 +119,7 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
onClick={onDiscard} onClick={onDiscard}
disabled={loading} disabled={loading}
className={cn( 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', '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)]', 'hover:text-foreground hover:border-[rgba(255,255,255,0.12)]',
'disabled:opacity-50 disabled:cursor-not-allowed' 'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -131,9 +131,9 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
onClick={() => onCommit()} onClick={() => onCommit()}
disabled={loading || nodes.length === 0} disabled={loading || nodes.length === 0}
className={cn( className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-semibold transition-all', 'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-semibold transition-all',
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20', 'bg-primary text-white',
'hover:opacity-90 active:scale-[0.97]', 'hover:brightness-110 active:scale-[0.98]',
'disabled:opacity-50 disabled:cursor-not-allowed' 'disabled:opacity-50 disabled:cursor-not-allowed'
)} )}
> >

View File

@@ -24,11 +24,11 @@ export function SourcePanel({ sourceText, sourceFormat, highlightExcerpt }: Sour
}, [sourceText, highlightExcerpt]) }, [sourceText, highlightExcerpt])
return ( 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)' }}> <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" /> <FileText size={16} className="text-muted-foreground" />
<span className="text-sm font-medium text-foreground">Source Document</span> <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} {sourceFormat}
</span> </span>
</div> </div>

View File

@@ -31,9 +31,9 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
<button <button
onClick={onViewFlow} onClick={onViewFlow}
className={cn( className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-semibold transition-all', 'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-semibold transition-all',
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20', 'bg-primary text-white',
'hover:opacity-90 active:scale-[0.97]' 'hover:brightness-110 active:scale-[0.98]'
)} )}
> >
View Flow View Flow
@@ -42,7 +42,7 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
<button <button
onClick={onConvertAnother} onClick={onConvertAnother}
className={cn( 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', '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)]' '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"> <div className="max-w-3xl mx-auto space-y-6">
{/* Quota info */} {/* Quota info */}
{quota && ( {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"> <div className="flex items-center gap-3">
<Sparkles size={18} className="text-primary" /> <Sparkles size={18} className="text-primary" />
<div> <div>
@@ -110,9 +110,9 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
<button <button
onClick={() => setMode('paste')} onClick={() => setMode('paste')}
className={cn( 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' 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)]' : '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 <button
onClick={() => setMode('file')} onClick={() => setMode('file')}
className={cn( 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' 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)]' : '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> </div>
{/* Content area */} {/* Content area */}
<div className="glass-card-static p-5 space-y-4"> <div className="card-flat p-5 space-y-4">
{mode === 'paste' ? ( {mode === 'paste' ? (
<> <>
<div> <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) Title (optional)
</label> </label>
<Input <Input
@@ -152,7 +152,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
/> />
</div> </div>
<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 KB Article Content
</label> </label>
<Textarea <Textarea
@@ -223,7 +223,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
{/* Target type selector */} {/* Target type selector */}
<div> <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 Target Flow Type
</p> </p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <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} key={t.value}
onClick={() => setTargetType(t.value)} onClick={() => setTargetType(t.value)}
className={cn( className={cn(
'glass-card-static p-4 text-left transition-all', 'card-flat p-4 text-left transition-all',
targetType === t.value targetType === t.value
? 'border-primary/30 bg-primary/5' ? 'border-primary/30 bg-primary/5'
: 'hover:border-[rgba(255,255,255,0.12)]' : 'hover:border-[rgba(255,255,255,0.12)]'
@@ -250,9 +250,9 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
onClick={handleSubmit} onClick={handleSubmit}
disabled={!canSubmit || loading || (quota != null && !quota.can_convert)} disabled={!canSubmit || loading || (quota != null && !quota.can_convert)}
className={cn( className={cn(
'w-full flex items-center justify-center gap-2 px-6 py-3 rounded-[10px] text-sm font-semibold transition-all', 'w-full flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold transition-all',
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20', 'bg-primary text-white',
'hover:opacity-90 active:scale-[0.97]', 'hover:brightness-110 active:scale-[0.98]',
'disabled:opacity-50 disabled:cursor-not-allowed' 'disabled:opacity-50 disabled:cursor-not-allowed'
)} )}
> >

View File

@@ -1,6 +1,6 @@
import { useEffect, useState, useCallback } from 'react' import { useEffect, useState, useCallback } from 'react'
import { useLocation, useNavigate, Link } from 'react-router-dom' 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 { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions' import { usePermissions } from '@/hooks/usePermissions'
import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { useUserPreferencesStore } from '@/store/userPreferencesStore'
@@ -16,7 +16,7 @@ export function AppLayout() {
const navigate = useNavigate() const navigate = useNavigate()
const { user, logout } = useAuthStore() const { user, logout } = useAuthStore()
const { effectiveRole } = usePermissions() const { effectiveRole } = usePermissions()
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed) const sidebarPinned = useUserPreferencesStore(s => s.sidebarPinned)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
// Close mobile menu on route change // Close mobile menu on route change
@@ -54,8 +54,8 @@ export function AppLayout() {
{ path: '/', label: 'Dashboard', icon: LayoutGrid }, { path: '/', label: 'Dashboard', icon: LayoutGrid },
{ path: '/sessions', label: 'Active Sessions', icon: Clock }, { path: '/sessions', label: 'Active Sessions', icon: Clock },
{ path: '/escalations', label: 'Escalations', icon: AlertTriangle }, { path: '/escalations', label: 'Escalations', icon: AlertTriangle },
{ path: '/trees', label: 'Flows', icon: Network }, { path: '/trees', label: 'Flows', icon: GitBranch },
{ path: '/step-library', label: 'Step Library', icon: Library }, { path: '/step-library', label: 'Step Library', icon: Layers },
{ path: '/scripts', label: 'Scripts', icon: Code2 }, { path: '/scripts', label: 'Scripts', icon: Code2 },
{ path: '/script-builder', label: 'Script Builder', icon: Wand2 }, { path: '/script-builder', label: 'Script Builder', icon: Wand2 },
{ path: '/analytics', label: 'Analytics', icon: BarChart3 }, { path: '/analytics', label: 'Analytics', icon: BarChart3 },
@@ -64,46 +64,21 @@ export function AppLayout() {
return ( return (
<> <>
{/* Atmosphere orbs — ambient light behind glass */}
<div <div
className="pointer-events-none fixed z-0" className={cn('app-shell relative z-1', sidebarPinned && 'app-shell--pinned')}
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')}
data-testid="app-shell" data-testid="app-shell"
> >
{/* Top Bar - spans full width */} {/* Top Bar - spans full width */}
<TopBar /> <TopBar />
{/* Sidebar - desktop only */} {/* Sidebar - desktop only, must fill grid row */}
<div className="hidden md:block"> <div className="hidden md:flex md:flex-col md:min-h-0 md:h-full">
<Sidebar /> <Sidebar />
</div> </div>
{/* Mobile hamburger - overlaid on topbar */} {/* Mobile hamburger - overlaid on topbar */}
<button <button
type="button"
onClick={() => setMobileMenuOpen(true)} 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" 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" aria-label="Open menu"
@@ -119,15 +94,17 @@ export function AppLayout() {
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
aria-hidden="true" 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"> <nav
<div className="flex h-14 items-center justify-between border-b border-border px-4"> 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"> <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" />
<BrandLogo size="sm" className="h-4 w-4" /> <span className="text-sm font-heading font-bold text-text-heading">ResolutionFlow</span>
</div>
<span className="text-sm font-heading font-bold">ResolutionFlow</span>
</Link> </Link>
<button <button
type="button"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground" className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground"
aria-label="Close menu" aria-label="Close menu"
@@ -138,7 +115,7 @@ export function AppLayout() {
<div className="flex flex-col p-3"> <div className="flex flex-col p-3">
{/* User info */} {/* 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> <p className="text-sm font-medium text-foreground">{user?.name || user?.email}</p>
{effectiveRole && effectiveRole !== 'engineer' && ( {effectiveRole && effectiveRole !== 'engineer' && (
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground"> <span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
@@ -162,8 +139,8 @@ export function AppLayout() {
className={cn( className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors', 'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
isActive isActive
? 'bg-[var(--sidebar-active)] text-foreground' ? 'bg-accent-dim text-foreground'
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground' : 'text-muted-foreground hover:bg-input hover:text-foreground'
)} )}
> >
<Icon size={18} /> <Icon size={18} />
@@ -174,10 +151,11 @@ export function AppLayout() {
</div> </div>
{/* Logout */} {/* 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 <button
type="button"
onClick={handleLogout} 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 size={18} />
Logout Logout

View File

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

View File

@@ -48,7 +48,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
title={label} title={label}
> >
{isActive && ( {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} /> <Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} style={iconColor ? { color: iconColor } : undefined} />
{badge !== undefined && badge !== 0 && badge !== 'dot' && ( {badge !== undefined && badge !== 0 && badge !== 'dot' && (
@@ -76,7 +76,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
> >
{/* Active indicator bar */} {/* Active indicator bar */}
{isActive && !isParentDimmed && ( {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} /> <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' ? ( badge === 'dot' ? (
<span className="ml-auto h-1.5 w-1.5 shrink-0 rounded-full bg-brand-gradient-from" /> <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} {badge}
</span> </span>
) )
@@ -117,7 +117,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
> >
<span className="truncate">{child.label}</span> <span className="truncate">{child.label}</span>
{child.count !== undefined && ( {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} {child.count}
</span> </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"> <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"> <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 Navigate
</span> </span>
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground"> <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 Open
</span> </span>
</div> </div>

View File

@@ -1,175 +1,497 @@
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'
import { useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import type { LucideIcon } from 'lucide-react'
import { import {
LayoutGrid, Network, Clock, FileOutput, BarChart3, TrendingUp, LayoutGrid, Clock, AlertTriangle, GitBranch, Layers, Code2, Wand2,
Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText, ListChecks, ListChecks, Download, BarChart3, Rocket,
BookOpen, Code2, Library, AlertTriangle, Wand2, Settings, Pin, PinOff,
Zap, Database, HelpCircle,
} from 'lucide-react' } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { sidebarApi } from '@/api' import { sidebarApi } from '@/api'
import type { SidebarStatsResponse } from '@/api/sidebar' 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 /* ── Types ──────────────────────────────────────────── */
const NAV_COLORS = {
dashboard: '#22d3ee', // cyan-400 interface NavSubItem {
flows: '#a78bfa', // violet-400 href: string
sessions: '#34d399', // emerald-400 label: string
exports: '#60a5fa', // blue-400 count?: number
stepLib: '#fb923c', // orange-400 }
scripts: '#2dd4bf', // teal-400
scriptBuilder: '#e879f9', // fuchsia-400 interface NavEntry {
analytics: '#38bdf8', // sky-400 href: string
guides: '#a3e635', // lime-400 icon: LucideIcon
feedback: '#818cf8', // indigo-400 label: string
} as const shortLabel: string
badge?: number
matchPaths?: string[]
children?: NavSubItem[]
}
interface NavSection {
title: string
items: NavEntry[]
}
/* ── Sidebar component ──────────────────────────────── */
export function Sidebar() { export function Sidebar() {
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar)
const location = useLocation() const location = useLocation()
const sidebarPinned = useUserPreferencesStore(s => s.sidebarPinned)
const toggleSidebarPinned = useUserPreferencesStore(s => s.toggleSidebarPinned)
const [stats, setStats] = useState<SidebarStatsResponse | null>(null) 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(() => { const refreshStats = useCallback(() => {
sidebarApi.getStats().then(setStats).catch(() => {}) 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(() => { useEffect(() => {
window.addEventListener('session-changed', refreshStats) window.addEventListener('session-changed', refreshStats)
return () => window.removeEventListener('session-changed', refreshStats) return () => window.removeEventListener('session-changed', refreshStats)
}, [refreshStats]) }, [refreshStats])
const handleSidebarWheel = (e: React.WheelEvent<HTMLElement>) => { /* ── Navigation data ──────────────────────────────── */
const sidebar = e.currentTarget
const canSidebarScroll = sidebar.scrollHeight > sidebar.clientHeight
const atTop = sidebar.scrollTop <= 0
const atBottom = sidebar.scrollTop + sidebar.clientHeight >= sidebar.scrollHeight - 1
// If sidebar can't consume wheel movement, forward it to main content scroller. /* ── Grouped nav: 5 top-level icons (Sentry-style) ── */
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()
}
}
}
return ( const railGroups: NavEntry[] = [
<nav {
className="sidebar flex flex-col border-r" href: '/', icon: LayoutGrid, label: 'Home', shortLabel: 'Home',
style={{ matchPaths: ['/'],
background: 'rgba(16, 17, 20, 0.5)', },
backdropFilter: 'var(--glass-blur-light)', {
WebkitBackdropFilter: 'var(--glass-blur-light)', href: '/sessions', icon: Zap, label: 'Work', shortLabel: 'Work',
borderColor: 'var(--glass-border)', badge: (stats?.active_count || 0) + (stats?.escalation_count || 0) || undefined,
}} matchPaths: ['/sessions', '/escalations', '/pilot'],
onWheel={handleSidebarWheel} children: [
> { href: '/sessions', label: 'Active Sessions', count: stats?.active_count || undefined },
{sidebarCollapsed ? ( { href: '/escalations', label: 'Escalations', count: stats?.escalation_count || undefined },
<> ],
{/* 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 /> href: '/trees', icon: Database, label: 'Knowledge', shortLabel: 'Know',
<NavItem href="/sessions" icon={Clock} label="Active Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} collapsed /> badge: stats?.tree_counts.total || undefined,
<NavItem href="/escalations" icon={AlertTriangle} label="Escalations" badge={stats?.escalation_count || undefined} iconColor="#fbbf24" collapsed /> matchPaths: ['/trees', '/flows', '/my-trees', '/step-library', '/scripts', '/script-builder', '/review-queue'],
<NavItem href="/trees" icon={Network} label="Flows" matchPaths={['/trees', '/flows', '/my-trees']} iconColor={NAV_COLORS.flows} collapsed /> children: [
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} collapsed /> { href: '/trees', label: 'All Flows', count: stats?.tree_counts.total || undefined },
<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={[
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || 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=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
{ href: '/trees?type=maintenance', label: 'Maintenance', count: stats?.tree_counts.maintenance || undefined }, { href: '/trees?type=maintenance', label: 'Maintenance', count: stats?.tree_counts.maintenance || undefined },
]} { href: '/step-library', label: 'Step Library' },
/> { href: '/scripts', label: 'Scripts' },
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} /> { href: '/script-builder', label: 'Script Builder' },
<NavItem href="/scripts" icon={Code2} label="Scripts" iconColor={NAV_COLORS.scripts} /> { href: '/review-queue', label: 'Review Queue' },
<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: '/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 */} /* Pinned mode still uses the detailed section layout */
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1"> const sections: NavSection[] = [
Insights {
</div> title: 'RESOLVE',
<NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} /> items: [
<NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} /> { href: '/', icon: LayoutGrid, label: 'Dashboard', shortLabel: 'Dash' },
<NavItem href="/analytics/flowpilot" icon={TrendingUp} label="FlowPilot Analytics" iconColor="#2dd4bf" /> { href: '/sessions', icon: Clock, label: 'Active Sessions', shortLabel: 'Sessions', badge: stats?.active_count || undefined, matchPaths: ['/sessions'] },
</div> { 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" /> <div className="flex-1" />
{/* Footer */} {/* Footer */}
<div <div className="px-3 pt-2 pb-4 space-y-0.5" style={{ borderTop: '1px solid var(--color-border-default)' }}>
className={cn( {footerItems.map((item, i) => renderPinnedItem(item, `footer-${i}`))}
"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" />
</>
)}
<button <button
onClick={toggleSidebar} type="button"
className={cn( onClick={toggleSidebarPinned}
"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", 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"
sidebarCollapsed ? "justify-center p-2.5" : "gap-3 px-3 py-2" title="Unpin sidebar"
)}
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
> >
{sidebarCollapsed ? <PanelLeftOpen size={20} /> : <PanelLeftClose size={18} />} <PinOff size={18} className="shrink-0" />
{!sidebarCollapsed && <span>Collapse</span>} <span>Unpin</span>
</button> </button>
</div> </div>
</nav> </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) return () => document.removeEventListener('mousedown', handleClickOutside)
}, [userMenuOpen]) }, [userMenuOpen])
// K / Ctrl+K global shortcut // Cmd+K / Ctrl+K global shortcut
const handleGlobalKeyDown = useCallback((e: KeyboardEvent) => { const handleGlobalKeyDown = useCallback((e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') { if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault() e.preventDefault()
@@ -55,12 +55,10 @@ export function TopBar() {
return ( return (
<> <>
<header <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={{ style={{
background: 'rgba(16, 17, 20, 0.6)', background: 'var(--color-bg-sidebar)',
backdropFilter: 'var(--glass-blur-strong)', borderBottom: '1px solid var(--color-border-default)',
WebkitBackdropFilter: 'var(--glass-blur-strong)',
borderColor: 'var(--glass-border)',
}} }}
> >
{/* Logo area */} {/* Logo area */}
@@ -68,10 +66,9 @@ export function TopBar() {
to="/" to="/"
className="flex items-center gap-2.5 pr-4 transition-all duration-200" className="flex items-center gap-2.5 pr-4 transition-all duration-200"
> >
<BrandLogo size="sm" className="h-7 w-7 shrink-0" /> <BrandLogo size="sm" />
<span className="text-sm font-heading font-bold tracking-tight whitespace-nowrap"> <span className="text-sm font-heading font-bold tracking-tight whitespace-nowrap text-text-heading">
<span className="text-foreground">Resolution</span> ResolutionFlow
<span className="text-gradient-brand">Flow</span>
</span> </span>
</Link> </Link>
@@ -85,16 +82,27 @@ export function TopBar() {
style={{ maxWidth: '480px' }} style={{ maxWidth: '480px' }}
> >
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" /> <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"> <div
Search flows, sessions, tags 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> </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"> <span
{navigator.platform?.toLowerCase().includes('mac') ? '⌘K' : 'Ctrl+K'} 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> </span>
</button> </button>
<button <button
onClick={() => setCommandPaletteOpen(true)} 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" title="Search"
> >
<Search size={18} /> <Search size={18} />
@@ -125,15 +133,19 @@ export function TopBar() {
<div className="relative ml-2" ref={menuRef}> <div className="relative ml-2" ref={menuRef}>
<button <button
onClick={() => setUserMenuOpen(!userMenuOpen)} 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'} title={user?.name || user?.email || 'User'}
> >
{initials} {initials}
</button> </button>
{userMenuOpen && ( {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
<div className="border-b border-border px-3 py-2.5 mb-1"> 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> <p className="text-sm font-medium text-foreground truncate">{user?.name || user?.email}</p>
{effectiveRole && effectiveRole !== 'engineer' && ( {effectiveRole && effectiveRole !== 'engineer' && (
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground"> <span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
@@ -145,7 +157,7 @@ export function TopBar() {
<Link <Link
to="/account" to="/account"
onClick={() => setUserMenuOpen(false)} 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} /> <Settings size={14} />
Account Account
@@ -154,18 +166,18 @@ export function TopBar() {
<Link <Link
to="/admin" to="/admin"
onClick={() => setUserMenuOpen(false)} 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} /> <Shield size={14} />
Admin Panel Admin Panel
</Link> </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 <button
onClick={handleLogout} onClick={handleLogout}
className={cn( className={cn(
'flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm', '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} /> <LogOut size={14} />

View File

@@ -102,7 +102,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
<div <div
className={cn( className={cn(
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border', '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 ? ( {isLoading ? (

View File

@@ -74,7 +74,7 @@ export function ExportFlowModal({ treeId, treeName, onClose }: ExportFlowModalPr
{/* Body */} {/* Body */}
<div className="px-5 py-4"> <div className="px-5 py-4">
<p className="text-sm text-muted-foreground"> <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> </p>
</div> </div>

View File

@@ -175,7 +175,7 @@ export function FolderEditModal({
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center"> <div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */} {/* 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 */} {/* Modal */}
<div className="relative z-10 w-full max-w-md bg-card border border-border rounded-2xl p-6 shadow-lg"> <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 <div
className={cn( className={cn(
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border', '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 <button
@@ -362,7 +362,7 @@ export function FolderSidebar({
{/* Mobile backdrop */} {/* Mobile backdrop */}
{mobileOpen && ( {mobileOpen && (
<div <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} onClick={onMobileClose}
aria-hidden="true" aria-hidden="true"
/> />
@@ -461,7 +461,7 @@ export function FolderSidebar({
<div <div
className={cn( className={cn(
'fixed z-50 w-44 rounded-md border border-border', '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 }} style={{ left: contextMenu.x, top: contextMenu.y }}
onClick={(e) => e.stopPropagation()} 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', 'flex flex-col items-center justify-center rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors cursor-pointer',
isDragging isDragging
? 'border-primary/50 bg-primary/5' ? 'border-primary/50 bg-primary/5'
: 'border-border hover:border-white/[0.12]' : 'border-border hover:border-border-hover'
)} )}
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }} onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }}
@@ -175,7 +175,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
<div className="space-y-2 text-xs"> <div className="space-y-2 text-xs">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-muted-foreground">Type:</span> <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} {TYPE_LABELS[parsed.flow.tree_type] || parsed.flow.tree_type}
</span> </span>
</div> </div>
@@ -198,7 +198,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
<div className="flex flex-wrap items-center gap-1.5"> <div className="flex flex-wrap items-center gap-1.5">
<span className="text-muted-foreground">Tags:</span> <span className="text-muted-foreground">Tags:</span>
{parsed.flow.tags.map((tag) => ( {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} {tag}
</span> </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"> <div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
{/* Backdrop */} {/* Backdrop */}
<div <div
className="absolute inset-0 bg-black/80 backdrop-blur-xs" className="absolute inset-0 bg-black/80"
onClick={onClose} onClick={onClose}
/> />

View File

@@ -46,7 +46,7 @@ export function TreeGridView({
</span> </span>
)} )}
{tree.tree_type === 'maintenance' && ( {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" /> <Wrench className="h-3 w-3" />
Maintenance Maintenance
</span> </span>
@@ -68,7 +68,7 @@ export function TreeGridView({
</span> </span>
)} )}
{tree.category_info && ( {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} {tree.category_info.name}
</span> </span>
)} )}
@@ -163,8 +163,8 @@ export function TreeGridView({
type="button" type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)} onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn( className={cn(
'rounded-md bg-gradient-brand px-3 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20', 'rounded-md border border-primary/40 px-3 py-2 text-sm font-medium text-primary',
'hover:opacity-90' 'hover:bg-primary/10 hover:border-primary/60 transition-colors'
)} )}
> >
Start Session Start Session

View File

@@ -46,7 +46,7 @@ export function TreeListView({
</span> </span>
)} )}
{tree.tree_type === 'maintenance' && ( {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" /> <Wrench className="h-3 w-3" />
Maintenance Maintenance
</span> </span>
@@ -74,7 +74,7 @@ export function TreeListView({
{/* Center: Category and Tags */} {/* Center: Category and Tags */}
<div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}> <div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}>
{tree.category_info && ( {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} {tree.category_info.name}
</span> </span>
)} )}
@@ -166,8 +166,8 @@ export function TreeListView({
type="button" type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)} onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn( className={cn(
'rounded-md bg-gradient-brand px-3 py-1.5 text-sm font-medium text-white shadow-lg shadow-primary/20', 'rounded-md border border-primary/40 px-3 py-1.5 text-sm font-medium text-primary',
'hover:opacity-90 whitespace-nowrap' 'hover:bg-primary/10 hover:border-primary/60 transition-colors whitespace-nowrap'
)} )}
> >
Start Start

View File

@@ -148,7 +148,7 @@ export function TreeTableView({
</span> </span>
)} )}
{tree.tree_type === 'maintenance' && ( {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" /> <Wrench className="h-3 w-3" />
Maintenance Maintenance
</span> </span>
@@ -176,7 +176,7 @@ export function TreeTableView({
</td> </td>
<td className="hidden lg:table-cell px-4 py-3"> <td className="hidden lg:table-cell px-4 py-3">
{tree.category_info && ( {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} {tree.category_info.name}
</span> </span>
)} )}
@@ -270,8 +270,8 @@ export function TreeTableView({
type="button" type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)} onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn( className={cn(
'rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-lg shadow-primary/20', 'rounded-md border border-primary/40 px-3 py-1.5 text-xs font-medium text-primary',
'hover:opacity-90 whitespace-nowrap' 'hover:bg-primary/10 hover:border-primary/60 transition-colors whitespace-nowrap'
)} )}
> >
Start Start

View File

@@ -96,7 +96,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={cn( 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 activeTab === tab.id
? "border-b-2 border-primary text-foreground" ? "border-b-2 border-primary text-foreground"
: "text-muted-foreground hover:text-foreground" : "text-muted-foreground hover:text-foreground"
@@ -112,7 +112,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
<div className="p-6"> <div className="p-6">
{activeTab === 'manual' && ( {activeTab === 'manual' && (
<div className="space-y-2"> <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) Server names (one per line)
</label> </label>
<textarea <textarea
@@ -192,7 +192,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
<button <button
onClick={handleLaunch} onClick={handleLaunch}
disabled={isLaunching || targets.length === 0} 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'} {isLaunching ? 'Launching\u2026' : targets.length > 0 ? `Launch ${targets.length} Sessions` : 'Launch'}
</button> </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"> <div className="flex items-center gap-2 mt-0.5">
{isComplete && session?.outcome && ( {isComplete && session?.outcome && (
<span className={cn( <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_COLORS[session.outcome] ?? OUTCOME_COLORS.unresolved
)}> )}>
{OUTCOME_LABELS[session.outcome] ?? session.outcome} {OUTCOME_LABELS[session.outcome] ?? session.outcome}
@@ -132,7 +132,7 @@ export function BatchStatusCard({ session, targetLabel, treeId, batchId }: Batch
{isNotStarted && ( {isNotStarted && (
<button <button
onClick={handleStart} 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" /> <Play className="h-3.5 w-3.5" />
Start Start

View File

@@ -226,7 +226,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
className={cn( className={cn(
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium', 'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
treeId 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' : '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} key={fbStep.id}
className={cn( className={cn(
'rounded-lg border p-3 transition-colors', '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' isCompleted && 'border-emerald-500/30 bg-emerald-500/5'
)} )}
> >

View File

@@ -212,7 +212,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
} }
return ( 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"> <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 */} {/* Header */}
<div className="border-b border-border px-6 py-4"> <div className="border-b border-border px-6 py-4">

View File

@@ -89,7 +89,7 @@ export function PrepareSessionModal({
return ( return (
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4"> <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"> <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 */} {/* Header */}
<div className="flex items-center justify-between border-b border-border px-5 py-4"> <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 */} {/* Intake form fields */}
{intakeFields.length > 0 && ( {intakeFields.length > 0 && (
<div className="space-y-4"> <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) Variables (optional can be filled later)
</h4> </h4>
{Array.from(grouped.entries()).map(([groupName, fields]) => ( {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"> <div className="flex items-center justify-end gap-3 border-t border-border px-5 py-4">
<button <button
onClick={onClose} 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 Cancel
</button> </button>
@@ -225,8 +225,8 @@ export function PrepareSessionModal({
onClick={handleSubmit} onClick={handleSubmit}
disabled={isSubmitting} disabled={isSubmitting}
className={cn( className={cn(
'rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114]', 'rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-white',
'shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]', 'hover:brightness-110 active:scale-[0.98]',
'disabled:opacity-40' 'disabled:opacity-40'
)} )}
> >

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