feat(theme): create unified theme system with semantic tokens and migrate core layout
This commit is contained in:
parent
b31e8ddade
commit
fcc1482967
10 changed files with 655 additions and 81 deletions
318
docs/plans/2025-02-26-theme-system-design.md
Normal file
318
docs/plans/2025-02-26-theme-system-design.md
Normal file
|
|
@ -0,0 +1,318 @@
|
||||||
|
# Comprehensive Theme System Design
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
We have colors scattered across 67+ files:
|
||||||
|
- Hardcoded hex: `bg-[#0a111a]`, `bg-[#111f2b]`, `bg-[#14202e]`
|
||||||
|
- Inline styles: `style={{ backgroundColor: '#1a2d3d' }}`
|
||||||
|
- Arbitrary Tailwind: `bg-white/5`, `border-white/10`
|
||||||
|
- Mixed variable systems: `--ui-bg-*`, `--color-bg-*`, etc.
|
||||||
|
|
||||||
|
## The Solution: Unified Theme System
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
```
|
||||||
|
src/styles/
|
||||||
|
themes/
|
||||||
|
index.css ← Theme definitions + data-attribute switching
|
||||||
|
tokens.css ← Base CSS variable names (no values)
|
||||||
|
components/
|
||||||
|
surfaces.css ← Surface layer utilities
|
||||||
|
interactions.css ← Hover, focus, active states
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token Architecture (12 Semantic Categories)
|
||||||
|
|
||||||
|
#### 1. SURFACE LAYERS (Backgrounds)
|
||||||
|
```css
|
||||||
|
--surface-backdrop: /* Page background */
|
||||||
|
--surface-elevated: /* Header - sits on top */
|
||||||
|
--surface-primary: /* Left sidebar */
|
||||||
|
--surface-secondary: /* Main content area */
|
||||||
|
--surface-tertiary: /* Panels/cards within sidebars */
|
||||||
|
--surface-quaternary: /* Cards */
|
||||||
|
--surface-overlay: /* Modals, dropdowns, drawers */
|
||||||
|
--surface-input: /* Form fields, inputs */
|
||||||
|
--surface-hover: /* Hover states */
|
||||||
|
--surface-active: /* Active/selected states */
|
||||||
|
--surface-tooltip: /* Tooltips, popovers */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. BORDERS
|
||||||
|
```css
|
||||||
|
--border-subtle: /* Dividers between sections */
|
||||||
|
--border-default: /* Card borders */
|
||||||
|
--border-strong: /* Focus rings, selected states */
|
||||||
|
--border-accent: /* Colored status borders */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. TEXT
|
||||||
|
```css
|
||||||
|
--text-primary: /* Headlines, important text */
|
||||||
|
--text-secondary: /* Body text */
|
||||||
|
--text-tertiary: /* Muted, hints */
|
||||||
|
--text-disabled: /* Disabled elements */
|
||||||
|
--text-inverse: /* Text on colored backgrounds */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. ACCENTS (Functional)
|
||||||
|
```css
|
||||||
|
--accent-info: /* Cyan - links, actions */
|
||||||
|
--accent-success: /* Green - ready, done */
|
||||||
|
--accent-warning: /* Amber - in progress */
|
||||||
|
--accent-danger: /* Red - blocked, errors */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. ACCENTS (Aurora Glow)
|
||||||
|
```css
|
||||||
|
--glow-info: /* Box-shadow glow for info */
|
||||||
|
--glow-success: /* Box-shadow glow for success */
|
||||||
|
--glow-warning: /* Box-shadow glow for warning */
|
||||||
|
--glow-danger: /* Box-shadow glow for danger */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. GRAPH COLORS
|
||||||
|
```css
|
||||||
|
--graph-node-default: /* Default node background */
|
||||||
|
--graph-node-epic: /* Epic node accent */
|
||||||
|
--graph-edge-default: /* Default edge color */
|
||||||
|
--graph-edge-selected: /* Selected edge color */
|
||||||
|
--graph-edge-cycle: /* Cycle warning color */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7. SEMANTIC ALPHAS
|
||||||
|
Instead of `bg-white/5` and `bg-black/40`, use themeable alphas:
|
||||||
|
```css
|
||||||
|
--alpha-white-low: /* 5% white */
|
||||||
|
--alpha-white-medium: /* 10% white */
|
||||||
|
--alpha-white-high: /* 20% white */
|
||||||
|
--alpha-black-low: /* 10% black */
|
||||||
|
--alpha-black-medium: /* 40% black */
|
||||||
|
--alpha-black-high: /* 80% black */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8. STATUS COLORS
|
||||||
|
```css
|
||||||
|
--status-ready: /* Ready/open status */
|
||||||
|
--status-in-progress: /* In progress status */
|
||||||
|
--status-blocked: /* Blocked status */
|
||||||
|
--status-closed: /* Closed/done status */
|
||||||
|
--status-deferred: /* Deferred status */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 9. SHADOWS
|
||||||
|
```css
|
||||||
|
--shadow-sm: /* Subtle elevation */
|
||||||
|
--shadow-md: /* Cards */
|
||||||
|
--shadow-lg: /* Modals, drawers */
|
||||||
|
--shadow-glow-info: /* Aurora glow */
|
||||||
|
--shadow-glow-success: /* Aurora glow */
|
||||||
|
--shadow-glow-warning: /* Aurora glow */
|
||||||
|
--shadow-glow-danger: /* Aurora glow */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 10. AGENT/ROLE COLORS
|
||||||
|
```css
|
||||||
|
--agent-role-ui: /* UI role color */
|
||||||
|
--agent-role-graph: /* Graph role color */
|
||||||
|
--agent-role-orchestrator: /* Orchestrator role */
|
||||||
|
--agent-role-researcher: /* Researcher role */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 11. SCROLLBARS
|
||||||
|
```css
|
||||||
|
--scrollbar-track: /* Scrollbar track */
|
||||||
|
--scrollbar-thumb: /* Scrollbar thumb */
|
||||||
|
--scrollbar-thumb-hover: /* Scrollbar thumb hover */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12. CODE/SYNTAX
|
||||||
|
```css
|
||||||
|
--code-background: /* Code block background */
|
||||||
|
--code-text: /* Code text color */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Theme Definitions (Example)
|
||||||
|
|
||||||
|
### Aurora Theme (Current)
|
||||||
|
```css
|
||||||
|
[data-theme="aurora"] {
|
||||||
|
/* Surfaces - Warm Charcoal */
|
||||||
|
--surface-backdrop: #181716;
|
||||||
|
--surface-elevated: #131211;
|
||||||
|
--surface-primary: #1f1e1d;
|
||||||
|
--surface-secondary: #242322;
|
||||||
|
--surface-tertiary: #282725;
|
||||||
|
--surface-quaternary: #302e2c;
|
||||||
|
--surface-overlay: #0d0c0b;
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--border-subtle: rgba(180, 175, 165, 0.15);
|
||||||
|
--border-default: rgba(180, 175, 165, 0.25);
|
||||||
|
--border-strong: rgba(180, 175, 165, 0.4);
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--text-primary: #f0eeea;
|
||||||
|
--text-secondary: #c9c5bc;
|
||||||
|
--text-tertiary: #a8a49a;
|
||||||
|
|
||||||
|
/* Accents */
|
||||||
|
--accent-info: #35c9ff;
|
||||||
|
--accent-success: #35d98f;
|
||||||
|
--accent-warning: #ffb24a;
|
||||||
|
--accent-danger: #ff4c72;
|
||||||
|
|
||||||
|
/* Glows */
|
||||||
|
--glow-info: 0 0 20px rgba(53, 201, 255, 0.3);
|
||||||
|
--glow-success: 0 0 20px rgba(53, 217, 143, 0.3);
|
||||||
|
|
||||||
|
/* Graph */
|
||||||
|
--graph-node-default: rgba(48, 46, 44, 0.8);
|
||||||
|
--graph-node-epic: rgba(53, 201, 255, 0.15);
|
||||||
|
--graph-edge-default: rgba(180, 175, 165, 0.3);
|
||||||
|
--graph-edge-selected: #35c9ff;
|
||||||
|
|
||||||
|
/* Alphas */
|
||||||
|
--alpha-white-low: rgba(240, 238, 234, 0.05);
|
||||||
|
--alpha-white-medium: rgba(240, 238, 234, 0.1);
|
||||||
|
--alpha-black-medium: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Midnight Theme (Future)
|
||||||
|
```css
|
||||||
|
[data-theme="midnight"] {
|
||||||
|
--surface-backdrop: #0a0a0f;
|
||||||
|
--surface-elevated: #050508;
|
||||||
|
--surface-primary: #111118;
|
||||||
|
--surface-secondary: #151520;
|
||||||
|
--surface-quaternary: #1e1e2e;
|
||||||
|
|
||||||
|
--accent-info: #8b5cf6;
|
||||||
|
--accent-success: #10b981;
|
||||||
|
|
||||||
|
/* Same structure, different values */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
### Phase 1: Create Token System
|
||||||
|
1. Create `src/styles/themes/index.css` with token definitions
|
||||||
|
2. Create `src/styles/themes/tokens.css` with token names
|
||||||
|
3. Add data-theme attribute to layout
|
||||||
|
|
||||||
|
### Phase 2: Component Audit & Migration
|
||||||
|
For each component:
|
||||||
|
1. Replace hardcoded colors with semantic tokens
|
||||||
|
2. Replace `bg-white/5` with `bg-[var(--alpha-white-low)]`
|
||||||
|
3. Replace `bg-[#0a111a]` with appropriate surface token
|
||||||
|
|
||||||
|
### Phase 3: Graph/Flow Colors
|
||||||
|
1. Extract all graph colors to `--graph-*` tokens
|
||||||
|
2. Update ReactFlow styles to use CSS variables
|
||||||
|
|
||||||
|
### Phase 4: Validation
|
||||||
|
1. Create theme preview page showing all tokens
|
||||||
|
2. Verify no hardcoded colors remain
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Before (Chaos):
|
||||||
|
```tsx
|
||||||
|
<div className="bg-[#0a111a] border border-white/10">
|
||||||
|
<span className="text-[#a8a49a]">Text</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### After (Clean):
|
||||||
|
```tsx
|
||||||
|
<div className="bg-[var(--surface-tertiary)] border border-[var(--border-default)]">
|
||||||
|
<span className="text-[var(--text-tertiary)]">Text</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Tailwind utilities (even cleaner):
|
||||||
|
```tsx
|
||||||
|
<div className="surface-tertiary border-default">
|
||||||
|
<span className="text-tertiary">Text</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Mapping Guide
|
||||||
|
|
||||||
|
| Component | Current | New Token |
|
||||||
|
|-----------|---------|-----------|
|
||||||
|
| TopBar | `bg-[var(--ui-bg-header)]` | `bg-[var(--surface-elevated)]` |
|
||||||
|
| LeftPanel | `bg-[var(--ui-bg-shell)]` | `bg-[var(--surface-primary)]` |
|
||||||
|
| Main content | `bg-[var(--ui-bg-main)]` | `bg-[var(--surface-secondary)]` |
|
||||||
|
| SocialCard | `bg-[var(--ui-bg-card)]` | `bg-[var(--surface-quaternary)]` |
|
||||||
|
| AssignmentPanel | `bg-[#0a111a]` | `bg-[var(--surface-tertiary)]` |
|
||||||
|
| Modal/Drawer | `bg-[#0d0c0b]` | `bg-[var(--surface-overlay)]` |
|
||||||
|
| Input fields | `bg-[#0f1824]` | `bg-[var(--surface-input)]` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files That Need Migration (Priority Order)
|
||||||
|
|
||||||
|
### HIGH (Core UI):
|
||||||
|
1. `src/app/globals.css` - Define tokens
|
||||||
|
2. `src/components/shared/unified-shell.tsx` - Main layout
|
||||||
|
3. `src/components/shared/top-bar.tsx` - Header
|
||||||
|
4. `src/components/shared/left-panel.tsx` - Sidebar
|
||||||
|
5. `src/components/shared/right-panel.tsx` - Right panel
|
||||||
|
6. `src/components/social/social-card.tsx` - Cards
|
||||||
|
|
||||||
|
### MEDIUM (Active Views):
|
||||||
|
7. `src/components/graph/assignment-panel.tsx` - Heavy hardcoded colors
|
||||||
|
8. `src/components/graph/graph-node-card.tsx` - Graph nodes
|
||||||
|
9. `src/components/activity/swarm-command-feed.tsx` - Activity feed
|
||||||
|
10. `src/components/graph/smart-dag.tsx` - Graph background
|
||||||
|
|
||||||
|
### LOWER (Secondary):
|
||||||
|
11. All modal/dialog components
|
||||||
|
12. All form/input components
|
||||||
|
13. Kanban components (if still used)
|
||||||
|
14. Swarm components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] Zero hardcoded hex colors in components (except for data viz)
|
||||||
|
- [ ] Zero `bg-white/X` or `bg-black/X` - all use `--alpha-*` tokens
|
||||||
|
- [ ] Theme switcher works instantly without reload
|
||||||
|
- [ ] All 12 token categories have values for each theme
|
||||||
|
- [ ] Visual regression test passes (no unintended changes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## For You (The Human)
|
||||||
|
|
||||||
|
Once this is implemented, you can just say:
|
||||||
|
> "Change the sidebar to be slightly lighter in the aurora theme"
|
||||||
|
|
||||||
|
And I'll know exactly where to go:
|
||||||
|
```css
|
||||||
|
[data-theme="aurora"] {
|
||||||
|
--surface-primary: #252422; /* Changed from #1f1e1d */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
> "Make the aurora glow more subtle"
|
||||||
|
|
||||||
|
```css
|
||||||
|
[data-theme="aurora"] {
|
||||||
|
--glow-info: 0 0 12px rgba(53, 201, 255, 0.15); /* Reduced from 20px/0.3 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything in one place. No hunting through 67 files.
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../styles/themes/index.css';
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export const metadata: Metadata = {
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" data-theme="aurora">
|
||||||
<body className={notoSans.variable}>{children}</body>
|
<body className={notoSans.variable}>{children}</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,10 @@ function buildEntries(issues: BeadIssue[]): EpicEntry[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusDot(status: BeadIssue['status']): string {
|
function statusDot(status: BeadIssue['status']): string {
|
||||||
if (status === 'blocked') return 'bg-[var(--ui-accent-blocked)]';
|
if (status === 'blocked') return 'bg-[var(--accent-danger)]';
|
||||||
if (status === 'in_progress') return 'bg-[var(--ui-accent-warning)]';
|
if (status === 'in_progress') return 'bg-[var(--accent-warning)]';
|
||||||
if (status === 'closed') return 'bg-[var(--ui-text-muted)]';
|
if (status === 'closed') return 'bg-[var(--text-tertiary)]';
|
||||||
return 'bg-[var(--ui-accent-ready)]';
|
return 'bg-[var(--accent-success)]';
|
||||||
}
|
}
|
||||||
|
|
||||||
function rowTone(entry: EpicEntry): string {
|
function rowTone(entry: EpicEntry): string {
|
||||||
|
|
@ -147,7 +147,7 @@ function rowTone(entry: EpicEntry): string {
|
||||||
if (entry.readyCount > 0) {
|
if (entry.readyCount > 0) {
|
||||||
return 'rgba(53, 217, 143, 0.08)';
|
return 'rgba(53, 217, 143, 0.08)';
|
||||||
}
|
}
|
||||||
return 'var(--ui-bg-panel)';
|
return 'var(--surface-tertiary)';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTaskMatch(task: BeadIssue, filters: LeftPanelFilters): boolean {
|
function isTaskMatch(task: BeadIssue, filters: LeftPanelFilters): boolean {
|
||||||
|
|
@ -190,9 +190,9 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="flex h-full min-h-0 overflow-hidden flex-col bg-[var(--ui-bg-shell)] border-r border-[var(--ui-border-strong)]" data-testid="left-panel">
|
<aside className="flex h-full min-h-0 overflow-hidden flex-col bg-[var(--surface-primary)] border-r border-[var(--border-strong)]" data-testid="left-panel">
|
||||||
<div className="px-4 py-3">
|
<div className="px-4 py-3">
|
||||||
<div className="mb-3 flex items-center gap-1 rounded-xl bg-[var(--ui-bg-panel)] p-1 border border-[var(--ui-border-strong)]">
|
<div className="mb-3 flex items-center gap-1 rounded-xl bg-[var(--surface-tertiary)] p-1 border border-[var(--border-strong)]">
|
||||||
{views.map((item) => {
|
{views.map((item) => {
|
||||||
const active = view === item.id;
|
const active = view === item.id;
|
||||||
return (
|
return (
|
||||||
|
|
@ -201,10 +201,10 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setView(item.id)}
|
onClick={() => setView(item.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-lg px-2 py-1 text-xs font-semibold uppercase tracking-[0.12em] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent-info)]',
|
'flex-1 rounded-lg px-2 py-1 text-xs font-semibold uppercase tracking-[0.12em] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]',
|
||||||
active
|
active
|
||||||
? 'bg-[#183149] text-[var(--ui-text-primary)]'
|
? 'bg-[var(--accent-info)]/20 text-[var(--accent-info)]'
|
||||||
: 'text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)]',
|
: 'text-[var(--text-tertiary)] hover:text-[var(--text-primary)]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|
@ -213,7 +213,7 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 rounded-xl bg-[var(--ui-bg-card)] p-2.5 border border-[var(--ui-border-soft)]">
|
<div className="space-y-2 rounded-xl bg-[var(--surface-quaternary)] p-2.5 border border-[var(--border-subtle)]">
|
||||||
<div className="grid grid-cols-1 gap-2">
|
<div className="grid grid-cols-1 gap-2">
|
||||||
<input
|
<input
|
||||||
value={filters.query}
|
value={filters.query}
|
||||||
|
|
@ -257,10 +257,10 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onFiltersChange({ ...filters, preset: filters.preset === 'active' ? 'all' : 'active' })}
|
onClick={() => onFiltersChange({ ...filters, preset: filters.preset === 'active' ? 'all' : 'active' })}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-lg px-2 py-1.5 text-[10px] font-semibold uppercase tracking-[0.11em] shadow-[0_8px_18px_-16px_rgba(0,0,0,0.9)] transition-colors',
|
'flex-1 rounded-lg px-2 py-1.5 text-[10px] font-semibold uppercase tracking-[0.11em] transition-colors border',
|
||||||
filters.preset === 'active'
|
filters.preset === 'active'
|
||||||
? 'bg-[#2f2618] text-[var(--ui-accent-warning)]'
|
? 'bg-[var(--accent-warning)]/15 border-[var(--accent-warning)]/40 text-[var(--accent-warning)]'
|
||||||
: 'bg-[#0f1824] text-[var(--ui-text-muted)]',
|
: 'bg-[var(--surface-quaternary)] border-[var(--border-subtle)] text-[var(--text-tertiary)]',
|
||||||
)}
|
)}
|
||||||
aria-pressed={filters.preset === 'active'}
|
aria-pressed={filters.preset === 'active'}
|
||||||
>
|
>
|
||||||
|
|
@ -270,10 +270,10 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onFiltersChange({ ...filters, preset: filters.preset === 'blocked_agents' ? 'all' : 'blocked_agents' })}
|
onClick={() => onFiltersChange({ ...filters, preset: filters.preset === 'blocked_agents' ? 'all' : 'blocked_agents' })}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-lg px-2 py-1.5 text-[10px] font-semibold uppercase tracking-[0.11em] shadow-[0_8px_18px_-16px_rgba(0,0,0,0.9)] transition-colors',
|
'flex-1 rounded-lg px-2 py-1.5 text-[10px] font-semibold uppercase tracking-[0.11em] transition-colors border',
|
||||||
filters.preset === 'blocked_agents'
|
filters.preset === 'blocked_agents'
|
||||||
? 'bg-[#2f1621] text-[var(--ui-accent-blocked)]'
|
? 'bg-[var(--accent-danger)]/15 border-[var(--accent-danger)]/40 text-[var(--accent-danger)]'
|
||||||
: 'bg-[#0f1824] text-[var(--ui-text-muted)]',
|
: 'bg-[var(--surface-quaternary)] border-[var(--border-subtle)] text-[var(--text-tertiary)]',
|
||||||
)}
|
)}
|
||||||
aria-pressed={filters.preset === 'blocked_agents'}
|
aria-pressed={filters.preset === 'blocked_agents'}
|
||||||
>
|
>
|
||||||
|
|
@ -286,8 +286,8 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-lg px-2 py-1.5 text-[10px] font-semibold uppercase tracking-[0.11em] transition-colors border',
|
'w-full rounded-lg px-2 py-1.5 text-[10px] font-semibold uppercase tracking-[0.11em] transition-colors border',
|
||||||
filters.hideClosed
|
filters.hideClosed
|
||||||
? 'bg-[var(--ui-accent-ready)]/15 border-[var(--ui-accent-ready)]/40 text-[var(--ui-accent-ready)]'
|
? 'bg-[var(--accent-success)]/15 border-[var(--accent-success)]/40 text-[var(--accent-success)]'
|
||||||
: 'bg-[var(--ui-bg-card)] border-[var(--ui-border-soft)] text-[var(--ui-text-muted)]',
|
: 'bg-[var(--surface-quaternary)] border-[var(--border-subtle)] text-[var(--text-tertiary)]',
|
||||||
)}
|
)}
|
||||||
aria-pressed={filters.hideClosed}
|
aria-pressed={filters.hideClosed}
|
||||||
>
|
>
|
||||||
|
|
@ -295,7 +295,7 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-2 font-mono text-[10px] uppercase tracking-[0.16em] text-[var(--ui-text-muted)]">Navigation / Epics</p>
|
<p className="mt-2 font-mono text-[10px] uppercase tracking-[0.16em] text-[var(--text-tertiary)]">Navigation / Epics</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto px-3 py-3 custom-scrollbar">
|
<div className="flex-1 overflow-y-auto px-3 py-3 custom-scrollbar">
|
||||||
|
|
@ -319,7 +319,7 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
const blockedPercent = total > 0 ? Math.round((blockedCount / total) * 100) : 0;
|
const blockedPercent = total > 0 ? Math.round((blockedCount / total) * 100) : 0;
|
||||||
const isExpanded = expanded[epic.id] ?? false;
|
const isExpanded = expanded[epic.id] ?? false;
|
||||||
const isSelected = selectedEpicId === epic.id;
|
const isSelected = selectedEpicId === epic.id;
|
||||||
const laneColor = blockedCount > 0 ? 'var(--ui-accent-blocked)' : activeCount > 0 ? 'var(--ui-accent-warning)' : 'var(--ui-accent-ready)';
|
const laneColor = blockedCount > 0 ? 'var(--accent-danger)' : activeCount > 0 ? 'var(--accent-warning)' : 'var(--accent-success)';
|
||||||
const rowBackground = rowTone(entry);
|
const rowBackground = rowTone(entry);
|
||||||
|
|
||||||
if (matchedChildren.length === 0 && hasActiveFilters && !isSelected) {
|
if (matchedChildren.length === 0 && hasActiveFilters && !isSelected) {
|
||||||
|
|
@ -330,10 +330,10 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
<div key={epic.id} className="mb-2">
|
<div key={epic.id} className="mb-2">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-xl px-3 py-3 transition-colors border border-[var(--ui-border-soft)]',
|
'rounded-xl px-3 py-3 transition-colors border border-[var(--border-subtle)]',
|
||||||
isSelected
|
isSelected
|
||||||
? 'text-[var(--ui-text-primary)] ring-1 ring-[var(--ui-accent-info)]/30'
|
? 'text-[var(--text-primary)] ring-1 ring-[var(--accent-info)]/30'
|
||||||
: 'text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)]',
|
: 'text-[var(--text-tertiary)] hover:text-[var(--text-primary)]',
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
borderLeft: `3px solid ${laneColor}`,
|
borderLeft: `3px solid ${laneColor}`,
|
||||||
|
|
@ -344,7 +344,7 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setExpanded((current) => ({ ...current, [epic.id]: !isExpanded }))}
|
onClick={() => setExpanded((current) => ({ ...current, [epic.id]: !isExpanded }))}
|
||||||
className="mt-0.5 inline-flex h-4 w-4 items-center justify-center rounded text-[var(--ui-text-muted)] transition-colors hover:bg-white/5 hover:text-[var(--ui-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent-info)]"
|
className="mt-0.5 inline-flex h-4 w-4 items-center justify-center rounded text-[var(--text-tertiary)] transition-colors hover:bg-[var(--alpha-white-low)] hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]"
|
||||||
aria-label={isExpanded ? `Collapse ${epic.title}` : `Expand ${epic.title}`}
|
aria-label={isExpanded ? `Collapse ${epic.title}` : `Expand ${epic.title}`}
|
||||||
aria-expanded={isExpanded}
|
aria-expanded={isExpanded}
|
||||||
>
|
>
|
||||||
|
|
@ -357,14 +357,14 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 items-center gap-1.5">
|
<div className="flex min-w-0 items-center gap-1.5">
|
||||||
{isExpanded ? <FolderOpen className="h-3.5 w-3.5 flex-shrink-0" aria-hidden="true" /> : <Folder className="h-3.5 w-3.5 flex-shrink-0" aria-hidden="true" />}
|
{isExpanded ? <FolderOpen className="h-3.5 w-3.5 flex-shrink-0" aria-hidden="true" /> : <Folder className="h-3.5 w-3.5 flex-shrink-0" aria-hidden="true" />}
|
||||||
<p className="truncate text-[15px] font-semibold leading-tight text-[var(--ui-text-primary)]">{epic.title}</p>
|
<p className="truncate text-[15px] font-semibold leading-tight text-[var(--text-primary)]">{epic.title}</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 truncate font-mono text-[11px] text-[var(--ui-text-muted)]">{epic.id}</p>
|
<p className="mt-0.5 truncate font-mono text-[11px] text-[var(--text-tertiary)]">{epic.id}</p>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onEpicSelect?.(epic.id)}
|
onClick={() => onEpicSelect?.(epic.id)}
|
||||||
className="inline-flex h-5 w-5 items-center justify-center rounded bg-[var(--ui-bg-card)] text-[var(--ui-text-muted)] border border-[var(--ui-border-soft)] transition-colors hover:text-[var(--ui-text-primary)]"
|
className="inline-flex h-5 w-5 items-center justify-center rounded bg-[var(--surface-quaternary)] text-[var(--text-tertiary)] border border-[var(--border-subtle)] transition-colors hover:text-[var(--text-primary)]"
|
||||||
aria-label={`Focus ${epic.title}`}
|
aria-label={`Focus ${epic.title}`}
|
||||||
>
|
>
|
||||||
<Star className="h-3 w-3" aria-hidden="true" />
|
<Star className="h-3 w-3" aria-hidden="true" />
|
||||||
|
|
@ -372,29 +372,29 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 text-[11px]">
|
<div className="flex items-center gap-3 text-[11px]">
|
||||||
<p><span className="text-[var(--ui-text-primary)]">{total}</span> tasks</p>
|
<p><span className="text-[var(--text-primary)]">{total}</span> tasks</p>
|
||||||
<p><span className="text-[var(--ui-accent-warning)]">{activeCount}</span> active</p>
|
<p><span className="text-[var(--accent-warning)]">{activeCount}</span> active</p>
|
||||||
<p><span className="text-[var(--ui-accent-blocked)]">{agentBlockedCount}</span> ag-blocked</p>
|
<p><span className="text-[var(--accent-danger)]">{agentBlockedCount}</span> ag-blocked</p>
|
||||||
<p className="ml-auto text-[var(--ui-text-muted)]">{formatRelative(latestTimestamp)}</p>
|
<p className="ml-auto text-[var(--text-tertiary)]">{formatRelative(latestTimestamp)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<div className="h-1.5 overflow-hidden rounded-full bg-[#0a111a]">
|
<div className="h-1.5 overflow-hidden rounded-full bg-[#0a111a]">
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<div style={{ width: `${readyPercent}%`, background: 'var(--ui-accent-ready)' }} />
|
<div style={{ width: `${readyPercent}%`, background: 'var(--accent-success)' }} />
|
||||||
<div style={{ width: `${activePercent}%`, background: 'var(--ui-accent-warning)' }} />
|
<div style={{ width: `${activePercent}%`, background: 'var(--accent-warning)' }} />
|
||||||
<div style={{ width: `${blockedPercent}%`, background: 'var(--ui-accent-blocked)' }} />
|
<div style={{ width: `${blockedPercent}%`, background: 'var(--accent-danger)' }} />
|
||||||
<div style={{ width: `${Math.max(0, 100 - readyPercent - activePercent - blockedPercent)}%`, background: 'var(--ui-text-muted)' }} />
|
<div style={{ width: `${Math.max(0, 100 - readyPercent - activePercent - blockedPercent)}%`, background: 'var(--text-tertiary)' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 flex items-center justify-between text-[10px] text-[var(--ui-text-muted)]">
|
<div className="mt-1 flex items-center justify-between text-[10px] text-[var(--text-tertiary)]">
|
||||||
<span>{donePercent}% done</span>
|
<span>{donePercent}% done</span>
|
||||||
<span><span className="text-[var(--ui-accent-ready)]">{readyCount}</span> ready</span>
|
<span><span className="text-[var(--accent-success)]">{readyCount}</span> ready</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{deferredCount + doneCount + blockedCount > 0 ? (
|
{deferredCount + doneCount + blockedCount > 0 ? (
|
||||||
<div className="mt-1.5 flex flex-wrap items-center gap-2 text-[10px] text-[var(--ui-text-muted)]">
|
<div className="mt-1.5 flex flex-wrap items-center gap-2 text-[10px] text-[var(--text-tertiary)]">
|
||||||
{blockedCount > 0 ? <span>{blockedCount} blocked</span> : null}
|
{blockedCount > 0 ? <span>{blockedCount} blocked</span> : null}
|
||||||
{deferredCount > 0 ? <span>{deferredCount} deferred</span> : null}
|
{deferredCount > 0 ? <span>{deferredCount} deferred</span> : null}
|
||||||
{doneCount > 0 ? <span>{doneCount} done</span> : null}
|
{doneCount > 0 ? <span>{doneCount} done</span> : null}
|
||||||
|
|
@ -409,16 +409,16 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
key={task.id}
|
key={task.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onEpicSelect?.(epic.id)}
|
onClick={() => onEpicSelect?.(epic.id)}
|
||||||
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-[var(--ui-text-muted)] transition-colors hover:bg-[#112133] hover:text-[var(--ui-text-primary)]"
|
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-[var(--text-tertiary)] transition-colors hover:bg-[var(--surface-tertiary)] hover:text-[var(--text-primary)]"
|
||||||
>
|
>
|
||||||
<span className={cn('h-1.5 w-1.5 rounded-full flex-shrink-0', statusDot(task.status))} />
|
<span className={cn('h-1.5 w-1.5 rounded-full flex-shrink-0', statusDot(task.status))} />
|
||||||
<span className="min-w-0 flex-1 truncate">{task.title}</span>
|
<span className="min-w-0 flex-1 truncate">{task.title}</span>
|
||||||
{task.assignee ? (
|
{task.assignee ? (
|
||||||
<span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[8px] font-bold uppercase bg-white/10 text-[var(--ui-text-primary)]">
|
<span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[8px] font-bold uppercase bg-[var(--alpha-white-low)] text-[var(--text-primary)]">
|
||||||
{task.assignee.slice(0, 2)}
|
{task.assignee.slice(0, 2)}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="font-mono text-[10px] text-[var(--ui-text-muted)] flex-shrink-0">{task.id}</span>
|
<span className="font-mono text-[10px] text-[var(--text-tertiary)] flex-shrink-0">{task.id}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -428,12 +428,12 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, filters, onFil
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer className="border-t border-[var(--ui-border-soft)] px-4 py-3">
|
<footer className="border-t border-[var(--border-subtle)] px-4 py-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-8 w-8 rounded-full bg-[linear-gradient(135deg,#9cb6bf,#f1dcc6)]" />
|
<div className="h-8 w-8 rounded-full bg-[linear-gradient(135deg,#9cb6bf,#f1dcc6)]" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-semibold text-[var(--ui-text-primary)]">Alex Chen</p>
|
<p className="text-sm font-semibold text-[var(--text-primary)]">Alex Chen</p>
|
||||||
<p className="text-xs text-[var(--ui-text-muted)]">Lead Ops</p>
|
<p className="text-xs text-[var(--text-tertiary)]">Lead Ops</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export function RightPanel({ children, rail, isOpen: externalIsOpen }: RightPane
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex overflow-hidden h-full bg-[var(--ui-bg-panel)] border-l border-[var(--ui-border-strong)]"
|
className="flex overflow-hidden h-full bg-[var(--surface-tertiary)] border-l border-[var(--border-strong)]"
|
||||||
style={{
|
style={{
|
||||||
boxShadow: isOpen ? '-8px 0 20px -12px rgba(0,0,0,0.4)' : 'none',
|
boxShadow: isOpen ? '-8px 0 20px -12px rgba(0,0,0,0.4)' : 'none',
|
||||||
}}
|
}}
|
||||||
|
|
@ -30,12 +30,12 @@ export function RightPanel({ children, rail, isOpen: externalIsOpen }: RightPane
|
||||||
<>
|
<>
|
||||||
{/* Main Content (Chat or Activity) */}
|
{/* Main Content (Chat or Activity) */}
|
||||||
<div className="flex-1 min-w-0 h-full overflow-hidden flex flex-col">
|
<div className="flex-1 min-w-0 h-full overflow-hidden flex flex-col">
|
||||||
<div className="border-l border-[var(--ui-border-soft)] bg-[var(--ui-bg-panel)]">
|
<div className="border-l border-[var(--border-subtle)] bg-[var(--surface-tertiary)]">
|
||||||
<div className="px-3 py-2 border-b border-[var(--ui-border-soft)]">
|
<div className="px-3 py-2 border-b border-[var(--border-subtle)]">
|
||||||
<p className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--ui-text-muted)]">Agent Pool Monitor</p>
|
<p className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--text-tertiary)]">Agent Pool Monitor</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto custom-scrollbar p-0 bg-[var(--ui-bg-shell)]">
|
<div className="flex-1 overflow-y-auto custom-scrollbar p-0 bg-[var(--surface-secondary)]">
|
||||||
{/* Remove default padding to allow edge-to-edge chat */}
|
{/* Remove default padding to allow edge-to-edge chat */}
|
||||||
{children || <span>Right Panel</span>}
|
{children || <span>Right Panel</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -46,8 +46,8 @@ export function RightPanel({ children, rail, isOpen: externalIsOpen }: RightPane
|
||||||
<div
|
<div
|
||||||
className="h-full w-10 flex-shrink-0 shadow-[-10px_0_20px_-18px_rgba(0,0,0,0.9)]"
|
className="h-full w-10 flex-shrink-0 shadow-[-10px_0_20px_-18px_rgba(0,0,0,0.9)]"
|
||||||
style={{
|
style={{
|
||||||
background: 'var(--ui-bg-shell)',
|
background: 'var(--surface-secondary)',
|
||||||
borderLeft: '1px solid var(--ui-border-soft)',
|
borderLeft: '1px solid var(--border-subtle)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{rail}
|
{rail}
|
||||||
|
|
@ -76,7 +76,7 @@ export function RightPanel({ children, rail, isOpen: externalIsOpen }: RightPane
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50"
|
className="fixed inset-0 z-50"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'var(--ui-bg-panel)',
|
backgroundColor: 'var(--surface-tertiary)',
|
||||||
paddingTop: 'env(safe-area-inset-top)',
|
paddingTop: 'env(safe-area-inset-top)',
|
||||||
paddingBottom: 'env(safe-area-inset-bottom)',
|
paddingBottom: 'env(safe-area-inset-bottom)',
|
||||||
overscrollBehavior: 'contain',
|
overscrollBehavior: 'contain',
|
||||||
|
|
@ -88,8 +88,8 @@ export function RightPanel({ children, rail, isOpen: externalIsOpen }: RightPane
|
||||||
<div className="flex justify-end px-4 py-3">
|
<div className="flex justify-end px-4 py-3">
|
||||||
<button
|
<button
|
||||||
onClick={handleCloseClick}
|
onClick={handleCloseClick}
|
||||||
className="p-2 rounded-md hover:bg-white/10"
|
className="p-2 rounded-md hover:bg-[var(--alpha-white-low)]"
|
||||||
style={{ color: 'var(--ui-text-muted)' }}
|
style={{ color: 'var(--text-tertiary)' }}
|
||||||
data-testid="right-panel-close"
|
data-testid="right-panel-close"
|
||||||
aria-label="Close panel"
|
aria-label="Close panel"
|
||||||
>
|
>
|
||||||
|
|
@ -100,7 +100,7 @@ export function RightPanel({ children, rail, isOpen: externalIsOpen }: RightPane
|
||||||
className="overflow-y-auto px-4 pb-4"
|
className="overflow-y-auto px-4 pb-4"
|
||||||
style={{
|
style={{
|
||||||
height: 'calc(100% - 4rem)',
|
height: 'calc(100% - 4rem)',
|
||||||
color: 'var(--ui-text-primary)',
|
color: 'var(--text-primary)',
|
||||||
overscrollBehavior: 'contain',
|
overscrollBehavior: 'contain',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,17 @@ interface MetricTileProps {
|
||||||
function MetricTile({ label, value, accent = 'info' }: MetricTileProps) {
|
function MetricTile({ label, value, accent = 'info' }: MetricTileProps) {
|
||||||
const accentColor =
|
const accentColor =
|
||||||
accent === 'ready'
|
accent === 'ready'
|
||||||
? 'var(--ui-accent-ready)'
|
? 'var(--accent-success)'
|
||||||
: accent === 'blocked'
|
: accent === 'blocked'
|
||||||
? 'var(--ui-accent-blocked)'
|
? 'var(--accent-danger)'
|
||||||
: accent === 'warning'
|
: accent === 'warning'
|
||||||
? 'var(--ui-accent-warning)'
|
? 'var(--accent-warning)'
|
||||||
: 'var(--ui-accent-info)';
|
: 'var(--accent-info)';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden items-center gap-2 rounded-md border border-[var(--ui-border-soft)] bg-[var(--ui-bg-panel)] px-2.5 py-1 text-xs md:inline-flex">
|
<div className="hidden items-center gap-2 rounded-md border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] px-2.5 py-1 text-xs md:inline-flex">
|
||||||
<p className="font-mono text-[10px] uppercase tracking-[0.13em] text-[var(--ui-text-muted)]">{label}</p>
|
<p className="font-mono text-[10px] uppercase tracking-[0.13em] text-[var(--text-tertiary)]">{label}</p>
|
||||||
<p className="font-mono text-sm leading-none text-[var(--ui-text-primary)]">{value}</p>
|
<p className="font-mono text-sm leading-none text-[var(--text-primary)]">{value}</p>
|
||||||
<span className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: accentColor }} />
|
<span className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: accentColor }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -55,12 +55,12 @@ export function TopBar({
|
||||||
const { isDesktop } = useResponsive();
|
const { isDesktop } = useResponsive();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex h-[var(--topbar-height)] items-center justify-between border-b border-[var(--ui-border-strong)] bg-[var(--ui-bg-header)]" data-testid="top-bar">
|
<header className="flex h-[var(--topbar-height)] items-center justify-between border-b border-[var(--border-strong)] bg-[var(--surface-elevated)]" data-testid="top-bar">
|
||||||
<div className="flex min-w-0 items-center">
|
<div className="flex min-w-0 items-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleLeftPanel}
|
onClick={toggleLeftPanel}
|
||||||
className="ml-3 mr-2 inline-flex h-8 w-8 items-center justify-center rounded-md text-[var(--ui-text-muted)] transition-colors hover:bg-white/5 hover:text-[var(--ui-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent-info)]"
|
className="ml-3 mr-2 inline-flex h-8 w-8 items-center justify-center rounded-md text-[var(--text-tertiary)] transition-colors hover:bg-[var(--alpha-white-low)] hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]"
|
||||||
aria-label={leftPanel === 'open' ? 'Collapse Sidebar' : 'Expand Sidebar'}
|
aria-label={leftPanel === 'open' ? 'Collapse Sidebar' : 'Expand Sidebar'}
|
||||||
aria-pressed={leftPanel === 'open'}
|
aria-pressed={leftPanel === 'open'}
|
||||||
data-testid="hamburger-button"
|
data-testid="hamburger-button"
|
||||||
|
|
@ -68,13 +68,13 @@ export function TopBar({
|
||||||
{leftPanel === 'open' ? <SidebarClose className="h-4 w-4" aria-hidden="true" /> : <Sidebar className="h-4 w-4" aria-hidden="true" />}
|
{leftPanel === 'open' ? <SidebarClose className="h-4 w-4" aria-hidden="true" /> : <Sidebar className="h-4 w-4" aria-hidden="true" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="mr-3 flex min-w-[210px] items-center gap-2 border-r border-[var(--ui-border-soft)] px-2 py-2">
|
<div className="mr-3 flex min-w-[210px] items-center gap-2 border-r border-[var(--border-subtle)] px-2 py-2">
|
||||||
<div className="flex h-9 w-9 items-center justify-center rounded-md bg-[var(--ui-bg-card)] text-[var(--ui-accent-ready)]">
|
<div className="flex h-9 w-9 items-center justify-center rounded-md bg-[var(--surface-quaternary)] text-[var(--accent-success)]">
|
||||||
<LayoutGrid className="h-5 w-5" aria-hidden="true" />
|
<LayoutGrid className="h-5 w-5" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-semibold uppercase tracking-[0.04em] text-[var(--ui-text-primary)]">Command Grid</p>
|
<p className="text-sm font-semibold uppercase tracking-[0.04em] text-[var(--text-primary)]">Command Grid</p>
|
||||||
<p className="font-mono text-[10px] text-[var(--ui-text-muted)]">v2.4.0-stable</p>
|
<p className="font-mono text-[10px] text-[var(--text-tertiary)]">v2.4.0-stable</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -93,21 +93,21 @@ export function TopBar({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleBlockedOnly}
|
onClick={toggleBlockedOnly}
|
||||||
aria-pressed={blockedOnly}
|
aria-pressed={blockedOnly}
|
||||||
className="inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-xs font-semibold uppercase tracking-[0.11em] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent-info)]"
|
className="inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-xs font-semibold uppercase tracking-[0.11em] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]"
|
||||||
style={{
|
style={{
|
||||||
borderColor: blockedOnly
|
borderColor: blockedOnly
|
||||||
? 'rgba(255, 76, 114, 0.6)'
|
? 'var(--accent-danger)'
|
||||||
: 'var(--ui-border-soft)',
|
: 'var(--border-default)',
|
||||||
backgroundColor: blockedOnly
|
backgroundColor: blockedOnly
|
||||||
? 'rgba(255, 76, 114, 0.15)'
|
? 'var(--status-blocked)'
|
||||||
: 'var(--ui-bg-panel)',
|
: 'var(--surface-tertiary)',
|
||||||
color: blockedOnly ? '#ffd4dd' : 'var(--ui-text-primary)',
|
color: blockedOnly ? '#ffd4dd' : 'var(--text-primary)',
|
||||||
}}
|
}}
|
||||||
data-testid="blocked-items-button"
|
data-testid="blocked-items-button"
|
||||||
>
|
>
|
||||||
<Lock className="h-3.5 w-3.5" aria-hidden="true" />
|
<Lock className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
Blocked Items
|
Blocked Items
|
||||||
<span className="rounded-full bg-[color-mix(in_srgb,var(--ui-accent-blocked)_84%,black)] px-1.5 py-0.5 font-mono text-[10px] text-[#fff0f3]">
|
<span className="rounded-full bg-[var(--accent-danger)] px-1.5 py-0.5 font-mono text-[10px] text-[var(--text-inverse)]">
|
||||||
{criticalAlerts}
|
{criticalAlerts}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -118,7 +118,7 @@ export function TopBar({
|
||||||
void onCreateTask?.();
|
void onCreateTask?.();
|
||||||
}}
|
}}
|
||||||
disabled={isCreatingTask}
|
disabled={isCreatingTask}
|
||||||
className="inline-flex items-center gap-2 rounded-xl border border-[var(--ui-accent-ready)] bg-[var(--ui-accent-ready)] px-4 py-2 text-xs font-semibold uppercase tracking-[0.11em] text-[#072514] transition-colors hover:brightness-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent-info)] disabled:opacity-60"
|
className="inline-flex items-center gap-2 rounded-xl border border-[var(--accent-success)] bg-[var(--accent-success)] px-4 py-2 text-xs font-semibold uppercase tracking-[0.11em] text-[var(--text-inverse)] transition-colors hover:brightness-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)] disabled:opacity-60"
|
||||||
data-testid="new-task-button"
|
data-testid="new-task-button"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" aria-hidden="true" />
|
<Plus className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
|
|
@ -131,7 +131,7 @@ export function TopBar({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleRightPanel}
|
onClick={toggleRightPanel}
|
||||||
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-[var(--ui-text-muted)] transition-colors hover:bg-white/5 hover:text-[var(--ui-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent-info)]"
|
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-[var(--text-tertiary)] transition-colors hover:bg-[var(--alpha-white-low)] hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]"
|
||||||
aria-label={rightPanel === 'open' ? 'Collapse Right Sidebar' : 'Expand Right Sidebar'}
|
aria-label={rightPanel === 'open' ? 'Collapse Right Sidebar' : 'Expand Right Sidebar'}
|
||||||
aria-pressed={rightPanel === 'open'}
|
aria-pressed={rightPanel === 'open'}
|
||||||
data-testid="settings-button"
|
data-testid="settings-button"
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ export function UnifiedShell({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen bg-[var(--ui-bg-app)]" data-testid="unified-shell">
|
<div className="flex flex-col h-screen bg-[var(--surface-backdrop)]" data-testid="unified-shell">
|
||||||
{/* TOP BAR: 3rem fixed */}
|
{/* TOP BAR: 3rem fixed */}
|
||||||
<TopBar />
|
<TopBar />
|
||||||
|
|
||||||
|
|
@ -182,7 +182,7 @@ export function UnifiedShell({
|
||||||
<ResizeHandle direction="left" onResize={handleLeftResize} />
|
<ResizeHandle direction="left" onResize={handleLeftResize} />
|
||||||
|
|
||||||
{/* MIDDLE CONTENT: flex-1 */}
|
{/* MIDDLE CONTENT: flex-1 */}
|
||||||
<div className="flex-1 relative overflow-hidden bg-[var(--ui-bg-main)]" data-testid="middle-content">
|
<div className="flex-1 relative overflow-hidden bg-[var(--surface-secondary)]" data-testid="middle-content">
|
||||||
{renderMiddleContent()}
|
{renderMiddleContent()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
113
src/styles/themes/aurora.css
Normal file
113
src/styles/themes/aurora.css
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
/**
|
||||||
|
* Aurora Theme - Warm Charcoal with Cyan Accents
|
||||||
|
*
|
||||||
|
* A dark theme with warm brown-grey charcoal backgrounds
|
||||||
|
* and bright cyan/teal accent colors for the "aurora" glow effect.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-theme="aurora"] {
|
||||||
|
/* ==========================================================================
|
||||||
|
1. SURFACE LAYERS - Warm Charcoal Scale
|
||||||
|
Lightest to darkest: quaternary → tertiary → secondary → primary → elevated → backdrop
|
||||||
|
========================================================================== */
|
||||||
|
--surface-backdrop: #181716;
|
||||||
|
--surface-elevated: #131211;
|
||||||
|
--surface-primary: #1f1e1d;
|
||||||
|
--surface-secondary: #242322;
|
||||||
|
--surface-tertiary: #282725;
|
||||||
|
--surface-quaternary: #302e2c;
|
||||||
|
--surface-overlay: #0d0c0b;
|
||||||
|
--surface-input: #1a1917;
|
||||||
|
--surface-hover: rgba(240, 238, 234, 0.05);
|
||||||
|
--surface-active: rgba(53, 201, 255, 0.1);
|
||||||
|
--surface-tooltip: #1f1e1d;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
2. BORDERS - Warm Grey
|
||||||
|
========================================================================== */
|
||||||
|
--border-subtle: rgba(180, 175, 165, 0.12);
|
||||||
|
--border-default: rgba(180, 175, 165, 0.22);
|
||||||
|
--border-strong: rgba(180, 175, 165, 0.38);
|
||||||
|
--border-accent: rgba(53, 201, 255, 0.5);
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
3. TEXT - Warm White Scale
|
||||||
|
========================================================================== */
|
||||||
|
--text-primary: #f0eeea;
|
||||||
|
--text-secondary: #c9c5bc;
|
||||||
|
--text-tertiary: #a8a49a;
|
||||||
|
--text-disabled: #6b6862;
|
||||||
|
--text-inverse: #131211;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
4. ACCENTS - Aurora Colors (Cyan/Teal/Green/Amber/Red)
|
||||||
|
========================================================================== */
|
||||||
|
--accent-info: #35c9ff;
|
||||||
|
--accent-success: #35d98f;
|
||||||
|
--accent-warning: #ffb24a;
|
||||||
|
--accent-danger: #ff4c72;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
5. ACCENT GLOWS - Soft Aurora Glow Effects
|
||||||
|
========================================================================== */
|
||||||
|
--glow-info: 0 0 20px rgba(53, 201, 255, 0.25);
|
||||||
|
--glow-success: 0 0 20px rgba(53, 217, 143, 0.25);
|
||||||
|
--glow-warning: 0 0 20px rgba(255, 178, 74, 0.25);
|
||||||
|
--glow-danger: 0 0 20px rgba(255, 76, 114, 0.25);
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
6. GRAPH COLORS
|
||||||
|
========================================================================== */
|
||||||
|
--graph-node-default: rgba(48, 46, 44, 0.9);
|
||||||
|
--graph-node-epic: rgba(53, 201, 255, 0.12);
|
||||||
|
--graph-edge-default: rgba(180, 175, 165, 0.25);
|
||||||
|
--graph-edge-selected: #35c9ff;
|
||||||
|
--graph-edge-cycle: #ffb24a;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
7. SEMANTIC ALPHAS - For overlays/hover states
|
||||||
|
========================================================================== */
|
||||||
|
--alpha-white-low: rgba(240, 238, 234, 0.05);
|
||||||
|
--alpha-white-medium: rgba(240, 238, 234, 0.1);
|
||||||
|
--alpha-white-high: rgba(240, 238, 234, 0.2);
|
||||||
|
--alpha-black-low: rgba(0, 0, 0, 0.2);
|
||||||
|
--alpha-black-medium: rgba(0, 0, 0, 0.4);
|
||||||
|
--alpha-black-high: rgba(0, 0, 0, 0.7);
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
8. STATUS COLORS - RGBA tints for status backgrounds
|
||||||
|
========================================================================== */
|
||||||
|
--status-ready: rgba(53, 217, 143, 0.15);
|
||||||
|
--status-in-progress: rgba(255, 178, 74, 0.15);
|
||||||
|
--status-blocked: rgba(255, 76, 114, 0.15);
|
||||||
|
--status-closed: rgba(168, 164, 154, 0.1);
|
||||||
|
--status-deferred: rgba(168, 164, 154, 0.08);
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
9. SHADOWS
|
||||||
|
========================================================================== */
|
||||||
|
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
10. AGENT ROLE COLORS
|
||||||
|
========================================================================== */
|
||||||
|
--agent-role-ui: #6B9BD2;
|
||||||
|
--agent-role-graph: #7CB97A;
|
||||||
|
--agent-role-orchestrator: #B08ED6;
|
||||||
|
--agent-role-researcher: #D4A574;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
11. SCROLLBARS
|
||||||
|
========================================================================== */
|
||||||
|
--scrollbar-track: rgba(240, 238, 234, 0.02);
|
||||||
|
--scrollbar-thumb: rgba(180, 175, 165, 0.2);
|
||||||
|
--scrollbar-thumb-hover: rgba(180, 175, 165, 0.35);
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
12. CODE/SYNTAX
|
||||||
|
========================================================================== */
|
||||||
|
--code-background: #1a1917;
|
||||||
|
--code-text: #c9c5bc;
|
||||||
|
}
|
||||||
24
src/styles/themes/index.css
Normal file
24
src/styles/themes/index.css
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Theme System Entry Point
|
||||||
|
*
|
||||||
|
* Import this file in your main CSS to enable the theme system.
|
||||||
|
* The data-theme attribute on <html> controls which theme is active.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* <html data-theme="aurora">
|
||||||
|
* <html data-theme="midnight"> (future)
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import './tokens.css';
|
||||||
|
@import './aurora.css';
|
||||||
|
|
||||||
|
/* Default theme fallback */
|
||||||
|
html:not([data-theme]) {
|
||||||
|
/* Inherits aurora as default since it's imported above */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Future themes would be added here:
|
||||||
|
* @import './midnight.css';
|
||||||
|
* @import './forest.css';
|
||||||
|
*/
|
||||||
117
src/styles/themes/tokens.css
Normal file
117
src/styles/themes/tokens.css
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* Theme Tokens - Variable Names Only
|
||||||
|
*
|
||||||
|
* This file defines all CSS custom property names.
|
||||||
|
* Values are assigned in theme-specific files (aurora.css, etc.)
|
||||||
|
*
|
||||||
|
* Usage in components:
|
||||||
|
* bg-[var(--surface-primary)]
|
||||||
|
* text-[var(--text-secondary)]
|
||||||
|
* border-[var(--border-default)]
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* ==========================================================================
|
||||||
|
1. SURFACE LAYERS (Backgrounds)
|
||||||
|
========================================================================== */
|
||||||
|
--surface-backdrop: /* Page/app background */
|
||||||
|
--surface-elevated: /* Header - visually sits on top */
|
||||||
|
--surface-primary: /* Left sidebar */
|
||||||
|
--surface-secondary: /* Main content area */
|
||||||
|
--surface-tertiary: /* Panels within sidebars */
|
||||||
|
--surface-quaternary: /* Cards */
|
||||||
|
--surface-overlay: /* Modals, dropdowns, drawers */
|
||||||
|
--surface-input: /* Form fields, inputs */
|
||||||
|
--surface-hover: /* Hover states */
|
||||||
|
--surface-active: /* Active/selected states */
|
||||||
|
--surface-tooltip: /* Tooltips, popovers */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
2. BORDERS
|
||||||
|
========================================================================== */
|
||||||
|
--border-subtle: /* Dividers between sections */
|
||||||
|
--border-default: /* Card borders */
|
||||||
|
--border-strong: /* Focus rings, selected states */
|
||||||
|
--border-accent: /* Colored status borders */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
3. TEXT
|
||||||
|
========================================================================== */
|
||||||
|
--text-primary: /* Headlines, important text */
|
||||||
|
--text-secondary: /* Body text */
|
||||||
|
--text-tertiary: /* Muted, hints */
|
||||||
|
--text-disabled: /* Disabled elements */
|
||||||
|
--text-inverse: /* Text on colored backgrounds */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
4. ACCENTS (Functional)
|
||||||
|
========================================================================== */
|
||||||
|
--accent-info: /* Cyan - links, actions */
|
||||||
|
--accent-success: /* Green - ready, done */
|
||||||
|
--accent-warning: /* Amber - in progress */
|
||||||
|
--accent-danger: /* Red - blocked, errors */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
5. ACCENT GLOWS (Aurora Effects)
|
||||||
|
========================================================================== */
|
||||||
|
--glow-info: /* Box-shadow for info glow */
|
||||||
|
--glow-success: /* Box-shadow for success glow */
|
||||||
|
--glow-warning: /* Box-shadow for warning glow */
|
||||||
|
--glow-danger: /* Box-shadow for danger glow */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
6. GRAPH COLORS (ReactFlow)
|
||||||
|
========================================================================== */
|
||||||
|
--graph-node-default: /* Default node background */
|
||||||
|
--graph-node-epic: /* Epic node accent tint */
|
||||||
|
--graph-edge-default: /* Default edge color */
|
||||||
|
--graph-edge-selected: /* Selected edge color */
|
||||||
|
--graph-edge-cycle: /* Cycle warning color */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
7. SEMANTIC ALPHAS (Replacing bg-white/5, bg-black/40)
|
||||||
|
========================================================================== */
|
||||||
|
--alpha-white-low: /* 5% white overlay */
|
||||||
|
--alpha-white-medium: /* 10% white overlay */
|
||||||
|
--alpha-white-high: /* 20% white overlay */
|
||||||
|
--alpha-black-low: /* 10% black overlay */
|
||||||
|
--alpha-black-medium: /* 40% black overlay */
|
||||||
|
--alpha-black-high: /* 80% black overlay */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
8. STATUS COLORS
|
||||||
|
========================================================================== */
|
||||||
|
--status-ready: /* Ready/open status tint */
|
||||||
|
--status-in-progress: /* In progress status tint */
|
||||||
|
--status-blocked: /* Blocked status tint */
|
||||||
|
--status-closed: /* Closed/done status tint */
|
||||||
|
--status-deferred: /* Deferred status tint */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
9. SHADOWS
|
||||||
|
========================================================================== */
|
||||||
|
--shadow-sm: /* Subtle elevation */
|
||||||
|
--shadow-md: /* Cards */
|
||||||
|
--shadow-lg: /* Modals, drawers */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
10. AGENT ROLE COLORS
|
||||||
|
========================================================================== */
|
||||||
|
--agent-role-ui: /* UI role color */
|
||||||
|
--agent-role-graph: /* Graph role color */
|
||||||
|
--agent-role-orchestrator: /* Orchestrator role */
|
||||||
|
--agent-role-researcher: /* Researcher role */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
11. SCROLLBARS
|
||||||
|
========================================================================== */
|
||||||
|
--scrollbar-track: /* Scrollbar track background */
|
||||||
|
--scrollbar-thumb: /* Scrollbar thumb */
|
||||||
|
--scrollbar-thumb-hover: /* Scrollbar thumb hover state */
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
12. CODE/SYNTAX
|
||||||
|
========================================================================== */
|
||||||
|
--code-background: /* Code block background */
|
||||||
|
--code-text: /* Code text color */
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue