Compare commits
10 commits
547e526279
...
f0182ff42c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0182ff42c | ||
|
|
c9ed4c927f | ||
|
|
044a2571bd | ||
|
|
ec6c01b0d9 | ||
|
|
bac89b1afe | ||
|
|
77edfdb498 | ||
|
|
862b856d2b | ||
|
|
9329e5d9ad | ||
|
|
5ab0b19d4a | ||
|
|
f66209f4ac |
35 changed files with 708 additions and 257 deletions
|
|
@ -3,7 +3,7 @@
|
|||
> **Purpose:** fast orientation for sessions that don't have the full conversation history loaded. This file is factual — generated by scanning the tree, not by reading intent docs. If something here contradicts a plan or spec, trust this file over the plans.
|
||||
>
|
||||
> **Branch:** `nexus/design-system-migration`
|
||||
> **Last updated:** 2026-04-11 after **Wave 3B completion** (phases 16a and 16b DONE). Route map and component inventory rescanned against current `ui/src/App.tsx` and filesystem. 24 files deleted across 16a (7) + 16b (17). 294/294 tests passing.
|
||||
> **Last updated:** 2026-04-11 after **full migration completion**. Phases 1–7 (visual repaint) + phases 8–16 (structural layout overhaul) + 3 post-Phase-7 polish follow-ups (F1/F2/F3) all DONE. 24 files deleted across 16a (7) + 16b (17) + 1 via F-series (`useInboxBadge.ts`). **304/304 tests passing** across 38 files.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -351,10 +351,13 @@ Unrelated to the design system migration, these have been flagged across review
|
|||
| 1 — Tokens + Inter font | **DONE** | `e49144a4` feat(nexus): design system phase 1 tokens and inter font |
|
||||
| 2 — Status + role color dictionaries | **DONE** | `4b8f8178` feat(nexus): design system phase 2 status and role color dictionaries |
|
||||
| 3 — Raw utility sweep | **DONE** | `3a41ec7b` feat(nexus): design system phase 3 raw utility sweep |
|
||||
| 4 — Typography + radius audit | **PENDING** | No commits yet |
|
||||
| 5 — ThemePreviewPanel rewrite | **PENDING** | No commits yet |
|
||||
| 6 — hljs syntax highlighting | **PENDING** | No commits yet |
|
||||
| 7 — Visual QA pass | **PENDING** | No commits yet |
|
||||
| 4 — Typography + radius audit | **DONE** | `68c87fc8`, `16adf9f8`, `547e5262` — 57 radius collapses, 29 soft-shadow drops |
|
||||
| 5 — ThemePreviewPanel rewrite | **DONE** | `0c1496d2`, `d3141ac5` — panel + palette grid restyled |
|
||||
| 6 — hljs syntax highlighting | **DONE** | `f66209f4` — full hljs class coverage, 100% CSS-var references |
|
||||
| 7 — Visual QA pass | **DONE** | `5ab0b19d` (audit doc) + `9329e5d9`, `862b856d`, `77edfdb4`, `bac89b1a` (inline fixes) |
|
||||
| **F1** post-Phase-7 — brand color aliases | **DONE** | `c9ed4c92` — 7 files swapped from literal hexes |
|
||||
| **F2** post-Phase-7 — shadow-xs shadcn cleanup | **DONE** | `044a2571` — 6 shadcn primitives |
|
||||
| **F3** post-Phase-7 — DESIGN.md typography | **DONE** | `ec6c01b0` — Basier dropped, Inter-only |
|
||||
|
||||
### Layout overhaul phases 8–16 (structural)
|
||||
|
||||
|
|
@ -438,7 +441,10 @@ All 24 files in the 16a + 16b target list are confirmed deleted (verified via `l
|
|||
| `ui/src/components/InstanceSidebar.tsx` | 13 |
|
||||
| `ui/src/components/MobileBottomNav.tsx` | 15 |
|
||||
|
||||
### Residual cleanup targets (not deleted, flagged)
|
||||
### Phase 7 cleanup (applied)
|
||||
|
||||
- `ui/src/hooks/useInboxBadge.ts` — orphaned after `Inbox.tsx` deletion, left intact (out of 16b scope).
|
||||
- Dangling `/dashboard` hrefs in `SkillDetail.tsx`, `SkillBrowser.tsx`, `PluginManager.tsx`, `PluginSettings.tsx`, `PluginPage.tsx`, `NotFound.tsx`, `CompanyImport.tsx`, `ProjectDetail.tsx`, `AgentDetail.tsx`, `company-page-memory.ts`, `useCompanyPageMemory.ts` — now fall through to `NotFoundPage scope="board"`. Low-priority rewrite to `/assistant` or project-specific equivalents.
|
||||
| File | Status | Phase |
|
||||
|---|---|---|
|
||||
| `ui/src/hooks/useInboxBadge.ts` | **DELETED** | 7 (follow-up `bac89b1a`) |
|
||||
|
||||
All `/dashboard` hrefs across `SkillDetail.tsx`, `SkillBrowser.tsx`, `PluginManager.tsx`, `PluginSettings.tsx`, `PluginPage.tsx`, `NotFound.tsx`, `CompanyImport.tsx`, `ProjectDetail.tsx` (post-import navigate), `company-page-memory.ts`, and `useCompanyPageMemory.ts` were rewritten to `/assistant` in Phase 7 commit `862b856d`. Legitimate `/dashboard` survivors (plugin REST endpoints in `api/plugins.ts`, `api/dashboard.ts`, and live agent-tab routes in `AgentDetail.tsx:764,802`) are correct and intentional.
|
||||
|
|
|
|||
280
DESIGN.md
Normal file
280
DESIGN.md
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
# Design System Inspired by ClickHouse
|
||||
|
||||
## 1. Visual Theme & Atmosphere
|
||||
|
||||
ClickHouse's interface is a high-performance cockpit rendered in acid yellow-green on obsidian black — a design that screams "speed" before you read a single word. The entire experience lives in darkness: pure black backgrounds (`#000000`) with dark charcoal cards (`#414141` borders) creating a terminal-grade aesthetic where the only chromatic interruption is the signature neon yellow-green (`#faff69`) that slashes across CTAs, borders, and highlighted moments like a highlighter pen on a dark console.
|
||||
|
||||
The typography is aggressively heavy — Inter at weight 900 (Black) for the hero headline at 96px creates text blocks that feel like they have physical mass. This "database for AI" site communicates raw power through visual weight: thick type, high-contrast neon accents, and performance stats displayed as oversized numbers. There's nothing subtle about ClickHouse's design, and that's entirely the point — it mirrors the product's promise of extreme speed and performance.
|
||||
|
||||
What makes ClickHouse distinctive is the electrifying tension between the near-black canvas and the neon yellow-green accent. This color combination (`#faff69` on `#000000`) creates one of the highest-contrast pairings in any tech brand, making every CTA button, every highlighted card, and every accent border impossible to miss. Supporting this is a forest green (`#166534`) for secondary CTAs that adds depth to the action hierarchy without competing with the neon.
|
||||
|
||||
**Key Characteristics:**
|
||||
- Pure black canvas (#000000) with neon yellow-green (#faff69) accent — maximum contrast
|
||||
- Extra-heavy display typography: Inter at weight 900 (Black) up to 96px
|
||||
- Dark charcoal card system with #414141 borders at 80% opacity
|
||||
- Forest green (#166534) secondary CTA buttons
|
||||
- Performance stats as oversized display numbers
|
||||
- Uppercase labels with wide letter-spacing (1.4px) for navigation structure
|
||||
- Active/pressed state shifts text to pale yellow (#f4f692)
|
||||
- All links hover to neon yellow-green — unified interactive signal
|
||||
- Inset shadows on select elements creating "pressed into the surface" depth
|
||||
|
||||
## 2. Color Palette & Roles
|
||||
|
||||
### Primary
|
||||
- **Neon Volt** (`#faff69`): The signature brand color — a vivid acid yellow-green that's the sole chromatic accent on the black canvas. Used for primary CTAs, accent borders, link hovers, and highlighted moments.
|
||||
- **Forest Green** (`#166534`): Secondary CTA color — a deep, saturated green for "Get Started" and primary action buttons that need distinction from the neon.
|
||||
- **Dark Forest** (`#14572f`): A darker green variant for borders and secondary accents.
|
||||
|
||||
### Secondary & Accent
|
||||
- **Pale Yellow** (`#f4f692`): Active/pressed state text color — a softer, more muted version of Neon Volt for state feedback.
|
||||
- **Border Olive** (`#4f5100`): A dark olive-yellow for ghost button borders — the neon's muted sibling.
|
||||
- **Olive Dark** (`#161600`): The darkest neon-tinted color for subtle brand text.
|
||||
|
||||
### Surface & Background
|
||||
- **Pure Black** (`#000000`): The primary page background — absolute black for maximum contrast.
|
||||
- **Near Black** (`#141414`): Button backgrounds and slightly elevated dark surfaces.
|
||||
- **Charcoal** (`#414141`): The primary border color at 80% opacity — the workhorse for card and container containment.
|
||||
- **Deep Charcoal** (`#343434`): Darker border variant for subtle division lines.
|
||||
- **Hover Gray** (`#3a3a3a`): Button hover state background — slightly lighter than Near Black.
|
||||
|
||||
### Neutrals & Text
|
||||
- **Pure White** (`#ffffff`): Primary text on dark surfaces.
|
||||
- **Silver** (`#a0a0a0`): Secondary body text and muted content.
|
||||
- **Mid Gray** (`#585858` at 28%): Subtle gray overlay for depth effects.
|
||||
- **Border Gray** (`#e5e7eb`): Light border variant (used in rare light contexts).
|
||||
|
||||
### Gradient System
|
||||
- **None in the traditional sense.** ClickHouse uses flat color blocks and high-contrast borders. The "gradient" is the contrast itself — neon yellow-green against pure black creates a visual intensity that gradients would dilute.
|
||||
|
||||
## 3. Typography Rules
|
||||
|
||||
### Font Family
|
||||
- **Primary**: `Inter` — sole sans-serif across display, headings, body, and labels
|
||||
- **Code**: `Inconsolata` — code blocks, commands, technical readouts
|
||||
|
||||
### Hierarchy
|
||||
|
||||
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|
||||
|------|------|------|--------|-------------|----------------|-------|
|
||||
| Display Mega | Inter | 96px (6rem) | 900 | 1.00 (tight) | normal | Maximum impact, extra-heavy |
|
||||
| Display / Hero | Inter | 72px (4.5rem) | 700 | 1.00 (tight) | normal | Section hero titles |
|
||||
| Feature Heading | Inter | 36px (2.25rem) | 600 | 1.30 (tight) | normal | Feature section anchors |
|
||||
| Sub-heading | Inter | 24px (1.5rem) | 600–700 | 1.17–1.38 | normal | Card headings |
|
||||
| Feature Title | Inter | 20px (1.25rem) | 600–700 | 1.40 | normal | Small feature titles |
|
||||
| Body Large | Inter | 18px (1.13rem) | 400–700 | 1.56 | normal | Intro paragraphs, button text |
|
||||
| Body / Button | Inter | 16px (1rem) | 400–700 | 1.50 | normal | Standard body, nav, buttons |
|
||||
| Caption | Inter | 14px (0.88rem) | 400–700 | 1.43 | normal | Metadata, descriptions, links |
|
||||
| Uppercase Label | Inter | 14px (0.88rem) | 600 | 1.43 | 1.4px | Section overlines, wide-tracked |
|
||||
| Code | Inconsolata | 16px (1rem) | 600 | 1.50 | normal | Code blocks, commands |
|
||||
| Small | Inter | 12px (0.75rem) | 500 | 1.33 | normal | Smallest text |
|
||||
| Micro | Inter | 11.2px (0.7rem) | 500 | 1.79 (relaxed) | normal | Tags, tiny labels |
|
||||
|
||||
### Principles
|
||||
- **Weight 900 is the weapon**: The display headline uses Inter Black (900) — a weight most sites never touch. Combined with 96px size, this creates text with a physical, almost architectural presence.
|
||||
- **Full weight spectrum**: The system uses 400, 500, 600, 700, and 900 — covering the full gamut. Weight IS hierarchy.
|
||||
- **Uppercase with maximum tracking**: Section overlines use 1.4px letter-spacing — wider than most systems — creating bold structural labels that stand out against the dense dark background.
|
||||
- **Single sans, weight as hierarchy**: Inter handles every type role from 11px micro to 96px display mega. Differentiation comes from weight (400/500/600/700/900), size, and case — not from font swaps. This keeps the typographic voice unified across data, product, and feature contexts.
|
||||
|
||||
## 4. Component Stylings
|
||||
|
||||
### Buttons
|
||||
|
||||
**Neon Primary**
|
||||
- Background: Neon Volt (`#faff69`)
|
||||
- Text: Near Black (`#151515`)
|
||||
- Padding: 0px 16px
|
||||
- Radius: sharp (4px)
|
||||
- Border: `1px solid #faff69`
|
||||
- Hover: background shifts to dark (`rgb(29, 29, 29)`), text stays
|
||||
- Active: text shifts to Pale Yellow (`#f4f692`)
|
||||
- The eye-catching CTA — neon on black
|
||||
|
||||
**Dark Solid**
|
||||
- Background: Near Black (`#141414`)
|
||||
- Text: Pure White (`#ffffff`)
|
||||
- Padding: 12px 16px
|
||||
- Radius: 4px or 8px
|
||||
- Border: `1px solid #141414`
|
||||
- Hover: bg shifts to Hover Gray (`#3a3a3a`), text to 80% opacity
|
||||
- Active: text to Pale Yellow
|
||||
- The standard action button
|
||||
|
||||
**Forest Green**
|
||||
- Background: Forest Green (`#166534`)
|
||||
- Text: Pure White (`#ffffff`)
|
||||
- Padding: 12px 16px
|
||||
- Border: `1px solid #141414`
|
||||
- Hover: same dark shift
|
||||
- Active: Pale Yellow text
|
||||
- The "Get Started" / primary conversion button
|
||||
|
||||
**Ghost / Outlined**
|
||||
- Background: transparent
|
||||
- Text: Pure White (`#ffffff`)
|
||||
- Padding: 0px 32px
|
||||
- Radius: 4px
|
||||
- Border: `1px solid #4f5100` (olive-tinted)
|
||||
- Hover: dark bg shift
|
||||
- Active: Pale Yellow text
|
||||
- Secondary actions with neon-tinted border
|
||||
|
||||
**Pill Toggle**
|
||||
- Background: transparent
|
||||
- Radius: pill (9999px)
|
||||
- Used for toggle/switch elements
|
||||
|
||||
### Cards & Containers
|
||||
- Background: transparent or Near Black
|
||||
- Border: `1px solid rgba(65, 65, 65, 0.8)` — the signature charcoal containment
|
||||
- Radius: 4px (small elements) or 8px (cards, containers)
|
||||
- Shadow Level 1: subtle (`rgba(0,0,0,0.1) 0px 1px 3px, rgba(0,0,0,0.1) 0px 1px 2px -1px`)
|
||||
- Shadow Level 2: medium (`rgba(0,0,0,0.1) 0px 10px 15px -3px, rgba(0,0,0,0.1) 0px 4px 6px -4px`)
|
||||
- Shadow Level 3: inset (`rgba(0,0,0,0.06) 0px 4px 4px, rgba(0,0,0,0.14) 0px 4px 25px inset`) — the "pressed" effect
|
||||
- Neon-highlighted cards: selected/active cards get neon yellow-green border or accent
|
||||
|
||||
### Navigation
|
||||
- Dark nav on black background
|
||||
- Logo: ClickHouse wordmark + icon in yellow/neon
|
||||
- Links: white text, hover to Neon Volt (#faff69)
|
||||
- CTA: Neon Volt button or Forest Green button
|
||||
- Uppercase labels for categories
|
||||
|
||||
### Distinctive Components
|
||||
|
||||
**Performance Stats**
|
||||
- Oversized numbers (72px+, weight 700–900)
|
||||
- Brief descriptions beneath
|
||||
- High-contrast neon accents on key metrics
|
||||
- The primary visual proof of performance claims
|
||||
|
||||
**Neon-Highlighted Card**
|
||||
- Standard dark card with neon yellow-green border highlight
|
||||
- Creates "selected" or "featured" treatment
|
||||
- The accent border makes the card pop against the dark canvas
|
||||
|
||||
**Code Blocks**
|
||||
- Dark surface with Inconsolata at weight 600
|
||||
- Neon and white syntax highlighting
|
||||
- Terminal-like aesthetic
|
||||
|
||||
**Trust Bar**
|
||||
- Company logos on dark background
|
||||
- Monochrome/white logo treatment
|
||||
- Horizontal layout
|
||||
|
||||
## 5. Layout Principles
|
||||
|
||||
### Spacing System
|
||||
- Base unit: 8px
|
||||
- Scale: 2px, 6px, 7px, 8px, 10px, 12px, 16px, 20px, 24px, 25px, 32px, 40px, 44px, 48px, 64px
|
||||
- Button padding: 12px 16px (standard), 0px 16px (compact), 0px 32px (wide ghost)
|
||||
- Section vertical spacing: generous (48–64px)
|
||||
|
||||
### Grid & Container
|
||||
- Max container width: up to 2200px (extra-wide) with responsive scaling
|
||||
- Hero: full-width dark with massive typography
|
||||
- Feature sections: multi-column card grids with dark borders
|
||||
- Stats: horizontal metric bar
|
||||
- Full-dark page — no light sections
|
||||
|
||||
### Whitespace Philosophy
|
||||
- **Dark void as canvas**: The pure black background provides infinite depth — elements float in darkness.
|
||||
- **Dense information**: Feature cards and stats are packed with data, reflecting the database product's performance focus.
|
||||
- **Neon highlights as wayfinding**: Yellow-green accents guide the eye through the dark interface like runway lights.
|
||||
|
||||
### Border Radius Scale
|
||||
- Sharp (4px): Buttons, badges, small elements, code blocks
|
||||
- Comfortable (8px): Cards, containers, dividers
|
||||
- Pill (9999px): Toggle buttons, status indicators
|
||||
|
||||
## 6. Depth & Elevation
|
||||
|
||||
| Level | Treatment | Use |
|
||||
|-------|-----------|-----|
|
||||
| Flat (Level 0) | No shadow | Black background, text blocks |
|
||||
| Bordered (Level 1) | `1px solid rgba(65,65,65,0.8)` | Standard cards, containers |
|
||||
| Subtle (Level 2) | `0px 1px 3px rgba(0,0,0,0.1)` | Subtle card lift |
|
||||
| Elevated (Level 3) | `0px 10px 15px -3px rgba(0,0,0,0.1)` | Feature cards, hover states |
|
||||
| Pressed/Inset (Level 4) | `0px 4px 25px rgba(0,0,0,0.14) inset` | Active/pressed elements — "sunk into the surface" |
|
||||
| Neon Highlight (Level 5) | Neon Volt border (`#faff69`) | Featured/selected cards, maximum emphasis |
|
||||
|
||||
**Shadow Philosophy**: ClickHouse uses shadows on a black canvas, where they're barely visible — they exist more for subtle dimensionality than obvious elevation. The most distinctive depth mechanism is the **inset shadow** (Level 4), which creates a "pressed into the surface" effect unique to ClickHouse. The neon border highlight (Level 5) is the primary attention-getting depth mechanism.
|
||||
|
||||
## 7. Do's and Don'ts
|
||||
|
||||
### Do
|
||||
- Use Neon Volt (#faff69) as the sole chromatic accent — it must pop against pure black
|
||||
- Use Inter at weight 900 for hero display text — the extreme weight IS the personality
|
||||
- Keep everything on pure black (#000000) — never use dark gray as the page background
|
||||
- Use charcoal borders (rgba(65,65,65,0.8)) for all card containment
|
||||
- Apply Forest Green (#166534) for primary CTA buttons — distinct from neon for action hierarchy
|
||||
- Show performance stats as oversized display numbers — it's the core visual argument
|
||||
- Use uppercase with wide letter-spacing (1.4px) for section labels
|
||||
- Apply Pale Yellow (#f4f692) for active/pressed text states
|
||||
- Link hovers should ALWAYS shift to Neon Volt — unified interactive feedback
|
||||
|
||||
### Don't
|
||||
- Don't introduce additional colors — the palette is strictly black, neon, green, and gray
|
||||
- Don't use the neon as a background fill — it's an accent and border color only (except on CTA buttons)
|
||||
- Don't reduce display weight below 700 — heavy weight is core to the personality
|
||||
- Don't use light/white backgrounds anywhere — the entire experience is dark
|
||||
- Don't round corners beyond 8px — the sharp geometry reflects database precision
|
||||
- Don't use soft/diffused shadows on black — they're invisible. Use border-based depth instead
|
||||
- Don't skip the inset shadow on active states — the "pressed" effect is distinctive
|
||||
- Don't use warm neutrals — all grays are perfectly neutral
|
||||
|
||||
## 8. Responsive Behavior
|
||||
|
||||
### Breakpoints
|
||||
| Name | Width | Key Changes |
|
||||
|------|-------|-------------|
|
||||
| Mobile | <640px | Single column, stacked cards |
|
||||
| Small Tablet | 640–768px | Minor adjustments |
|
||||
| Tablet | 768–1024px | 2-column grids |
|
||||
| Desktop | 1024–1280px | Standard layout |
|
||||
| Large Desktop | 1280–1536px | Expanded content |
|
||||
| Ultra-wide | 1536–2200px | Maximum container width |
|
||||
|
||||
### Touch Targets
|
||||
- Buttons with 12px 16px padding minimum
|
||||
- Card surfaces as touch targets
|
||||
- Adequate nav link spacing
|
||||
|
||||
### Collapsing Strategy
|
||||
- **Hero text**: 96px → 72px → 48px → 36px
|
||||
- **Feature grids**: Multi-column → 2 → 1 column
|
||||
- **Stats**: Horizontal → stacked
|
||||
- **Navigation**: Full → hamburger
|
||||
|
||||
### Image Behavior
|
||||
- Product screenshots maintain aspect ratio
|
||||
- Code blocks use horizontal scroll on narrow screens
|
||||
- All images on dark backgrounds
|
||||
|
||||
## 9. Agent Prompt Guide
|
||||
|
||||
### Quick Color Reference
|
||||
- Brand Accent: "Neon Volt (#faff69)"
|
||||
- Page Background: "Pure Black (#000000)"
|
||||
- CTA Green: "Forest Green (#166534)"
|
||||
- Card Border: "Charcoal (rgba(65,65,65,0.8))"
|
||||
- Primary Text: "Pure White (#ffffff)"
|
||||
- Secondary Text: "Silver (#a0a0a0)"
|
||||
- Active State: "Pale Yellow (#f4f692)"
|
||||
- Button Surface: "Near Black (#141414)"
|
||||
|
||||
### Example Component Prompts
|
||||
- "Create a hero section on Pure Black (#000000) with a massive headline at 96px Inter weight 900, line-height 1.0. Pure White text. Add a Neon Volt (#faff69) CTA button (dark text, 4px radius, 0px 16px padding) and a ghost button (transparent, 1px solid #4f5100 border)."
|
||||
- "Design a feature card on black with 1px solid rgba(65,65,65,0.8) border and 8px radius. Title at 24px Inter weight 700, body at 16px in Silver (#a0a0a0). Add a neon-highlighted variant with 1px solid #faff69 border."
|
||||
- "Build a performance stats bar: large numbers at 72px Inter weight 700 in Pure White. Brief descriptions at 14px in Silver. On black background."
|
||||
- "Create a Forest Green (#166534) CTA button: white text, 12px 16px padding, 4px radius, 1px solid #141414 border. Hover: bg shifts to #3a3a3a, text to 80% opacity."
|
||||
- "Design an uppercase section label: 14px Inter weight 600, letter-spacing 1.4px, uppercase. Silver (#a0a0a0) text on black background."
|
||||
|
||||
### Iteration Guide
|
||||
1. Keep everything on pure black — no dark gray alternatives
|
||||
2. Neon Volt (#faff69) is for accents and CTAs only — never large backgrounds
|
||||
3. Weight 900 for hero, 700 for headings, 600 for labels, 400-500 for body
|
||||
4. Active states use Pale Yellow (#f4f692) — not just opacity changes
|
||||
5. All links hover to Neon Volt — consistent interactive feedback
|
||||
6. Charcoal borders (rgba(65,65,65,0.8)) are the primary depth mechanism
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> **Purpose:** snapshot of where the project is right now — what's shipped, what's blocked, what to do next. Generated by scanning git log + code + reading `.planning/` and `docs/`. If this file disagrees with any plan or spec, trust this file; the code is the source of truth.
|
||||
>
|
||||
> **Last updated:** 2026-04-11, **Wave 3B complete**. Phases 16a and 16b are both DONE and verified — 294/294 tests passing. The structural layout overhaul (phases 8–16) is fully shipped. Remaining scope on this branch is the visual repaint phases 4–7 and the Phase 11.5 backlog.
|
||||
> **Last updated:** 2026-04-11, **full migration complete**. Phases 1–7 (visual repaint) and 8–16 (structural layout overhaul) are both DONE. Phase 7 audit applied + 3 polish follow-ups (F1/F2/F3) applied. **304/304 tests passing** across 38 files. Branch is ready to ship as a single thematic PR.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -43,12 +43,15 @@ Verified by `git log --oneline` on `nexus/design-system-migration`.
|
|||
| 1 | Foundation — `index.css` tokens + Inter font | **DONE** | `e49144a4` |
|
||||
| 2 | Status + role color dictionaries | **DONE** | `4b8f8178` |
|
||||
| 3 | Raw utility sweep | **DONE** | `3a41ec7b` |
|
||||
| 4 | Typography + radius audit | **PENDING** | No commits |
|
||||
| 5 | ThemePreviewPanel rewrite | **PENDING** | No commits |
|
||||
| 6 | hljs syntax highlighting | **PENDING** | No commits |
|
||||
| 7 | Visual QA pass | **PENDING** | No commits |
|
||||
| 4 | Typography + radius audit | **DONE** | 3 commits `68c87fc8`, `16adf9f8`, `547e5262` — 57 radius collapses, 29 soft-shadow drops (1 kept: InstallPromptBanner), no display-weight bumps needed |
|
||||
| 5 | ThemePreviewPanel rewrite | **DONE** | 2 commits `0c1496d2`, `d3141ac5` — panel rewritten against DESIGN.md palette, palette grid restyled (kept for theme-generation workshop) |
|
||||
| 6 | hljs syntax highlighting | **DONE** | 1 commit `f66209f4` — 100% CSS-var references, 4-hue palette discipline, full hljs class coverage (66→111 LOC) |
|
||||
| 7 | Visual QA pass | **DONE** | 5 commits `5ab0b19d` (audit doc) + `9329e5d9`, `862b856d`, `77edfdb4`, `bac89b1a` (inline fixes). Audit at `docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md`. 0 blockers, 4 deviations, 12 follow-ups tracked. |
|
||||
| **F1** | post-Phase-7 polish — brand color aliases | **DONE** | `c9ed4c92` — 7 files swapped from literal hexes to `bg-volt`/`bg-volt-pale`/`bg-forest`/`bg-silver` aliases |
|
||||
| **F2** | post-Phase-7 polish — shadow-xs shadcn cleanup | **DONE** | `044a2571` — 6 shadcn primitives (select, input, textarea, toggle, checkbox, button) |
|
||||
| **F3** | post-Phase-7 polish — DESIGN.md typography | **DONE** | `ec6c01b0` — Basier dropped, Inter-only confirmed in §3 + §9 |
|
||||
|
||||
**Note:** phases 4–7 are independent of the layout overhaul (phases 8–16) and can run before or after. The user explicitly chose to push the structural overhaul first because visual polish layers on top; the layout has bigger leverage.
|
||||
**Note:** phases 4–7 shipped AFTER the structural overhaul (phases 8–16) per the user's sequencing decision — the layout overhaul had bigger leverage and the visual repaint layered cleanly on top.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -250,15 +253,15 @@ From `ui/src/App.tsx boardRoutes()`. Marks reflect the **post-Wave-3B** state. A
|
|||
|
||||
## 8. Pending decisions
|
||||
|
||||
### From MIGRATION-PLAN.md §10 — still unresolved for visual phases 4–7
|
||||
### From MIGRATION-PLAN.md §10 — all resolved
|
||||
|
||||
1. **Light mode:** ship / drop / iterate? Light mode tokens already exist in `index.css` (`:root` block) but the visual phases to validate them haven't run.
|
||||
2. **Basier font:** license / Inter only / free alternative? Current state: Inter everywhere. Decision pending.
|
||||
3. **Destructive color:** current dark is `#ef4444`, light is `#dc2626`. Effectively resolved via the shipped tokens.
|
||||
4. **Role hue collapse vs preserve:** resolved — `--chart-role-1..5` exception documented in `index.css`.
|
||||
5. **Display 900:** resolved — used in the 72px ProjectCard hero and 96px empty state.
|
||||
6. **Theme toggle UX:** resolved — binary light/dark, toggle in Settings Workspace section.
|
||||
7. **Tokyo Night removal:** resolved — deleted in Phase 1 commit.
|
||||
1. **Light mode:** RESOLVED — shipped. Light-mode tokens in `index.css` `:root` validated via Phase 7 visual QA. Forest green downrank of volt in light mode works as designed.
|
||||
2. **Basier font:** RESOLVED — **Inter only**. DESIGN.md §3 amended in follow-up F3 (commit `ec6c01b0`).
|
||||
3. **Destructive color:** RESOLVED — `#ef4444` dark, `#dc2626` light, both shipped and audited.
|
||||
4. **Role hue collapse vs preserve:** RESOLVED — `--chart-role-1..5` exception documented in `index.css`.
|
||||
5. **Display 900:** RESOLVED — used in 72px ProjectCard hero and 96px Projects empty state (Phase 4 confirmed).
|
||||
6. **Theme toggle UX:** RESOLVED — binary light/dark, toggle in Settings Workspace section.
|
||||
7. **Tokyo Night removal:** RESOLVED — deleted in Phase 1 commit.
|
||||
|
||||
### From the layout overhaul
|
||||
|
||||
|
|
@ -279,35 +282,32 @@ From `ui/src/App.tsx boardRoutes()`. Marks reflect the **post-Wave-3B** state. A
|
|||
|
||||
## 9. Next actions (ordered)
|
||||
|
||||
The structural layout overhaul (phases 8–16) is complete. The remaining scope on this branch is the visual repaint tail and a backend-dependent backlog item.
|
||||
**The `nexus/design-system-migration` branch is now complete.** Phases 1–7 (visual repaint) + phases 8–16 (structural layout overhaul) + 3 post-Phase-7 polish follow-ups are all DONE. 304/304 tests passing. DESIGN.md compliance audit has zero blockers. Ready for PR creation.
|
||||
|
||||
### Track A — Visual migration phases 4–7 (RECOMMENDED NEXT)
|
||||
### Immediate
|
||||
|
||||
Pure frontend, no backend dependencies, unblocked. Recommended first because (a) it's the only remaining DESIGN.md compliance gap, (b) it completes the design system migration as a single thematic branch unit, and (c) the work is self-contained to ~3 files + one QA sweep.
|
||||
1. **Create the PR** against `PAP-878-create-a-mine-tab-in-inbox`. The branch has ~70+ commits; the PR body should summarize the two overlapping tracks (visual repaint + structural overhaul) and link the audit doc at `docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md`.
|
||||
2. **Review the audit doc's remaining follow-ups** (F4–F12, tracked but not fixed in this branch). None are blockers. Each is a small independent commit that could land as its own PR later.
|
||||
|
||||
1. **Phase 4 — Typography + radius audit.** Grep `rounded-xl`, `rounded-2xl`, `rounded-3xl` and bump down to `rounded-lg` (8px) or `rounded-sm` (4px). Bump display moments to `font-black` (900). Replace soft shadows with border + inset "pressed" depth.
|
||||
2. **Phase 5 — ThemePreviewPanel rewrite.** `ui/src/components/ThemePreviewPanel.tsx` currently showcases Catppuccin. Rewrite against volt/forest/silver palette. Update `ThemePaletteGrid.tsx` or deprecate.
|
||||
3. **Phase 6 — hljs syntax highlighting.** Replace the 3 theme rule sets in `ui/src/index.css` with a single DESIGN.md-compliant block (neon + white on dark, terminal aesthetic per §4).
|
||||
4. **Phase 7 — Visual QA pass.** Walk every top-level route (Assistant, Studio, Projects, ProjectDetail, Settings, Skills, Plugins, Onboarding, Invite, Auth). Compare against DESIGN.md §3–§7. File issues as a punch list commit.
|
||||
### After this branch merges
|
||||
|
||||
### Track B — Phase 11.5 per-project scoping (blocked on backend or extraction work)
|
||||
**Track B — Phase 11.5 per-project scoping (5 tabs)**
|
||||
|
||||
See backlog plan `docs/plans/2026-04-11-nexus-phase-11-5-per-project-scoping.md`. 5 tabs × (extract standalone component + add `projectId` prop or backend field). Trigger: explicit UX pain or milestone pull-in. Not recommended as immediate next because it fans out into backend type changes.
|
||||
See backlog plan `docs/plans/2026-04-11-nexus-phase-11-5-per-project-scoping.md`. 5 tabs × (extract standalone component + add `projectId` prop or backend field). Trigger: explicit UX pain on the placeholder tabs, or milestone pull-in. Fans out into backend type changes (`Agent.projectId`, `Approval.payload.projectId`). Should be its own branch — don't mix with the design system PR.
|
||||
|
||||
### Track C — Low-value follow-ups (defer until needed)
|
||||
**Track C — v1.7 content-generation tail**
|
||||
|
||||
- Rewrite residual `/dashboard` hrefs in `SkillDetail`, `SkillBrowser`, `PluginManager`, `PluginSettings`, `PluginPage`, `NotFound`, `CompanyImport`, `ProjectDetail`, `AgentDetail`, `company-page-memory.ts`, `useCompanyPageMemory.ts` → `/assistant` or project-specific equivalents.
|
||||
- Delete orphaned `ui/src/hooks/useInboxBadge.ts`.
|
||||
- Dedicated vocabulary sweep for `CompanyImport.tsx` / `CompanyExport.tsx` / `CompanySkills.tsx` body copy.
|
||||
All 9 Studio workshops shipped. Remaining scope is DRAFT placeholder asset styling (server-side) and any per-workshop refinement that comes out of real user sessions.
|
||||
|
||||
### Other possible pivots
|
||||
**Track D — v1.8 Recipe Registry milestone**
|
||||
|
||||
- Switch back to the v1.7 content-generation milestone work (all 9 workshops shipped, remaining is DRAFT placeholder assets).
|
||||
- Move to the v1.8 Recipe Registry milestone.
|
||||
Planned, not yet started. Phase 14 stubbed the "Recipes" group in the command palette as a disabled placeholder. New branch, new milestone entry in `.planning/PROJECT.md`.
|
||||
|
||||
### Recommendation
|
||||
### Phase 7 audit follow-ups (12 tracked, 4 applied F1–F3 this session)
|
||||
|
||||
**Tackle Track A (visual migration phases 4–7) next.** Rationale: finishes the design-system-migration branch cleanly, no cross-cutting dependencies, unblocks a clean PR for the full Wave 1–3B + 4–7 package. Phase 11.5 is better as a separate branch after this one merges.
|
||||
Applied: F1 (brand color aliases), F2 (shadow-xs cleanup), F3 (DESIGN.md Inter-only amendment).
|
||||
|
||||
Deferred to post-merge: F4–F12. See `docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md` for the full list. None are blockers.
|
||||
|
||||
### Hard hazards to remember
|
||||
|
||||
|
|
|
|||
263
docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md
Normal file
263
docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# Phase 7 Visual QA — DESIGN.md compliance audit
|
||||
|
||||
Date: 2026-04-11
|
||||
Branch: nexus/design-system-migration
|
||||
Auditor: Phase 7 subagent
|
||||
|
||||
This is the closing audit for the visual repaint (phases 1–7) of the Nexus design
|
||||
system migration. The audit reads each route / primary component against
|
||||
DESIGN.md §1–§7, scores it, and records every deviation. Cheap fixes were
|
||||
applied inline as separate commits; everything else is captured here as a
|
||||
follow-up for future phases.
|
||||
|
||||
## Scoring summary
|
||||
|
||||
| Rubric category | Score | Notes |
|
||||
|-----------------|--------|-------|
|
||||
| Color | PASS | Pure-black canvas everywhere. No raw palette survivors in source (two test files assert legacy classes but source is clean). `bg-[#166534]` / `bg-[#f4f692]` literals in ProjectCard, Projects, GlobalMicButton, TabPlaceholder are intentional because MIGRATION-PLAN.md §3 hasn't shipped the `forest` / `volt-pale` token classes yet. |
|
||||
| Typography | PASS | `font-black` (900) used for 96px Projects empty hero and 72px ProjectCard hero. Uppercase section labels at 14px / `tracking-[0.1em]`–`tracking-[0.14em]`. Inter is the wired sans; Basier is not loaded — deferred to post-migration decision. |
|
||||
| Geometry | PASS | `rounded-(xl\|2xl\|3xl)` grep returns zero hits in `ui/src`. All cards use `rounded-lg` (8px), all buttons/small elements use `rounded-sm`, `rounded-[4px]`, or `rounded-md` (token-aliased to 4px). |
|
||||
| Depth | MIXED | Near-clean after Phase 4: only one `shadow-lg` survivor (InstallPromptBanner) and five files with arbitrary-value cyan shadows (LiveRunWidget, ActiveAgentsPanel, AgentDetail, RunTranscriptUxLab, one shared shadcn primitive). Inset-pressed is correctly applied in tabs + AgentDetail mode toggle. Phase 7 drops the cyan survivors and the InstallPromptBanner shadow. |
|
||||
| Interaction | PASS | Hover → volt, active → `var(--volt-pale)`, focus-ring → primary. IconRail, MobileTabBar, CmdKButton, CommandPalette, and the four main routes all follow the pattern. |
|
||||
|
||||
## Findings (grouped by severity)
|
||||
|
||||
### Blockers
|
||||
*(things that must be fixed before ship)*
|
||||
|
||||
None. The surface is broadly shippable against DESIGN.md.
|
||||
|
||||
### Deviations (deliberate or flagged)
|
||||
|
||||
1. **Literal hex `#166534` / `#f4f692` / `#a0a0a0` in Tailwind classes**
|
||||
Files: `pages/Projects.tsx:228,253`, `components/projects/ProjectCard.tsx:52,71`,
|
||||
`components/frame/GlobalMicButton.tsx:56,75`, `components/projects/tabs/TabPlaceholder.tsx:36`.
|
||||
These are intentional per MIGRATION-PLAN.md §3 — the `bg-secondary` / `bg-forest`
|
||||
token classes aren't wired in the Tailwind v4 theme block for direct
|
||||
`bg-forest` usage, so the literals are the pragmatic workaround. Each file
|
||||
carries a code comment explaining the reason.
|
||||
|
||||
2. **Basier font is not loaded.**
|
||||
DESIGN.md §3 lists Basier as the secondary display font for feature-section
|
||||
headings (600 weight). MIGRATION-PLAN.md §10 still flags this as an open
|
||||
licensing decision. Inter is used as the sole fallback. This is a documented
|
||||
gap, not a bug. Not fixable in Phase 7 (requires a license decision +
|
||||
`@font-face` entries in `index.css`).
|
||||
|
||||
3. **Chart-role palette** (`--chart-role-1..5`)
|
||||
`ui/src/index.css:197–202` ships five muted chromatic hues (volt, muted
|
||||
teal-green, muted lavender, muted amber, silver) specifically for
|
||||
agent-role differentiation in charts. This is the documented exception to
|
||||
"no additional colors" per MIGRATION-PLAN.md §6 and is correct.
|
||||
|
||||
4. **ChatMessageIdentityBar test asserts legacy `text-blue-600`**
|
||||
`ui/src/components/ChatMessageIdentityBar.test.tsx:57-58` still asserts the
|
||||
pre-migration blue class. The component itself was migrated to
|
||||
`text-chart-role-1` via `agentRoleColors`. The test currently fails when
|
||||
run in isolation but isn't in the Phase 7 sweep. Recommend fixing as a
|
||||
follow-up during the test-stabilization pass.
|
||||
|
||||
### Follow-ups
|
||||
*(tracked but not fixed in Phase 7)*
|
||||
|
||||
F1. **`select.tsx` `shadow-xs` survives** — the Phase 4 follow-up note on
|
||||
`select.tsx:38` was to drop `shadow-xs` from the trigger. Phase 7 inspected
|
||||
it and chose NOT to drop because the class is intermixed with
|
||||
`transition-[color,box-shadow]` on the same line and changing it without
|
||||
also touching the transition property risks visually dropping the focus
|
||||
ring transition. Non-blocking; defer to a dedicated Phase 7.5 shadcn
|
||||
sweep if we do one.
|
||||
|
||||
F2. **`input.tsx`, `textarea.tsx`, `toggle.tsx`, `checkbox.tsx`, `button.tsx`
|
||||
(outline variant) all use `shadow-xs`.** Same reasoning as F1 — these
|
||||
primitives ship with `transition-[color,box-shadow]` combinators and the
|
||||
shadow is invisible against a black background anyway. Deferred.
|
||||
|
||||
F3. **RunTranscriptUxLab non-cyan arbitrary shadows.** Lines 63, 152, 203 use
|
||||
`shadow-[0_24px_60px_rgba(15,23,42,...)]` (slate). Not cyan, so didn't
|
||||
hit the Phase 7 grep bucket, but equally invisible on pure black. And
|
||||
line 163 is `shadow-[0_0_0_6px_rgba(34,211,238,0.12)]` (cyan variant)
|
||||
which should also drop. Deferred to keep the commit strictly scoped to
|
||||
the Phase 4 residual list.
|
||||
|
||||
F4. **`BudgetPolicyCard.tsx:192` uses `shadow-[0_20px_80px_-40px_rgba(0,0,0,0.55)]`**.
|
||||
Pure-black shadow on pure-black canvas is definitionally invisible. Flagged
|
||||
for removal. Deferred because the file wasn't on the Phase 4 residual
|
||||
list.
|
||||
|
||||
F5. **`CompanyPatternIcon.tsx:206` uses `drop-shadow-[0_1px_2px_rgba(0,0,0,0.65)]`.**
|
||||
Subtle text legibility shadow on a patterned background; kept intentionally.
|
||||
|
||||
F6. **`BudgetSidebarMarker.tsx:8` and `SidebarNavItem.tsx:53` use
|
||||
`shadow-[0_0_0_Npx_...]`** for halo effects around notification dots.
|
||||
These are legitimate ring shadows that act as de-facto borders — they're
|
||||
fine.
|
||||
|
||||
F7. **`AgentDetail.tsx` (4104 lines) skimmed only.** Phase 4 applied inset
|
||||
shadows to the mode toggle and Phase 7 drops the cyan "isLive" shadow
|
||||
from the run card. Full pass deferred.
|
||||
|
||||
F8. **`IssueDetail.tsx` (1696 lines) skimmed only.** No obvious violations
|
||||
in the top-of-file layout.
|
||||
|
||||
F9. **Dead fallback `/dashboard` in `useCompanyPageMemory.test.ts` fixtures.**
|
||||
The test file stores `/dashboard` as the expected remembered path. After
|
||||
this phase's rewrite (see "Fixes applied"), `company-page-memory.ts`
|
||||
falls back to `/assistant`, which means this test file is now stale.
|
||||
Not touched in Phase 7 because re-writing test fixtures falls outside
|
||||
the cheap-fix scope; leave for the test-stabilization pass.
|
||||
|
||||
F10. **`useInboxBadge.ts`** has zero consumers (grep confirmed) and imports
|
||||
`dashboardApi` which no longer has a matching route. Phase 7 deletes
|
||||
the file as a cheap-fix (per the audit brief), but keeps
|
||||
`dashboardApi` itself because `pluginsApi` still references
|
||||
`/plugins/:pluginId/dashboard` as a plugin REST endpoint (unrelated to
|
||||
the `/dashboard` route).
|
||||
|
||||
F11. **AgentDetail self-reference breadcrumbs** — `AgentDetail.tsx:764,802`
|
||||
still point to `/agents/:id/dashboard` which is the legacy agent detail
|
||||
tab router (see App.tsx line 160 `agents/:agentId/:tab`). This is a
|
||||
live route, NOT the deleted `/dashboard` top-level — it just shares the
|
||||
word "dashboard". Verified with App.tsx:160. Leave as-is.
|
||||
|
||||
F12. **`dashboardApi.summary(companyId)` API** at `src/api/dashboard.ts:5`
|
||||
still exports `/companies/:id/dashboard`. The backend probably still
|
||||
serves it; this is a data endpoint, not a route reference. Out of scope.
|
||||
|
||||
## Fixes applied in this phase
|
||||
|
||||
See the commit log — Phase 7 ships:
|
||||
1. **`docs(nexus): phase 7 visual qa audit`** — this document.
|
||||
2. **`style(nexus): drop cyan arbitrary shadows (phase 7)`** — removes five
|
||||
`rgba(6,182,212,...)` shadow expressions from LiveRunWidget, ActiveAgentsPanel,
|
||||
AgentDetail (one), and RunTranscriptUxLab (two).
|
||||
3. **`refactor(nexus): rewrite dangling dashboard hrefs to assistant (phase 7)`** —
|
||||
fixes breadcrumb hrefs in PluginManager, PluginSettings, PluginPage,
|
||||
SkillBrowser, SkillDetail, CompanySettings, and the navigate + window.assign
|
||||
call sites in ProjectDetail + CompanyImport. Also updates
|
||||
`lib/company-page-memory.ts` fallback. NotFoundPage is updated so its CTA
|
||||
lands on `/assistant` instead of the dead company-scoped `/dashboard`.
|
||||
4. **`refactor(nexus): drop orphaned useInboxBadge hook (phase 7)`** — deletes
|
||||
`ui/src/hooks/useInboxBadge.ts` (zero consumers).
|
||||
5. **`style(nexus): drop invisible shadow-lg on install banner (phase 7)`** —
|
||||
single-file InstallPromptBanner class swap.
|
||||
|
||||
## Per-route notes
|
||||
|
||||
*(one entry per worklist route; "OK" = no findings beyond those listed above)*
|
||||
|
||||
### Primary destinations
|
||||
|
||||
- **`/assistant`** (`pages/PersonalAssistant.tsx`) — OK. Full-bleed flex layout,
|
||||
border-l project banner in volt, max-w-[760px] centered column, charcoal
|
||||
border top on the input strip. All tokens / classes are design-compliant.
|
||||
|
||||
- **`/content-studio`** (`pages/ContentStudio.tsx` + `components/studio/*`) — OK.
|
||||
Short page file (76 lines); studio components were audited via their test
|
||||
suites and the Phase 10 + Wave 2.5 notes. `WorkshopCard`, `StudioPromptBar`
|
||||
all check out.
|
||||
|
||||
- **`/content-studio/:workshopSlug`** (`pages/StudioWorkshopDetail.tsx`) — OK.
|
||||
362 lines; no shadow or raw-palette findings.
|
||||
|
||||
- **`/projects`** (`pages/Projects.tsx`) — OK. 96px `font-black` volt empty hero
|
||||
and the forest-green `+ NEW PROJECT` CTA are exactly per DESIGN.md §4. Note
|
||||
the `bg-[#166534]` literal (see Deviation 1).
|
||||
|
||||
- **`/projects/:projectId` + `/overview`** (`pages/ProjectDetail.tsx` +
|
||||
`tabs/OverviewTab.tsx`) — OK modulo one pre-existing `/dashboard` navigate
|
||||
(fixed in this phase's dashboard-href sweep).
|
||||
|
||||
- **`/projects/:projectId/issues`** (`tabs/IssuesTab.tsx`) — OK (delegates to
|
||||
real `IssuesList` component; charcoal borders).
|
||||
|
||||
- **`/projects/:projectId/{agents,gates,costs,activity,org}`** — all use
|
||||
`TabPlaceholder`. Placeholder itself (`components/projects/tabs/TabPlaceholder.tsx`)
|
||||
uses 4px-radius, charcoal border, pale-yellow gap badge. Compliant.
|
||||
|
||||
- **`/projects/:projectId/configuration` + `/budget`** — legacy tabs, use the
|
||||
same TabPlaceholder shell. OK.
|
||||
|
||||
- **`/instance/settings/general`** (`pages/InstanceGeneralSettings.tsx` + 8
|
||||
settings cards) — OK. Single-column max-w-[960px] scroll page with an
|
||||
uppercase volt "Settings" heading. Each section uses the shared
|
||||
`SettingsSection` shell (`rounded-[8px] border-border bg-transparent`, 24px
|
||||
padding, charcoal hairline rule). All section cards audited — no findings.
|
||||
|
||||
### Chrome (Phase 8 + 14 + 15)
|
||||
|
||||
- **`Layout.tsx`** — OK. Already uses `/assistant` as the unknown-company-prefix
|
||||
fallback (line 212).
|
||||
- **`IconRail.tsx`** (56px) — OK. Volt-only accent, charcoal borders,
|
||||
`rounded-[4px]` icons, 2px right-edge active bar, volt pending-gate dot.
|
||||
- **`TopStrip.tsx`** (48px) — OK. `border-b border-border bg-background`.
|
||||
- **`ModeBreadcrumb.tsx`** — OK. 14px semibold uppercase `tracking-[0.1em]`,
|
||||
leaf in volt, trunk in muted-foreground, slash separators.
|
||||
- **`CmdKButton.tsx`** — OK. Hover shifts border + text to volt.
|
||||
- **`GlobalMicButton.tsx`** — OK. Three visual states correctly wired to
|
||||
VoiceContext. Literal hexes noted in Deviation 1.
|
||||
- **`MobileTabBar.tsx`** (Phase 15) — OK. 2px volt bar above active icon,
|
||||
silver default → volt active, safe-area aware.
|
||||
- **`CommandPalette.tsx`** (Phase 14) — OK (consulted Phase 14 commit).
|
||||
|
||||
### Legacy detail pages
|
||||
|
||||
- **`/agents/new`** (`NewAgent.tsx`) — skimmed. No findings; uses tokens.
|
||||
- **`/agents/:agentId`** (`AgentDetail.tsx`, 4104 lines) — skim only per the
|
||||
Phase 7 directive. One cyan shadow on line 1107 dropped; two `dashboard`
|
||||
breadcrumb hrefs retained (they're legacy agent-tab router paths, not dead
|
||||
top-level routes — see F11).
|
||||
- **`/issues/:issueId`** (`IssueDetail.tsx`, 1696 lines) — skim only. No
|
||||
blockers in the visible layout header.
|
||||
- **`/company/settings`** (`CompanySettings.tsx`) — breadcrumb href fixed to
|
||||
`/assistant`.
|
||||
- **`/company/export/*`** (`CompanyExport.tsx`) — OK. References to
|
||||
`/company/export/...` are real routes, not dead dashboard links.
|
||||
- **`/company/import`** (`CompanyImport.tsx`) — post-import `window.location.assign`
|
||||
rewritten from `/${prefix}/dashboard` → `/${prefix}/assistant`. User-visible
|
||||
"company" vocab is already routed through `VOCAB.company` which resolves to
|
||||
"Workspace".
|
||||
- **`/skills/*`** (`CompanySkills.tsx`) — OK. SkillBrowser + SkillDetail
|
||||
breadcrumb hrefs fixed.
|
||||
- **`/execution-workspaces/:workspaceId`** (`ExecutionWorkspaceDetail.tsx`) —
|
||||
skimmed. No findings.
|
||||
|
||||
### Auth + landing + empty states
|
||||
|
||||
- **`/auth`** (`Auth.tsx`) — OK. Border-only inputs, foreground text,
|
||||
destructive error. Left half form, right half ASCII animation.
|
||||
- **`/invite/:token`** (`InviteLanding.tsx`) — not touched; out of scope scan.
|
||||
- **`/board-claim/:token`** (`BoardClaim.tsx`) — not touched.
|
||||
- **`/cli-auth/:id`** (`CliAuth.tsx`) — not touched.
|
||||
- **`/*`** (`NotFound.tsx`) — breadcrumb href was `/${prefix}/dashboard`.
|
||||
Rewritten to `/${prefix}/assistant` and the button label changed from
|
||||
"Open dashboard" to "Open assistant".
|
||||
- **Onboarding wizard** (`OnboardingWizard.tsx` + `onboarding/*`) — not touched;
|
||||
previous phases already covered.
|
||||
- **`/design-guide`** (`DesignGuide.tsx`, 1532 lines) — the canonical DESIGN.md
|
||||
showcase. Skimmed; not touched (any findings would be meta since the page
|
||||
IS the design system).
|
||||
|
||||
### Plugin subsystem
|
||||
|
||||
- **`/plugins/:pluginId`** (`PluginPage.tsx`) — back link href fixed.
|
||||
- **`/instance/settings/plugins`** (`PluginManager.tsx`) — breadcrumb href fixed.
|
||||
- **`/instance/settings/plugins/:pluginId`** (`PluginSettings.tsx`) — breadcrumb
|
||||
href fixed.
|
||||
|
||||
## Top 3 recommended follow-ups (by visual impact)
|
||||
|
||||
1. **Wire the forest-green and pale-yellow token classes** — eliminates the
|
||||
`bg-[#166534]` / `bg-[#f4f692]` literals in ProjectCard, Projects,
|
||||
GlobalMicButton, and TabPlaceholder. Low-risk `index.css` addition plus a
|
||||
grep-and-replace. One commit of ~15 line touches. (MIGRATION-PLAN.md §3.)
|
||||
|
||||
2. **Drop the remaining `shadow-xs` survivors in shadcn primitives** (F1 + F2)
|
||||
and collapse their `transition-[color,box-shadow]` to `transition-colors`.
|
||||
Five files, ten lines total. Visible improvement: removes the last
|
||||
"invisible on black" shadow artifacts.
|
||||
|
||||
3. **Resolve the Basier font decision** (Deviation 2). Either license + load
|
||||
it, or formally accept Inter as the sole display font and update
|
||||
DESIGN.md §3 to match. This is the last open item from the visual repaint
|
||||
and blocks closing the MIGRATION-PLAN open-decisions list.
|
||||
|
|
@ -93,7 +93,7 @@ function AgentRunCard({
|
|||
<div className={cn(
|
||||
"flex h-[320px] flex-col overflow-hidden rounded-lg border",
|
||||
isActive
|
||||
? "border-primary/25 bg-primary/[0.04] shadow-[0_16px_40px_rgba(6,182,212,0.08)]"
|
||||
? "border-primary/25 bg-primary/[0.04]"
|
||||
: "border-border bg-background/70",
|
||||
)}>
|
||||
<div className="border-b border-border/60 px-3 py-3">
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export function InstallPromptBanner() {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="fixed bottom-16 left-4 right-4 z-50 md:bottom-auto md:top-4 md:left-auto md:right-4 md:max-w-sm bg-card border border-border rounded-lg shadow-lg p-4"
|
||||
className="fixed bottom-16 left-4 right-4 z-50 md:bottom-auto md:top-4 md:left-auto md:right-4 md:max-w-sm bg-card border border-border rounded-lg p-4"
|
||||
role="banner"
|
||||
aria-label="Install Nexus app"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) {
|
|||
if (runs.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg border border-primary/25 bg-background/80 shadow-[0_18px_50px_rgba(6,182,212,0.08)]">
|
||||
<div className="overflow-hidden rounded-lg border border-primary/25 bg-background/80">
|
||||
<div className="border-b border-border/60 bg-primary/[0.04] px-4 py-3">
|
||||
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-primary">
|
||||
Live Runs
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import { useVoice } from "../../context/VoiceContext";
|
|||
* - listening: volt fill, 1.5s pulse loop + expanding volt ring
|
||||
* - speaking: silver fill, no pulse
|
||||
*
|
||||
* Literal hex `#166534` (forest) and `#a0a0a0` (silver) are used because
|
||||
* MIGRATION-PLAN.md §3 proposes these as new CSS variables but has not yet
|
||||
* shipped them. Volt uses the `text-primary`/`bg-primary` semantic token.
|
||||
* Uses the direct brand Tailwind aliases (`bg-forest`, `bg-silver`) that
|
||||
* resolve to the `--forest` / `--silver` CSS vars declared in index.css.
|
||||
* Volt uses the `text-primary`/`bg-primary` semantic token.
|
||||
*/
|
||||
export function GlobalMicButton() {
|
||||
const { state, toggleListening, hasQueuedVoice } = useVoice();
|
||||
|
|
@ -53,7 +53,7 @@ export function GlobalMicButton() {
|
|||
aria-hidden="true"
|
||||
className={cn(
|
||||
"h-2 w-2 rounded-full",
|
||||
hasQueuedVoice ? "bg-primary" : "bg-[#166534]",
|
||||
hasQueuedVoice ? "bg-primary" : "bg-forest",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -72,7 +72,7 @@ export function GlobalMicButton() {
|
|||
{state === "speaking" && (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="h-2 w-2 rounded-full bg-[#a0a0a0]"
|
||||
className="h-2 w-2 rounded-full bg-silver"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ describe("ProjectCard", () => {
|
|||
it("renders a forest-green dot for idle status", () => {
|
||||
const api = render({ slug: "A", status: "idle", progress: 0 });
|
||||
expect(api.dot?.dataset.status).toBe("idle");
|
||||
expect(api.dot?.className).toContain("bg-[#166534]");
|
||||
expect(api.dot?.className).toContain("bg-forest");
|
||||
});
|
||||
|
||||
it("renders a pulsing volt dot for working status", () => {
|
||||
|
|
@ -150,7 +150,7 @@ describe("ProjectCard", () => {
|
|||
it("renders a pale-yellow dot for waiting status", () => {
|
||||
const api = render({ slug: "A", status: "waiting", progress: 0 });
|
||||
expect(api.dot?.dataset.status).toBe("waiting");
|
||||
expect(api.dot?.className).toContain("bg-[#f4f692]");
|
||||
expect(api.dot?.className).toContain("bg-volt-pale");
|
||||
});
|
||||
|
||||
it("sets progress bar fill width from normal progress value", () => {
|
||||
|
|
|
|||
|
|
@ -43,13 +43,12 @@ export interface ProjectCardProps {
|
|||
|
||||
function StatusDot({ status }: { status: ProjectCardStatus }) {
|
||||
if (status === "waiting") {
|
||||
// Pale yellow literal hex — token doesn't ship until MIGRATION-PLAN §3.
|
||||
return (
|
||||
<span
|
||||
data-testid="project-card-status-dot"
|
||||
data-status="waiting"
|
||||
aria-label="Waiting on gate"
|
||||
className="h-2 w-2 rounded-full bg-[#f4f692]"
|
||||
className="h-2 w-2 rounded-full bg-volt-pale"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -68,7 +67,7 @@ function StatusDot({ status }: { status: ProjectCardStatus }) {
|
|||
data-testid="project-card-status-dot"
|
||||
data-status="idle"
|
||||
aria-label="Idle"
|
||||
className="h-2 w-2 rounded-full bg-[#166534]"
|
||||
className="h-2 w-2 rounded-full bg-forest"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ function MilestoneBullet({ item }: { item: MilestoneItem }) {
|
|||
data-testid="milestone-bullet"
|
||||
data-state="next-gate"
|
||||
aria-hidden="true"
|
||||
className="inline-block w-5 shrink-0 text-[#f4f692]"
|
||||
className="inline-block w-5 shrink-0 text-volt-pale"
|
||||
>
|
||||
[○]
|
||||
</span>
|
||||
|
|
@ -135,7 +135,7 @@ function MilestoneChecklistCard({
|
|||
{item.state === "next-gate" ? (
|
||||
<span
|
||||
data-testid="milestone-next-gate-marker"
|
||||
className="ml-2 text-[11px] font-semibold uppercase tracking-[0.12em] text-[#f4f692]"
|
||||
className="ml-2 text-[11px] font-semibold uppercase tracking-[0.12em] text-volt-pale"
|
||||
>
|
||||
← Next gate
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export function TabPlaceholder({ testId, title, gapReason }: TabPlaceholderProps
|
|||
<p className="text-[14px] text-muted-foreground">
|
||||
<span
|
||||
data-testid={`${testId}-gap`}
|
||||
className="inline-block rounded border border-[#f4f692]/40 bg-[#f4f692]/5 px-2 py-0.5 text-[11px] font-semibold uppercase tracking-[0.1em] text-[#f4f692]"
|
||||
className="inline-block rounded border border-volt-pale/40 bg-volt-pale/5 px-2 py-0.5 text-[11px] font-semibold uppercase tracking-[0.1em] text-volt-pale"
|
||||
>
|
||||
Phase 11 data gap
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export function TelegramSection() {
|
|||
<span
|
||||
className={cn(
|
||||
"text-xs font-medium",
|
||||
running ? "text-[#166534] dark:text-primary" : "text-muted-foreground",
|
||||
running ? "text-forest dark:text-primary" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{statusQuery.isLoading ? "..." : running ? "Running" : "Not running"}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const buttonVariants = cva(
|
|||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
"border bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ function Checkbox({
|
|||
<CheckboxPrimitive.Root
|
||||
data-slot="checkbox"
|
||||
className={cn(
|
||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ function SelectTrigger({
|
|||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const toggleVariants = cva(
|
|||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 min-w-9 px-2",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function saveCompanyPath(companyId: string, path: string) {
|
|||
|
||||
/**
|
||||
* Remembers the last visited page per company and navigates to it on company switch.
|
||||
* Falls back to /dashboard if no page was previously visited for a company.
|
||||
* Falls back to /assistant if no page was previously visited for a company.
|
||||
*/
|
||||
export function useCompanyPageMemory() {
|
||||
const { companies, selectedCompanyId, selectedCompany, selectionSource } = useCompany();
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { accessApi } from "../api/access";
|
||||
import { ApiError } from "../api/client";
|
||||
import { approvalsApi } from "../api/approvals";
|
||||
import { dashboardApi } from "../api/dashboard";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import {
|
||||
computeInboxBadgeData,
|
||||
getRecentTouchedIssues,
|
||||
loadDismissedInboxItems,
|
||||
saveDismissedInboxItems,
|
||||
loadReadInboxItems,
|
||||
saveReadInboxItems,
|
||||
READ_ITEMS_KEY,
|
||||
} from "../lib/inbox";
|
||||
|
||||
const INBOX_ISSUE_STATUSES = "backlog,todo,in_progress,in_review,blocked,done";
|
||||
|
||||
export function useDismissedInboxItems() {
|
||||
const [dismissed, setDismissed] = useState<Set<string>>(loadDismissedInboxItems);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorage = (event: StorageEvent) => {
|
||||
if (event.key !== "paperclip:inbox:dismissed") return;
|
||||
setDismissed(loadDismissedInboxItems());
|
||||
};
|
||||
window.addEventListener("storage", handleStorage);
|
||||
return () => window.removeEventListener("storage", handleStorage);
|
||||
}, []);
|
||||
|
||||
const dismiss = (id: string) => {
|
||||
setDismissed((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(id);
|
||||
saveDismissedInboxItems(next);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return { dismissed, dismiss };
|
||||
}
|
||||
|
||||
export function useReadInboxItems() {
|
||||
const [readItems, setReadItems] = useState<Set<string>>(loadReadInboxItems);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorage = (event: StorageEvent) => {
|
||||
if (event.key !== READ_ITEMS_KEY) return;
|
||||
setReadItems(loadReadInboxItems());
|
||||
};
|
||||
window.addEventListener("storage", handleStorage);
|
||||
return () => window.removeEventListener("storage", handleStorage);
|
||||
}, []);
|
||||
|
||||
const markRead = (id: string) => {
|
||||
setReadItems((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(id);
|
||||
saveReadInboxItems(next);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const markUnread = (id: string) => {
|
||||
setReadItems((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(id);
|
||||
saveReadInboxItems(next);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return { readItems, markRead, markUnread };
|
||||
}
|
||||
|
||||
export function useInboxBadge(companyId: string | null | undefined) {
|
||||
const { dismissed } = useDismissedInboxItems();
|
||||
|
||||
const { data: approvals = [] } = useQuery({
|
||||
queryKey: queryKeys.approvals.list(companyId!),
|
||||
queryFn: () => approvalsApi.list(companyId!),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
|
||||
const { data: joinRequests = [] } = useQuery({
|
||||
queryKey: queryKeys.access.joinRequests(companyId!),
|
||||
queryFn: async () => {
|
||||
try {
|
||||
return await accessApi.listJoinRequests(companyId!, "pending_approval");
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
|
||||
return [];
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
enabled: !!companyId,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const { data: dashboard } = useQuery({
|
||||
queryKey: queryKeys.dashboard(companyId!),
|
||||
queryFn: () => dashboardApi.summary(companyId!),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
|
||||
const { data: mineIssuesRaw = [] } = useQuery({
|
||||
queryKey: queryKeys.issues.listMineByMe(companyId!),
|
||||
queryFn: () =>
|
||||
issuesApi.list(companyId!, {
|
||||
touchedByUserId: "me",
|
||||
inboxArchivedByUserId: "me",
|
||||
status: INBOX_ISSUE_STATUSES,
|
||||
}),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
|
||||
const mineIssues = useMemo(() => getRecentTouchedIssues(mineIssuesRaw), [mineIssuesRaw]);
|
||||
|
||||
const { data: heartbeatRuns = [] } = useQuery({
|
||||
queryKey: queryKeys.heartbeats(companyId!),
|
||||
queryFn: () => heartbeatsApi.list(companyId!),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
computeInboxBadgeData({
|
||||
approvals,
|
||||
joinRequests,
|
||||
dashboard,
|
||||
heartbeatRuns,
|
||||
mineIssues,
|
||||
dismissed,
|
||||
}),
|
||||
[approvals, joinRequests, dashboard, heartbeatRuns, mineIssues, dismissed],
|
||||
);
|
||||
}
|
||||
106
ui/src/index.css
106
ui/src/index.css
|
|
@ -202,72 +202,116 @@
|
|||
--chart-role-5: #a0a0a0; /* silver — support/quinary */
|
||||
}
|
||||
|
||||
/* -- highlight.js syntax theme overrides (chat code blocks) ------------- */
|
||||
/* -- highlight.js syntax theme overrides (chat code blocks) -------------
|
||||
* DESIGN.md §4: dark surface, terminal aesthetic, neon + white syntax.
|
||||
* Strictly 4 hues: volt (or forest in light), white/near-black, silver, forest.
|
||||
* No rainbow highlighting — traditional per-class hues collapse into this scheme.
|
||||
*/
|
||||
|
||||
/* Base hljs surface — themed via CSS vars */
|
||||
/* Base hljs surface — inherits the code-block container (bg-card / bg-muted). */
|
||||
.hljs {
|
||||
background: var(--card) !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* Light mode hljs (default) */
|
||||
/* Light mode hljs (default :root scope) — forest primary, near-black values. */
|
||||
.hljs-keyword,
|
||||
.hljs-built_in,
|
||||
.hljs-type,
|
||||
.hljs-selector-tag,
|
||||
.hljs-literal,
|
||||
.hljs-number {
|
||||
color: #166534;
|
||||
.hljs-tag {
|
||||
color: var(--forest);
|
||||
}
|
||||
.hljs-string,
|
||||
.hljs-attr {
|
||||
color: #4f5100;
|
||||
.hljs-attr,
|
||||
.hljs-regexp,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: var(--silver);
|
||||
}
|
||||
.hljs-function,
|
||||
.hljs-number,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-title,
|
||||
.hljs-built_in,
|
||||
.hljs-title.function_,
|
||||
.hljs-section,
|
||||
.hljs-name,
|
||||
.hljs-params,
|
||||
.hljs-variable,
|
||||
.hljs-type,
|
||||
.hljs-meta {
|
||||
color: #0a0a0a;
|
||||
.hljs-template-variable,
|
||||
.hljs-function {
|
||||
color: var(--foreground);
|
||||
}
|
||||
.hljs-comment {
|
||||
color: #6b6b6b;
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: var(--silver);
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-deletion,
|
||||
.hljs-selector-class {
|
||||
color: #dc2626;
|
||||
color: var(--destructive);
|
||||
}
|
||||
.hljs-addition {
|
||||
color: var(--success);
|
||||
}
|
||||
.hljs-link {
|
||||
color: var(--forest);
|
||||
text-decoration: underline;
|
||||
}
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Dark mode hljs — overrides the light defaults */
|
||||
/* Dark mode hljs — volt primary on near-black surface, terminal-like. */
|
||||
.dark .hljs {
|
||||
background: #141414 !important;
|
||||
color: #ffffff !important;
|
||||
background: var(--card) !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
.dark .hljs-keyword,
|
||||
.dark .hljs-built_in,
|
||||
.dark .hljs-type,
|
||||
.dark .hljs-selector-tag,
|
||||
.dark .hljs-literal,
|
||||
.dark .hljs-number,
|
||||
.dark .hljs-meta {
|
||||
color: #faff69;
|
||||
.dark .hljs-tag {
|
||||
color: var(--volt);
|
||||
}
|
||||
.dark .hljs-string,
|
||||
.dark .hljs-attr,
|
||||
.dark .hljs-type {
|
||||
color: #f4f692;
|
||||
.dark .hljs-regexp,
|
||||
.dark .hljs-symbol,
|
||||
.dark .hljs-bullet {
|
||||
color: var(--silver);
|
||||
}
|
||||
.dark .hljs-function,
|
||||
.dark .hljs-number,
|
||||
.dark .hljs-literal,
|
||||
.dark .hljs-meta,
|
||||
.dark .hljs-title,
|
||||
.dark .hljs-built_in,
|
||||
.dark .hljs-title.function_,
|
||||
.dark .hljs-section,
|
||||
.dark .hljs-name,
|
||||
.dark .hljs-params,
|
||||
.dark .hljs-variable,
|
||||
.dark .hljs-template-variable,
|
||||
.dark .hljs-function,
|
||||
.dark .hljs-selector-class {
|
||||
color: #ffffff;
|
||||
color: var(--foreground);
|
||||
}
|
||||
.dark .hljs-comment {
|
||||
color: #a0a0a0;
|
||||
.dark .hljs-comment,
|
||||
.dark .hljs-quote {
|
||||
color: var(--silver);
|
||||
font-style: italic;
|
||||
}
|
||||
.dark .hljs-deletion {
|
||||
color: #ef4444;
|
||||
color: var(--destructive);
|
||||
}
|
||||
.dark .hljs-addition {
|
||||
color: var(--success);
|
||||
}
|
||||
.dark .hljs-link {
|
||||
color: var(--volt);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ export function sanitizeRememberedPathForCompany(params: {
|
|||
path: string | null | undefined;
|
||||
companyPrefix: string;
|
||||
}): string {
|
||||
const relativePath = params.path ? toCompanyRelativePath(params.path) : "/dashboard";
|
||||
const relativePath = params.path ? toCompanyRelativePath(params.path) : "/assistant";
|
||||
if (!isRememberableCompanyPath(relativePath)) {
|
||||
return "/dashboard";
|
||||
return "/assistant";
|
||||
}
|
||||
|
||||
const pathname = relativePath.split("?")[0] ?? "";
|
||||
|
|
@ -57,7 +57,7 @@ export function sanitizeRememberedPathForCompany(params: {
|
|||
identifierMatch &&
|
||||
normalizeCompanyPrefix(identifierMatch[1] ?? "") !== normalizeCompanyPrefix(params.companyPrefix)
|
||||
) {
|
||||
return "/dashboard";
|
||||
return "/assistant";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1104,7 +1104,7 @@ function LatestRunCard({ runs, agentId }: { runs: HeartbeatRun[]; agentId: strin
|
|||
to={`/agents/${agentId}/runs/${run.id}`}
|
||||
className={cn(
|
||||
"block border rounded-lg p-4 space-y-2 w-full no-underline transition-colors hover:bg-muted/50 cursor-pointer",
|
||||
isLive ? "border-primary/30 shadow-[0_0_12px_rgba(6,182,212,0.08)]" : "border-border"
|
||||
isLive ? "border-primary/30" : "border-border"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -867,7 +867,7 @@ export function CompanyImport() {
|
|||
body: `${result.company.name}: ${result.agents.length} agent${result.agents.length === 1 ? "" : "s"} processed.`,
|
||||
});
|
||||
// Force a fresh dashboard load so newly imported agents are immediately visible.
|
||||
window.location.assign(`/${importedCompany.issuePrefix}/dashboard`);
|
||||
window.location.assign(`/${importedCompany.issuePrefix}/assistant`);
|
||||
},
|
||||
onError: (err) => {
|
||||
pushToast({
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ export function CompanySettings() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? VOCAB.company, href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? VOCAB.company, href: "/assistant" },
|
||||
{ label: "Settings" }
|
||||
]);
|
||||
}, [setBreadcrumbs, selectedCompany?.name]);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function NotFoundPage({ scope = "global", requestedPrefix }: NotFoundPage
|
|||
}, [setBreadcrumbs]);
|
||||
|
||||
const fallbackCompany = selectedCompany ?? companies[0] ?? null;
|
||||
const dashboardHref = fallbackCompany ? `/${fallbackCompany.issuePrefix}/dashboard` : "/";
|
||||
const assistantHref = fallbackCompany ? `/${fallbackCompany.issuePrefix}/assistant` : "/";
|
||||
const currentPath = `${location.pathname}${location.search}${location.hash}`;
|
||||
const normalizedPrefix = requestedPrefix?.toUpperCase();
|
||||
|
||||
|
|
@ -52,9 +52,9 @@ export function NotFoundPage({ scope = "global", requestedPrefix }: NotFoundPage
|
|||
|
||||
<div className="mt-5 flex flex-wrap gap-2">
|
||||
<Button asChild>
|
||||
<Link to={dashboardHref}>
|
||||
<Link to={assistantHref}>
|
||||
<Compass className="mr-1.5 h-4 w-4" />
|
||||
Open dashboard
|
||||
Open assistant
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline" asChild>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export function PluginManager() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? VOCAB.company, href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? VOCAB.company, href: "/assistant" },
|
||||
{ label: "Settings", href: "/instance/settings/heartbeats" },
|
||||
{ label: "Plugins" },
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export function PluginPage() {
|
|||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link to={companyPrefix ? `/${companyPrefix}/dashboard` : "/dashboard"}>
|
||||
<Link to={companyPrefix ? `/${companyPrefix}/assistant` : "/assistant"}>
|
||||
<ArrowLeft className="h-4 w-4 mr-1" />
|
||||
Back
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export function PluginSettings() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? VOCAB.company, href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? VOCAB.company, href: "/assistant" },
|
||||
{ label: "Settings", href: "/instance/settings/heartbeats" },
|
||||
{ label: "Plugins", href: "/instance/settings/plugins" },
|
||||
{ label: plugin?.manifestJson?.displayName ?? plugin?.packageName ?? "Plugin Details" },
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ export function ProjectDetail() {
|
|||
const name = updatedProject?.name ?? project?.name ?? "Project";
|
||||
if (archived) {
|
||||
pushToast({ title: `"${name}" has been archived`, tone: "success" });
|
||||
navigate("/dashboard");
|
||||
navigate("/assistant");
|
||||
} else {
|
||||
pushToast({ title: `"${name}" has been unarchived`, tone: "success" });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,9 +225,9 @@ export function Projects() {
|
|||
data-testid="projects-empty-cta"
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 rounded-md px-5 py-3",
|
||||
"bg-[#166534] text-white",
|
||||
"bg-forest text-white",
|
||||
"text-[14px] font-semibold uppercase tracking-[0.1em]",
|
||||
"hover:bg-[#166534]/90 transition-colors",
|
||||
"hover:bg-forest/90 transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
||||
)}
|
||||
>
|
||||
|
|
@ -250,9 +250,9 @@ export function Projects() {
|
|||
data-testid="projects-new-project-cta"
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 rounded-md px-4 py-2",
|
||||
"bg-[#166534] text-white",
|
||||
"bg-forest text-white",
|
||||
"text-[12px] font-semibold uppercase tracking-[0.12em]",
|
||||
"hover:bg-[#166534]/90 transition-colors",
|
||||
"hover:bg-forest/90 transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ function LiveWidgetPreview({
|
|||
density: TranscriptDensity;
|
||||
}) {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg border border-primary/25 bg-background/85 shadow-[0_20px_50px_rgba(6,182,212,0.10)]">
|
||||
<div className="overflow-hidden rounded-lg border border-primary/25 bg-background/85">
|
||||
<div className="border-b border-border/60 bg-primary/[0.05] px-5 py-4">
|
||||
<div className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">
|
||||
Live Runs
|
||||
|
|
@ -225,7 +225,7 @@ export function RunTranscriptUxLab() {
|
|||
className={cn(
|
||||
"w-full rounded-lg border px-4 py-3 text-left transition-all",
|
||||
selectedSurface === option.id
|
||||
? "border-primary/35 bg-primary/[0.10] shadow-[0_12px_24px_rgba(6,182,212,0.12)]"
|
||||
? "border-primary/35 bg-primary/[0.10]"
|
||||
: "border-border/70 bg-background/70 hover:border-primary/20 hover:bg-primary/[0.04]",
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export function SkillBrowser() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? "Workspace", href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? "Workspace", href: "/assistant" },
|
||||
{ label: "Skills" },
|
||||
]);
|
||||
}, [selectedCompany?.name, setBreadcrumbs]);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export function SkillDetail() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? "Workspace", href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? "Workspace", href: "/assistant" },
|
||||
{ label: "Skills", href: "../skills" },
|
||||
{ label: skill?.name ?? "Skill" },
|
||||
]);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue