feat: Luminous Depth UI redesign

Complete visual overhaul of the Aryx application with the Luminous Depth
design language — blue-tinted dark surfaces, brand gradient accents,
glass-morphism effects, and refined typography.

Design Foundation:
- New color system with CSS custom properties for all surfaces, borders,
  text, accents, and status colors
- Typography: Outfit (display), DM Sans (body), JetBrains Mono (code)
- Animations: accent-flow, thinking-wave, ambient-glow
- Utility classes: glass-surface, glow-border, brand-gradient-bg/text
- Light theme with cool-tinted whites and blue-tinted shadows

Components Updated:
- UI primitives (TextInput, SelectInput, ToggleSwitch, FormField, etc.)
- AppShell with ambient glow background
- Sidebar with brand gradient selection and accent-flow running bars
- ChatPane with semantic phase tinting and gradient user avatars
- WelcomePane with nebula background and motion entrance animations
- All chat banners (Approval, UserInput, PlanReview, MCP Auth, etc.)
- ActivityPanel and RunTimeline with glass-surface cards
- Settings panels, modals, and editor shells
- TerminalPanel with harmonized ANSI palette
- PatternGraph nodes with glass-surface styling
- MarkdownComposer toolbar

Zero hardcoded zinc/indigo color classes remain in renderer components.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-29 17:10:00 +02:00
co-authored by Copilot
parent 2f1c5bc6d7
commit f8b4c3cf4f
43 changed files with 1214 additions and 969 deletions
+20
View File
@@ -5,7 +5,11 @@
"": { "": {
"name": "kopaya", "name": "kopaya",
"dependencies": { "dependencies": {
"@fontsource-variable/dm-sans": "^5.2.8",
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@fontsource-variable/outfit": "^5.2.8",
"keytar": "^7.9.0", "keytar": "^7.9.0",
"motion": "^12.38.0",
"node-pty": "^1.1.0", "node-pty": "^1.1.0",
}, },
"devDependencies": { "devDependencies": {
@@ -157,6 +161,12 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
"@fontsource-variable/dm-sans": ["@fontsource-variable/dm-sans@5.2.8", "", {}, "sha512-AxkvMTvNWgfrmlyjiV05vlHYJa+nRQCf1EfvIrQAPBpFJW0O9VTz7oAFr9S3lvbWdmnFoBk7yFqQL86u64nl2g=="],
"@fontsource-variable/jetbrains-mono": ["@fontsource-variable/jetbrains-mono@5.2.8", "", {}, "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q=="],
"@fontsource-variable/outfit": ["@fontsource-variable/outfit@5.2.8", "", {}, "sha512-4oUDCZx/Tcz6HZP423w/niqEH31Gks5IsqHV2ZZz1qKHaVIZdj2f0/S1IK2n8jl6Xo0o3N+3RjNHlV9R73ozQA=="],
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
@@ -569,6 +579,8 @@
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
@@ -831,6 +843,12 @@
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
"motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="],
"motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
"motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
@@ -1011,6 +1029,8 @@
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
+4
View File
@@ -68,7 +68,11 @@
"yaml": "^2.8.3" "yaml": "^2.8.3"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/dm-sans": "^5.2.8",
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@fontsource-variable/outfit": "^5.2.8",
"keytar": "^7.9.0", "keytar": "^7.9.0",
"motion": "^12.38.0",
"node-pty": "^1.1.0" "node-pty": "^1.1.0"
} }
} }
+58 -58
View File
@@ -24,12 +24,12 @@ import { ProviderIcon } from './ProviderIcons';
/* ── Mode accent colours ───────────────────────────────────── */ /* ── Mode accent colours ───────────────────────────────────── */
const modeAccent: Record<OrchestrationMode, { dot: string; bar: string; label: string }> = { const modeAccent: Record<OrchestrationMode, { dot: string; bar: string; label: string }> = {
single: { dot: 'bg-indigo-400', bar: 'bg-indigo-500/60', label: 'text-indigo-400' }, single: { dot: 'bg-[#245CF9]', bar: 'bg-[#245CF9] opacity-60', label: 'text-[#245CF9]' },
sequential: { dot: 'bg-amber-400', bar: 'bg-amber-500/60', label: 'text-amber-400' }, sequential: { dot: 'bg-[var(--color-status-warning)]', bar: 'bg-[var(--color-status-warning)] opacity-60', label: 'text-[var(--color-status-warning)]' },
concurrent: { dot: 'bg-emerald-400', bar: 'bg-emerald-500/60', label: 'text-emerald-400' }, concurrent: { dot: 'bg-[var(--color-status-success)]', bar: 'bg-[var(--color-status-success)] opacity-60', label: 'text-[var(--color-status-success)]' },
handoff: { dot: 'bg-sky-400', bar: 'bg-sky-500/60', label: 'text-sky-400' }, handoff: { dot: 'bg-[var(--color-accent-sky)]', bar: 'bg-[var(--color-accent-sky)] opacity-60', label: 'text-[var(--color-accent-sky)]' },
'group-chat': { dot: 'bg-violet-400', bar: 'bg-violet-500/60', label: 'text-violet-400' }, 'group-chat': { dot: 'bg-[var(--color-accent-purple)]', bar: 'bg-[var(--color-accent-purple)] opacity-60', label: 'text-[var(--color-accent-purple)]' },
magentic: { dot: 'bg-zinc-500', bar: 'bg-zinc-600/60', label: 'text-zinc-500' }, magentic: { dot: 'bg-[var(--color-text-muted)]', bar: 'bg-[var(--color-text-muted)] opacity-60', label: 'text-[var(--color-text-muted)]' },
}; };
/* ── Helpers ───────────────────────────────────────────────── */ /* ── Helpers ───────────────────────────────────────────────── */
@@ -62,7 +62,7 @@ const modeLabels: Record<OrchestrationMode, string> = {
function SectionHeader({ children }: { children: ReactNode }) { function SectionHeader({ children }: { children: ReactNode }) {
return ( return (
<h3 className="mb-2 flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-600"> <h3 className="font-display mb-2 flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{children} {children}
</h3> </h3>
); );
@@ -87,7 +87,7 @@ function AgentRow({
const isCompleted = isAgentActivityCompleted(row.activity); const isCompleted = isAgentActivityCompleted(row.activity);
return ( return (
<div className={`relative flex gap-2.5 py-2.5 ${isLast ? '' : 'border-b border-zinc-800/50'}`}> <div className={`relative flex gap-2.5 py-2.5 ${isLast ? '' : 'border-b border-[var(--color-border-subtle)]'}`}>
{/* Left accent bar — visible only when this agent is actively working */} {/* Left accent bar — visible only when this agent is actively working */}
{isActive && ( {isActive && (
<div className={`absolute -left-3 bottom-2 top-2 w-[3px] rounded-full ${accent.bar}`} /> <div className={`absolute -left-3 bottom-2 top-2 w-[3px] rounded-full ${accent.bar}`} />
@@ -96,12 +96,12 @@ function AgentRow({
{/* Status dot */} {/* Status dot */}
<div className="flex shrink-0 pt-0.5"> <div className="flex shrink-0 pt-0.5">
<span <span
className={`size-2 rounded-full ${ className={`size-2 rounded-full transition-all duration-200 ${
isActive isActive
? `animate-pulse ${accent.dot}` ? `animate-pulse ${accent.dot} ring-2 ring-[var(--color-border-glow)]`
: isCompleted : isCompleted
? 'bg-emerald-400' ? 'bg-[var(--color-status-success)]'
: 'bg-zinc-700' : 'bg-[var(--color-surface-3)]'
}`} }`}
/> />
</div> </div>
@@ -109,13 +109,13 @@ function AgentRow({
{/* Content */} {/* Content */}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="truncate text-[12px] font-medium text-zinc-200">{row.agentName}</span> <span className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">{row.agentName}</span>
</div> </div>
{/* Model + effort inline */} {/* Model + effort inline */}
{agent && ( {agent && (
<div className="mt-1 flex flex-wrap items-center gap-1"> <div className="mt-1 flex flex-wrap items-center gap-1">
<span className="inline-flex items-center gap-1 text-[10px] text-zinc-500"> <span className="inline-flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]">
{(() => { {(() => {
const prov = inferProvider(agent.model); const prov = inferProvider(agent.model);
return prov ? <ProviderIcon provider={prov} className="size-2.5" /> : null; return prov ? <ProviderIcon provider={prov} className="size-2.5" /> : null;
@@ -124,8 +124,8 @@ function AgentRow({
</span> </span>
{agent.reasoningEffort && ( {agent.reasoningEffort && (
<> <>
<span className="text-[10px] text-zinc-700">·</span> <span className="text-[10px] text-[var(--color-text-muted)]">·</span>
<span className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500"> <span className="inline-flex items-center gap-0.5 text-[10px] text-[var(--color-text-muted)]">
<Sparkles className="size-2" /> <Sparkles className="size-2" />
{formatEffort(agent.reasoningEffort)} {formatEffort(agent.reasoningEffort)}
</span> </span>
@@ -141,8 +141,8 @@ function AgentRow({
isActive isActive
? accent.label ? accent.label
: isCompleted : isCompleted
? 'text-emerald-400' ? 'text-[var(--color-status-success)]'
: 'text-zinc-600' : 'text-[var(--color-text-muted)]'
}`} }`}
> >
{formatAgentActivityLabel(row.activity)} {formatAgentActivityLabel(row.activity)}
@@ -151,20 +151,20 @@ function AgentRow({
{/* Per-agent usage summary */} {/* Per-agent usage summary */}
{agentUsage && agentUsage.requestCount > 0 && ( {agentUsage && agentUsage.requestCount > 0 && (
<div className="mt-0.5 flex items-center gap-1 text-[10px] text-zinc-600"> <div className="mt-0.5 flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]">
<span className="tabular-nums">{formatTokenCount(agentUsage.inputTokens)} in</span> <span className="font-mono tabular-nums">{formatTokenCount(agentUsage.inputTokens)} in</span>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums">{formatTokenCount(agentUsage.outputTokens)} out</span> <span className="font-mono tabular-nums">{formatTokenCount(agentUsage.outputTokens)} out</span>
{agentUsage.cost > 0 && ( {agentUsage.cost > 0 && (
<> <>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums">{agentUsage.cost.toFixed(2)} cost</span> <span className="font-mono tabular-nums">{agentUsage.cost.toFixed(2)} cost</span>
</> </>
)} )}
{agentUsage.durationMs > 0 && ( {agentUsage.durationMs > 0 && (
<> <>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums">{formatDuration(agentUsage.durationMs)}</span> <span className="font-mono tabular-nums">{formatDuration(agentUsage.durationMs)}</span>
</> </>
)} )}
</div> </div>
@@ -182,15 +182,15 @@ function TurnEventIcon({ kind, phase, success }: { kind: SessionEventKind; phase
const base = 'size-3'; const base = 'size-3';
switch (kind) { switch (kind) {
case 'subagent': case 'subagent':
return <ArrowRight className={`${base} ${success === false ? 'text-red-400' : 'text-sky-400'}`} />; return <ArrowRight className={`${base} ${success === false ? 'text-[var(--color-status-error)]' : 'text-[var(--color-accent-sky)]'}`} />;
case 'hook-lifecycle': case 'hook-lifecycle':
return <Cog className={`${base} ${phase === 'start' ? 'animate-spin text-amber-400' : success === false ? 'text-red-400' : 'text-emerald-400'}`} />; return <Cog className={`${base} ${phase === 'start' ? 'animate-spin text-[var(--color-status-warning)]' : success === false ? 'text-[var(--color-status-error)]' : 'text-[var(--color-status-success)]'}`} />;
case 'skill-invoked': case 'skill-invoked':
return <Sparkles className={`${base} text-violet-400`} />; return <Sparkles className={`${base} text-[var(--color-accent-purple)]`} />;
case 'session-compaction': case 'session-compaction':
return <CheckCircle2 className={`${base} ${phase === 'start' ? 'animate-pulse text-amber-400' : 'text-emerald-400'}`} />; return <CheckCircle2 className={`${base} ${phase === 'start' ? 'animate-pulse text-[var(--color-status-warning)]' : 'text-[var(--color-status-success)]'}`} />;
default: default:
return <Zap className={`${base} text-zinc-500`} />; return <Zap className={`${base} text-[var(--color-text-muted)]`} />;
} }
} }
@@ -238,19 +238,19 @@ export function ActivityPanel({
{/* Header — top padding clears the title bar overlay zone */} {/* Header — top padding clears the title bar overlay zone */}
<div className="drag-region border-b border-[var(--color-border)] px-4 pb-3 pt-3"> <div className="drag-region border-b border-[var(--color-border)] px-4 pb-3 pt-3">
<div className="flex min-h-8 items-center gap-2"> <div className="flex min-h-8 items-center gap-2">
<Activity className="size-4 text-zinc-500" /> <Activity className="size-4 text-[var(--color-text-muted)]" />
<span className="text-[12px] font-semibold uppercase tracking-[0.12em] text-zinc-400"> <span className="font-display text-[12px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-secondary)]">
Activity Activity
</span> </span>
{hasPendingApproval ? ( {hasPendingApproval ? (
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<ShieldAlert className="size-3 text-amber-400" /> <ShieldAlert className="size-3 text-[var(--color-status-warning)]" />
<span className="text-[9px] font-semibold uppercase tracking-wider text-amber-400"> <span className="text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-warning)]">
Approval{totalApprovalCount > 1 ? `s (${totalApprovalCount})` : ''} Approval{totalApprovalCount > 1 ? `s (${totalApprovalCount})` : ''}
</span> </span>
</span> </span>
) : isBusy ? ( ) : isBusy ? (
<span className="size-1.5 animate-pulse rounded-full bg-blue-400" /> <span className="size-1.5 animate-pulse rounded-full bg-[var(--color-status-info)]" />
) : null} ) : null}
</div> </div>
</div> </div>
@@ -261,7 +261,7 @@ export function ActivityPanel({
<SectionHeader> <SectionHeader>
<Users className="size-3" /> <Users className="size-3" />
<span>Agents</span> <span>Agents</span>
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[9px] tabular-nums text-zinc-500"> <span className="font-mono rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
{activityRows.length} {activityRows.length}
</span> </span>
<span className={`ml-auto text-[9px] font-medium normal-case tracking-normal ${accent.label}`}> <span className={`ml-auto text-[9px] font-medium normal-case tracking-normal ${accent.label}`}>
@@ -270,7 +270,7 @@ export function ActivityPanel({
</SectionHeader> </SectionHeader>
{activityRows.length > 0 ? ( {activityRows.length > 0 ? (
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3"> <div className="glass-surface rounded-lg px-3">
{activityRows.map((row, index) => { {activityRows.map((row, index) => {
const agentKey = row.activity?.agentId ?? row.key; const agentKey = row.activity?.agentId ?? row.key;
const agentUsage = sessionRequestUsage?.perAgent[agentKey] const agentUsage = sessionRequestUsage?.perAgent[agentKey]
@@ -288,7 +288,7 @@ export function ActivityPanel({
})} })}
</div> </div>
) : ( ) : (
<p className="py-4 text-center text-[11px] text-zinc-600">No agents configured</p> <p className="py-4 text-center text-[11px] text-[var(--color-text-muted)]">No agents configured</p>
)} )}
</div> </div>
@@ -300,32 +300,32 @@ export function ActivityPanel({
<span>Session Usage</span> <span>Session Usage</span>
</SectionHeader> </SectionHeader>
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2.5"> <div className="glass-surface rounded-lg px-3 py-2.5">
<div className="flex flex-wrap items-center gap-1.5 text-[11px] text-zinc-400"> <div className="flex flex-wrap items-center gap-1.5 text-[11px] text-[var(--color-text-secondary)]">
<span className="font-medium tabular-nums"> <span className="font-mono font-medium tabular-nums">
{sessionRequestUsage.requestCount} premium request{sessionRequestUsage.requestCount === 1 ? '' : 's'} {sessionRequestUsage.requestCount} premium request{sessionRequestUsage.requestCount === 1 ? '' : 's'}
</span> </span>
{sessionRequestUsage.totalNanoAiu > 0 && ( {sessionRequestUsage.totalNanoAiu > 0 && (
<> <>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums">{formatNanoAiu(sessionRequestUsage.totalNanoAiu)} AIU</span> <span className="font-mono tabular-nums">{formatNanoAiu(sessionRequestUsage.totalNanoAiu)} AIU</span>
</> </>
)} )}
</div> </div>
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[10px] text-zinc-500"> <div className="mt-1 flex flex-wrap items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
<span className="tabular-nums">{formatTokenCount(sessionRequestUsage.totalInputTokens)} in</span> <span className="font-mono tabular-nums">{formatTokenCount(sessionRequestUsage.totalInputTokens)} in</span>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums">{formatTokenCount(sessionRequestUsage.totalOutputTokens)} out</span> <span className="font-mono tabular-nums">{formatTokenCount(sessionRequestUsage.totalOutputTokens)} out</span>
{sessionRequestUsage.totalCost > 0 && ( {sessionRequestUsage.totalCost > 0 && (
<> <>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums">{sessionRequestUsage.totalCost.toFixed(2)} cost</span> <span className="font-mono tabular-nums">{sessionRequestUsage.totalCost.toFixed(2)} cost</span>
</> </>
)} )}
{sessionRequestUsage.totalDurationMs > 0 && ( {sessionRequestUsage.totalDurationMs > 0 && (
<> <>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums">{formatDuration(sessionRequestUsage.totalDurationMs)} total</span> <span className="font-mono tabular-nums">{formatDuration(sessionRequestUsage.totalDurationMs)} total</span>
</> </>
)} )}
</div> </div>
@@ -339,7 +339,7 @@ export function ActivityPanel({
<Clock className="size-3" /> <Clock className="size-3" />
<span>Timeline</span> <span>Timeline</span>
{session.runs.length > 0 && ( {session.runs.length > 0 && (
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[9px] tabular-nums text-zinc-500"> <span className="font-mono rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
{session.runs.length} {session.runs.length}
</span> </span>
)} )}
@@ -354,12 +354,12 @@ export function ActivityPanel({
<SectionHeader> <SectionHeader>
<Zap className="size-3" /> <Zap className="size-3" />
<span>Events</span> <span>Events</span>
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[9px] tabular-nums text-zinc-500"> <span className="font-mono rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
{turnEvents.length} {turnEvents.length}
</span> </span>
</SectionHeader> </SectionHeader>
<div className="space-y-0.5 rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2"> <div className="glass-surface space-y-0.5 rounded-lg px-3 py-2">
{turnEvents.slice().reverse().map((entry, index) => ( {turnEvents.slice().reverse().map((entry, index) => (
<div key={index} className="flex items-start gap-2 py-1"> <div key={index} className="flex items-start gap-2 py-1">
<div className="mt-0.5 shrink-0"> <div className="mt-0.5 shrink-0">
@@ -367,13 +367,13 @@ export function ActivityPanel({
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-[11px] font-medium text-zinc-300">{entry.label}</span> <span className="text-[11px] font-medium text-[var(--color-text-secondary)]">{entry.label}</span>
<span className="ml-auto shrink-0 text-[9px] tabular-nums text-zinc-700"> <span className="font-mono ml-auto shrink-0 text-[9px] tabular-nums text-[var(--color-text-muted)]">
{formatTurnEventTimestamp(entry.occurredAt)} {formatTurnEventTimestamp(entry.occurredAt)}
</span> </span>
</div> </div>
{entry.detail && ( {entry.detail && (
<p className="text-[10px] leading-snug text-zinc-600">{entry.detail}</p> <p className="text-[10px] leading-snug text-[var(--color-text-muted)]">{entry.detail}</p>
)} )}
</div> </div>
</div> </div>
+19 -19
View File
@@ -18,9 +18,9 @@ function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) {
} }
const styles = { const styles = {
premium: 'bg-amber-500/10 text-amber-400', premium: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]',
standard: 'bg-zinc-700/50 text-zinc-500', standard: 'bg-[var(--color-surface-3)]/50 text-[var(--color-text-muted)]',
fast: 'bg-emerald-500/10 text-emerald-400', fast: 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]',
}; };
return ( return (
@@ -75,10 +75,10 @@ export function ModelSelect({
return ( return (
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">{label}</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
<div className="relative" ref={containerRef}> <div className="relative" ref={containerRef}>
<button <button
className="flex w-full items-center gap-2 rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-left text-[13px] text-zinc-100 outline-none transition hover:border-zinc-600 focus:border-indigo-500/50 disabled:cursor-not-allowed disabled:opacity-60" className="flex w-full items-center gap-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 text-left text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 hover:border-[var(--color-border)] focus:border-[var(--color-accent)]/50 disabled:cursor-not-allowed disabled:opacity-60"
disabled={disabled} disabled={disabled}
onClick={() => setOpen((current) => !current)} onClick={() => setOpen((current) => !current)}
type="button" type="button"
@@ -86,25 +86,25 @@ export function ModelSelect({
{provider && <ProviderIcon provider={provider} />} {provider && <ProviderIcon provider={provider} />}
<span className="flex-1 truncate">{selected?.name ?? (value || 'Select model')}</span> <span className="flex-1 truncate">{selected?.name ?? (value || 'Select model')}</span>
<ChevronDown <ChevronDown
className={`size-3.5 text-zinc-500 transition ${open ? 'rotate-180' : ''}`} className={`size-3.5 text-[var(--color-text-muted)] transition ${open ? 'rotate-180' : ''}`}
/> />
</button> </button>
{open && !disabled && ( {open && !disabled && (
<div className="absolute z-30 mt-1 max-h-72 w-full overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl"> <div className="absolute z-30 mt-1 max-h-72 w-full overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
{groupedModels.map((providerGroup) => { {groupedModels.map((providerGroup) => {
return ( return (
<div key={providerGroup.id}> <div key={providerGroup.id}>
<div className="flex items-center gap-2 px-3 pb-1 pt-2.5"> <div className="flex items-center gap-2 px-3 pb-1 pt-2.5">
<ProviderIcon provider={providerGroup.id} /> <ProviderIcon provider={providerGroup.id} />
<span className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500"> <span className="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{providerGroup.label} {providerGroup.label}
</span> </span>
</div> </div>
{providerGroup.models.map((model) => ( {providerGroup.models.map((model) => (
<button <button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${ className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300' model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
}`} }`}
key={model.id} key={model.id}
onClick={() => { onClick={() => {
@@ -122,13 +122,13 @@ export function ModelSelect({
})} })}
{otherModels.length > 0 && ( {otherModels.length > 0 && (
<div> <div>
<div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-zinc-500"> <div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Other Other
</div> </div>
{otherModels.map((model) => ( {otherModels.map((model) => (
<button <button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${ className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300' model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
}`} }`}
key={model.id} key={model.id}
onClick={() => { onClick={() => {
@@ -173,15 +173,15 @@ export function ReasoningEffortSelect({
if (supportedEfforts && supportedEfforts.length === 0) { if (supportedEfforts && supportedEfforts.length === 0) {
return ( return (
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">{label}</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
<div className="relative"> <div className="relative">
<input <input
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 pr-9 text-[13px] text-zinc-500 outline-none" className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 pr-9 text-[13px] text-[var(--color-text-muted)] outline-none"
disabled disabled
readOnly readOnly
value="Not supported for this model" value="Not supported for this model"
/> />
<Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-zinc-600" /> <Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-[var(--color-text-muted)]" />
</div> </div>
</label> </label>
); );
@@ -189,10 +189,10 @@ export function ReasoningEffortSelect({
return ( return (
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">{label}</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
<div className="relative"> <div className="relative">
<select <select
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 pr-9 text-[13px] text-zinc-100 outline-none transition focus:border-indigo-500/50 disabled:cursor-not-allowed disabled:opacity-60" className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 pr-9 text-[13px] text-[var(--color-text-primary)] outline-none transition focus:border-[var(--color-accent)]/50 disabled:cursor-not-allowed disabled:opacity-60"
disabled={disabled || !selectedValue} disabled={disabled || !selectedValue}
onChange={(event) => onChange(event.target.value as ReasoningEffort)} onChange={(event) => onChange(event.target.value as ReasoningEffort)}
value={selectedValue} value={selectedValue}
@@ -203,7 +203,7 @@ export function ReasoningEffortSelect({
</option> </option>
))} ))}
</select> </select>
<Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-zinc-500" /> <Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-[var(--color-text-muted)]" />
</div> </div>
</label> </label>
); );
+15 -4
View File
@@ -10,22 +10,33 @@ interface AppShellProps {
export function AppShell({ sidebar, content, detailPanel, terminalPanel, overlay }: AppShellProps) { export function AppShell({ sidebar, content, detailPanel, terminalPanel, overlay }: AppShellProps) {
return ( return (
<div className="relative flex h-screen bg-[var(--color-surface-0)] text-zinc-100"> <div className="relative flex h-screen bg-[var(--color-surface-0)] text-[var(--color-text-primary)]">
{/* Full-width drag region matching the title bar overlay height */} {/* Full-width drag region matching the title bar overlay height */}
<div className="drag-region absolute inset-x-0 top-0 z-10 h-3" /> <div className="drag-region absolute inset-x-0 top-0 z-10 h-3" />
<aside className="flex w-72 shrink-0 flex-col border-r border-[var(--color-border)] bg-[var(--color-surface-1)]"> {/* Sidebar */}
<aside className="flex w-72 shrink-0 flex-col border-r border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]">
{sidebar} {sidebar}
</aside> </aside>
{/* Main content + terminal */}
<main className="relative flex min-w-0 flex-1 flex-col"> <main className="relative flex min-w-0 flex-1 flex-col">
<div className="min-h-0 flex-1">{content}</div> {/* Ambient glow behind active content area */}
<div
className="pointer-events-none absolute inset-0 opacity-30"
style={{ background: 'var(--gradient-glow)' }}
/>
<div className="relative min-h-0 flex-1">{content}</div>
{terminalPanel} {terminalPanel}
</main> </main>
{/* Detail panel */}
{detailPanel && ( {detailPanel && (
<aside className="flex w-64 shrink-0 flex-col border-l border-[var(--color-border)] bg-[var(--color-surface-1)]"> <aside className="flex w-64 shrink-0 flex-col border-l border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]">
{detailPanel} {detailPanel}
</aside> </aside>
)} )}
{overlay} {overlay}
</div> </div>
); );
+60 -60
View File
@@ -250,16 +250,16 @@ export function ChatPane({
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
{/* Header — extra top padding clears the title bar overlay zone */} {/* Header — extra top padding clears the title bar overlay zone */}
<header className="drag-region border-b border-[var(--color-border)] px-6 pb-3 pt-3"> <header className="drag-region border-b border-[var(--color-border-subtle)] px-6 pb-3 pt-3">
<div className="flex min-h-8 items-center justify-between"> <div className="flex min-h-8 items-center justify-between">
<div className="min-w-0"> <div className="min-w-0">
<h2 className="truncate text-[13px] font-semibold leading-tight text-zinc-100">{session.title}</h2> <h2 className="font-display truncate text-[13px] font-semibold leading-tight text-[var(--color-text-primary)]">{session.title}</h2>
<p className="truncate text-[11px] leading-tight text-zinc-500"> <p className="truncate text-[11px] leading-tight text-[var(--color-text-muted)]">
{isScratchpad {isScratchpad
? `Scratchpad · ${pattern.name}` ? `Scratchpad · ${pattern.name}`
: `${project.name} · ${pattern.name} · ${pattern.mode}`} : `${project.name} · ${pattern.name} · ${pattern.mode}`}
{!isScratchpad && project.git?.status === 'ready' && ( {!isScratchpad && project.git?.status === 'ready' && (
<span className="ml-2 inline-flex items-center gap-1 text-zinc-600"> <span className="ml-2 inline-flex items-center gap-1 text-[var(--color-text-muted)]">
<GitBranch className="inline size-2.5" /> <GitBranch className="inline size-2.5" />
{project.git.branch ?? project.git.head?.shortHash ?? 'HEAD'} {project.git.branch ?? project.git.head?.shortHash ?? 'HEAD'}
{project.git.isDirty && ( {project.git.isDirty && (
@@ -273,31 +273,31 @@ export function ChatPane({
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{pendingApproval && ( {pendingApproval && (
<div className="flex items-center gap-1.5 text-[12px] font-medium text-amber-400"> <div className="flex items-center gap-1.5 text-[12px] font-medium text-[var(--color-status-warning)]">
<ShieldAlert className="size-3.5" /> <ShieldAlert className="size-3.5" />
Awaiting approval Awaiting approval
{queuedApprovals.length > 0 && ( {queuedApprovals.length > 0 && (
<span className="rounded-full bg-amber-500/15 px-1.5 py-0.5 text-[10px] tabular-nums"> <span className="rounded-full bg-[var(--color-status-warning)]/15 px-1.5 py-0.5 text-[10px] tabular-nums">
+{queuedApprovals.length} queued +{queuedApprovals.length} queued
</span> </span>
)} )}
</div> </div>
)} )}
{pendingUserInput && !pendingApproval && ( {pendingUserInput && !pendingApproval && (
<div className="flex items-center gap-1.5 text-[12px] font-medium text-blue-400"> <div className="flex items-center gap-1.5 text-[12px] font-medium text-[var(--color-accent-sky)]">
<MessageCircleQuestion className="size-3.5" /> <MessageCircleQuestion className="size-3.5" />
Awaiting your input Awaiting your input
</div> </div>
)} )}
{isSessionBusy && !pendingApproval && !pendingUserInput && <span className="size-2 animate-pulse rounded-full bg-blue-400" />} {isSessionBusy && !pendingApproval && !pendingUserInput && <span className="size-2 animate-pulse rounded-full bg-[var(--color-accent-sky)]" />}
{session.status === 'error' && ( {session.status === 'error' && (
<div className="flex items-center gap-1.5 text-[12px] text-red-400"> <div className="flex items-center gap-1.5 text-[12px] text-[var(--color-status-error)]">
<AlertCircle className="size-3.5" /> <AlertCircle className="size-3.5" />
Error Error
</div> </div>
)} )}
{session.status === 'idle' && !pendingApproval && !pendingUserInput && session.messages.length > 0 && ( {session.status === 'idle' && !pendingApproval && !pendingUserInput && session.messages.length > 0 && (
<span className="text-[12px] text-zinc-600"> <span className="text-[12px] text-[var(--color-text-muted)]">
{session.messages.length} message{session.messages.length === 1 ? '' : 's'} {session.messages.length} message{session.messages.length === 1 ? '' : 's'}
</span> </span>
)} )}
@@ -309,18 +309,18 @@ export function ChatPane({
<div className="flex-1 overflow-y-auto" ref={transcriptRef}> <div className="flex-1 overflow-y-auto" ref={transcriptRef}>
{session.messages.length === 0 ? ( {session.messages.length === 0 ? (
<div className="flex h-full flex-col items-center justify-center gap-2 px-6 text-center"> <div className="flex h-full flex-col items-center justify-center gap-2 px-6 text-center">
<Bot className="size-10 text-zinc-800" /> <Bot className="size-10 text-[var(--color-surface-3)]" />
<p className="text-[13px] text-zinc-500">Send a message to start the conversation</p> <p className="text-[13px] text-[var(--color-text-muted)]">Send a message to start the conversation</p>
<p className="text-[12px] text-zinc-700"> <p className="text-[12px] text-[var(--color-text-muted)]">
{isScratchpad ? ( {isScratchpad ? (
<> <>
Scratchpad is ready for ad-hoc questions using{' '} Scratchpad is ready for ad-hoc questions using{' '}
<span className="text-zinc-500">{pattern.name}</span> <span className="text-[var(--color-text-secondary)]">{pattern.name}</span>
</> </>
) : ( ) : (
<> <>
Using <span className="text-zinc-500">{pattern.name}</span> in{' '} Using <span className="text-[var(--color-text-secondary)]">{pattern.name}</span> in{' '}
<span className="text-zinc-500">{project.name}</span> <span className="text-[var(--color-text-secondary)]">{project.name}</span>
</> </>
)} )}
</p> </p>
@@ -333,14 +333,14 @@ export function ChatPane({
const phase = getAssistantMessagePhase(session, message, index); const phase = getAssistantMessagePhase(session, message, index);
const assistantContainerClass = const assistantContainerClass =
phase === 'thinking' phase === 'thinking'
? 'border-sky-500/20 bg-sky-500/5' ? 'border-[var(--color-accent-sky)]/20 bg-[var(--color-accent-sky)]/5'
: phase === 'final' : phase === 'final'
? 'border-emerald-500/20 bg-emerald-500/5' ? 'border-[var(--color-status-success)]/20 bg-[var(--color-status-success)]/5'
: 'border-zinc-800 bg-zinc-900/40'; : 'border-[var(--color-border)] bg-[var(--color-surface-1)]/40';
const assistantBadgeClass = const assistantBadgeClass =
phase === 'thinking' phase === 'thinking'
? 'border-sky-400/20 bg-sky-400/10 text-sky-300' ? 'border-[var(--color-accent-sky)]/20 bg-[var(--color-accent-sky)]/10 text-[var(--color-accent-sky)]'
: 'border-emerald-400/20 bg-emerald-400/10 text-emerald-300'; : 'border-[var(--color-status-success)]/20 bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]';
const phaseLabel = const phaseLabel =
phase === 'thinking' ? 'Thinking' : phase === 'final' ? 'Final' : undefined; phase === 'thinking' ? 'Thinking' : phase === 'final' ? 'Final' : undefined;
@@ -349,13 +349,13 @@ export function ChatPane({
<div className="flex gap-3"> <div className="flex gap-3">
<div <div
className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full ${ className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full ${
isUser ? 'bg-indigo-600 text-white' : 'bg-zinc-800 text-zinc-400' isUser ? 'brand-gradient-bg text-white' : 'bg-[var(--color-surface-2)] text-[var(--color-text-secondary)]'
}`} }`}
> >
{isUser ? <User className="size-3.5" /> : <Bot className="size-3.5" />} {isUser ? <User className="size-3.5" /> : <Bot className="size-3.5" />}
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="mb-1 flex items-center gap-2 text-[12px] font-medium text-zinc-400"> <div className="mb-1 flex items-center gap-2 text-[12px] font-medium text-[var(--color-text-secondary)]">
<span>{message.authorName}</span> <span>{message.authorName}</span>
{!isUser && phaseLabel && ( {!isUser && phaseLabel && (
<span <span
@@ -368,8 +368,8 @@ export function ChatPane({
<div <div
className={ className={
isUser isUser
? 'text-[14px] leading-relaxed text-zinc-200' ? 'text-[14px] leading-relaxed text-[var(--color-text-primary)]'
: `rounded-xl border px-4 py-3 text-[14px] leading-relaxed text-zinc-200 ${assistantContainerClass}` : `rounded-xl border px-4 py-3 text-[14px] leading-relaxed text-[var(--color-text-primary)] ${assistantContainerClass}`
} }
> >
{/* Attachment thumbnails */} {/* Attachment thumbnails */}
@@ -380,13 +380,13 @@ export function ChatPane({
<img <img
key={attIdx} key={attIdx}
alt={getAttachmentDisplayName(att)} alt={getAttachmentDisplayName(att)}
className="max-h-48 max-w-xs rounded-lg border border-zinc-700 object-cover" className="max-h-48 max-w-xs rounded-lg border border-[var(--color-border)] object-cover"
src={`data:${att.mimeType};base64,${att.data}`} src={`data:${att.mimeType};base64,${att.data}`}
/> />
) : ( ) : (
<div <div
key={attIdx} key={attIdx}
className="flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2 py-1 text-[11px] text-zinc-400" className="flex items-center gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2 py-1 text-[11px] text-[var(--color-text-secondary)]"
> >
<Paperclip className="size-3" /> <Paperclip className="size-3" />
{getAttachmentDisplayName(att)} {getAttachmentDisplayName(att)}
@@ -396,14 +396,14 @@ export function ChatPane({
</div> </div>
)} )}
{!isUser && message.pending ? ( {!isUser && message.pending ? (
<div className="whitespace-pre-wrap break-words text-[14px] leading-relaxed text-zinc-200"> <div className="whitespace-pre-wrap break-words text-[14px] leading-relaxed text-[var(--color-text-primary)]">
{message.content} {message.content}
</div> </div>
) : ( ) : (
<MarkdownContent content={message.content} /> <MarkdownContent content={message.content} />
)} )}
{message.pending && message.content && ( {message.pending && message.content && (
<span className="mt-1 inline-block h-4 w-[2px] animate-pulse rounded-sm bg-zinc-400" /> <span className="mt-1 inline-block h-4 w-[2px] animate-pulse rounded-sm bg-[var(--color-accent)]" />
)} )}
</div> </div>
{message.pending && !message.content && <ThinkingDots />} {message.pending && !message.content && <ThinkingDots />}
@@ -423,24 +423,24 @@ export function ChatPane({
</div> </div>
{/* Input area */} {/* Input area */}
<div className="border-t border-[var(--color-border)] px-6 py-4"> <div className="border-t border-[var(--color-border-subtle)] px-6 py-4">
{session.lastError && ( {session.lastError && (
<div className="mb-3 flex items-start gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-[13px] text-red-300"> <div className="mb-3 flex items-start gap-2 rounded-lg bg-[var(--color-status-error)]/10 px-3 py-2 text-[13px] text-[var(--color-status-error)]">
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" /> <AlertCircle className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
<span>{session.lastError}</span> <span>{session.lastError}</span>
</div> </div>
)} )}
{configError && ( {configError && (
<div className="mb-3 flex items-start gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-[13px] text-red-300"> <div className="mb-3 flex items-start gap-2 rounded-lg bg-[var(--color-status-error)]/10 px-3 py-2 text-[13px] text-[var(--color-status-error)]">
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" /> <AlertCircle className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
<span>{configError}</span> <span>{configError}</span>
</div> </div>
)} )}
{approvalError && ( {approvalError && (
<div className="mb-3 flex items-start gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-[13px] text-red-300"> <div className="mb-3 flex items-start gap-2 rounded-lg bg-[var(--color-status-error)]/10 px-3 py-2 text-[13px] text-[var(--color-status-error)]">
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" /> <AlertCircle className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
<span>{approvalError}</span> <span>{approvalError}</span>
</div> </div>
)} )}
@@ -544,7 +544,7 @@ export function ChatPane({
value={sessionReasoningEffort} value={sessionReasoningEffort}
/> />
{isUpdatingSessionModelConfig && ( {isUpdatingSessionModelConfig && (
<Loader2 className="size-3 animate-spin text-zinc-500" /> <Loader2 className="size-3 animate-spin text-[var(--color-text-muted)]" />
)} )}
</div> </div>
)} )}
@@ -584,13 +584,13 @@ export function ChatPane({
{pendingAttachments.map((attachment, index) => ( {pendingAttachments.map((attachment, index) => (
<div <div
key={index} key={index}
className="flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-1.5 text-[11px] text-zinc-300" className="flex items-center gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2.5 py-1.5 text-[11px] text-[var(--color-text-secondary)]"
> >
<Paperclip className="size-3 text-zinc-500" /> <Paperclip className="size-3 text-[var(--color-text-muted)]" />
<span className="max-w-[160px] truncate">{getAttachmentDisplayName(attachment)}</span> <span className="max-w-[160px] truncate">{getAttachmentDisplayName(attachment)}</span>
<button <button
aria-label="Remove attachment" aria-label="Remove attachment"
className="ml-1 rounded p-0.5 text-zinc-500 hover:bg-zinc-700 hover:text-zinc-300" className="ml-1 rounded p-0.5 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={() => setPendingAttachments((prev) => prev.filter((_, i) => i !== index))} onClick={() => setPendingAttachments((prev) => prev.filter((_, i) => i !== index))}
type="button" type="button"
> >
@@ -601,7 +601,7 @@ export function ChatPane({
</div> </div>
)} )}
<div className="rounded-xl border border-zinc-700 bg-zinc-900 transition-colors focus-within:border-indigo-500/50"> <div className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-1)] transition-all duration-200 focus-within:border-[var(--color-border-glow)] focus-within:shadow-[0_0_16px_rgba(36,92,249,0.06)]">
<MarkdownComposer <MarkdownComposer
ref={composerRef} ref={composerRef}
disabled={isComposerDisabled} disabled={isComposerDisabled}
@@ -651,7 +651,7 @@ export function ChatPane({
{/* Attachment picker */} {/* Attachment picker */}
<button <button
aria-label="Attach image" aria-label="Attach image"
className="flex size-8 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="flex size-8 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition-all duration-150 hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text-primary)]"
disabled={isComposerDisabled} disabled={isComposerDisabled}
onClick={() => { onClick={() => {
const input = document.createElement('input'); const input = document.createElement('input');
@@ -685,10 +685,10 @@ export function ChatPane({
<button <button
aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'} aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'}
aria-pressed={isPlanMode} aria-pressed={isPlanMode}
className={`flex size-8 items-center justify-center rounded-lg transition ${ className={`flex size-8 items-center justify-center rounded-lg transition-all duration-150 ${
isPlanMode isPlanMode
? 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30' ? 'bg-[var(--color-status-success)]/20 text-[var(--color-status-success)] hover:bg-[var(--color-status-success)]/30'
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300' : 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text-primary)]'
}`} }`}
disabled={isComposerDisabled} disabled={isComposerDisabled}
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')} onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
@@ -700,16 +700,16 @@ export function ChatPane({
{/* Send / Stop / Steer button */} {/* Send / Stop / Steer button */}
<button <button
className={`flex size-8 items-center justify-center rounded-lg transition ${ className={`flex size-8 items-center justify-center rounded-lg transition-all duration-150 ${
isSessionBusy && !hasComposerContent && pendingAttachments.length === 0 isSessionBusy && !hasComposerContent && pendingAttachments.length === 0
? 'bg-red-600/80 text-white hover:bg-red-500' ? 'bg-[var(--color-status-error)]/80 text-white hover:bg-[var(--color-status-error)]'
: canSubmitInput || pendingAttachments.length > 0 : canSubmitInput || pendingAttachments.length > 0
? isSessionBusy ? isSessionBusy
? 'bg-amber-600 text-white hover:bg-amber-500' ? 'bg-[var(--color-status-warning)] text-white hover:brightness-110'
: isPlanMode : isPlanMode
? 'bg-emerald-600 text-white hover:bg-emerald-500' ? 'bg-[var(--color-status-success)] text-white hover:brightness-110'
: 'bg-indigo-600 text-white hover:bg-indigo-500' : 'brand-gradient-bg text-white shadow-[0_2px_12px_rgba(36,92,249,0.25)] hover:shadow-[0_4px_20px_rgba(36,92,249,0.35)]'
: 'bg-zinc-800 text-zinc-600' : 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
}`} }`}
disabled={!canSubmitInput && !isSessionBusy && pendingAttachments.length === 0} disabled={!canSubmitInput && !isSessionBusy && pendingAttachments.length === 0}
onClick={() => { onClick={() => {
@@ -741,16 +741,16 @@ export function ChatPane({
</MarkdownComposer> </MarkdownComposer>
{isPlanMode && !isSessionBusy && ( {isPlanMode && !isSessionBusy && (
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5"> <div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
<div className="size-1.5 rounded-full bg-emerald-500" /> <div className="size-1.5 rounded-full bg-[var(--color-status-success)]" />
<span className="text-[10px] font-medium text-emerald-400/80"> <span className="text-[10px] font-medium text-[var(--color-status-success)]/80">
Plan mode the agent will propose a plan instead of implementing Plan mode the agent will propose a plan instead of implementing
</span> </span>
</div> </div>
)} )}
{isSessionBusy && (hasComposerContent || pendingAttachments.length > 0) && ( {isSessionBusy && (hasComposerContent || pendingAttachments.length > 0) && (
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5"> <div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
<div className="size-1.5 rounded-full bg-amber-500" /> <div className="size-1.5 rounded-full bg-[var(--color-status-warning)]" />
<span className="text-[10px] font-medium text-amber-400/80"> <span className="text-[10px] font-medium text-[var(--color-status-warning)]/80">
Steering your message will be injected into the current turn Steering your message will be injected into the current turn
</span> </span>
</div> </div>
@@ -760,15 +760,15 @@ export function ChatPane({
{/* Session usage bar */} {/* Session usage bar */}
{sessionUsage && sessionUsage.tokenLimit > 0 && ( {sessionUsage && sessionUsage.tokenLimit > 0 && (
<div className="px-1 pt-1.5"> <div className="px-1 pt-1.5">
<div className="flex items-center gap-2 text-[10px] text-zinc-500"> <div className="flex items-center gap-2 text-[10px] text-[var(--color-text-muted)]">
<div className="h-1 flex-1 overflow-hidden rounded-full bg-zinc-800"> <div className="h-1 flex-1 overflow-hidden rounded-full bg-[var(--color-surface-2)]">
<div <div
className={`h-full rounded-full transition-all ${ className={`h-full rounded-full transition-all ${
sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.9 sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.9
? 'bg-red-500' ? 'bg-[var(--color-status-error)]'
: sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.7 : sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.7
? 'bg-amber-500' ? 'bg-[var(--color-status-warning)]'
: 'bg-indigo-500/60' : 'bg-[var(--color-accent)]/60'
}`} }`}
style={{ width: `${Math.min(100, (sessionUsage.currentTokens / sessionUsage.tokenLimit) * 100)}%` }} style={{ width: `${Math.min(100, (sessionUsage.currentTokens / sessionUsage.tokenLimit) * 100)}%` }}
/> />
+64 -64
View File
@@ -44,35 +44,35 @@ function getStatusConfig(status: SidecarConnectionStatus): StatusConfig {
switch (status) { switch (status) {
case 'ready': case 'ready':
return { return {
icon: <CheckCircle2 className="size-4 text-emerald-400" />, icon: <CheckCircle2 className="size-4 text-[var(--color-status-success)]" />,
label: 'Connected to GitHub Copilot', label: 'Connected to GitHub Copilot',
accentClasses: 'text-emerald-400', accentClasses: 'text-[var(--color-status-success)]',
dotClasses: 'bg-emerald-400', dotClasses: 'bg-[var(--color-status-success)]',
}; };
case 'copilot-cli-missing': case 'copilot-cli-missing':
return { return {
icon: <Download className="size-4 text-amber-400" />, icon: <Download className="size-4 text-[var(--color-status-warning)]" />,
label: 'Copilot CLI not found', label: 'Copilot CLI not found',
accentClasses: 'text-amber-400', accentClasses: 'text-[var(--color-status-warning)]',
dotClasses: 'bg-amber-400', dotClasses: 'bg-[var(--color-status-warning)]',
actionIcon: <Terminal className="size-3" />, actionIcon: <Terminal className="size-3" />,
actionLabel: 'Install the copilot CLI and ensure it is on your PATH', actionLabel: 'Install the copilot CLI and ensure it is on your PATH',
}; };
case 'copilot-auth-required': case 'copilot-auth-required':
return { return {
icon: <LogIn className="size-4 text-blue-400" />, icon: <LogIn className="size-4 text-[var(--color-status-info)]" />,
label: 'Sign-in required', label: 'Sign-in required',
accentClasses: 'text-blue-400', accentClasses: 'text-[var(--color-status-info)]',
dotClasses: 'bg-blue-400', dotClasses: 'bg-[var(--color-status-info)]',
actionIcon: <Terminal className="size-3" />, actionIcon: <Terminal className="size-3" />,
actionLabel: 'Run copilot auth login in your terminal, then refresh', actionLabel: 'Run copilot auth login in your terminal, then refresh',
}; };
case 'copilot-error': case 'copilot-error':
return { return {
icon: <XCircle className="size-4 text-red-400" />, icon: <XCircle className="size-4 text-[var(--color-status-error)]" />,
label: 'Connection error', label: 'Connection error',
accentClasses: 'text-red-400', accentClasses: 'text-[var(--color-status-error)]',
dotClasses: 'bg-red-400', dotClasses: 'bg-[var(--color-status-error)]',
}; };
} }
} }
@@ -108,21 +108,21 @@ function VersionBadge({ status, installedVersion }: { status: SidecarCopilotCliV
switch (status) { switch (status) {
case 'latest': case 'latest':
return ( return (
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-400"> <span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-status-success)]/10 px-2 py-0.5 text-[10px] font-medium text-[var(--color-status-success)]">
<CheckCircle2 className="size-2.5" /> <CheckCircle2 className="size-2.5" />
{versionLabel ?? 'Up to date'} {versionLabel ?? 'Up to date'}
</span> </span>
); );
case 'outdated': case 'outdated':
return ( return (
<span className="inline-flex items-center gap-1 rounded-full bg-amber-500/10 px-2 py-0.5 text-[10px] font-medium text-amber-400"> <span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-status-warning)]/10 px-2 py-0.5 text-[10px] font-medium text-[var(--color-status-warning)]">
<ArrowUpCircle className="size-2.5" /> <ArrowUpCircle className="size-2.5" />
Update available Update available
</span> </span>
); );
case 'unknown': case 'unknown':
return ( return (
<span className="inline-flex items-center gap-1 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-500"> <span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
{versionLabel ?? 'Version unknown'} {versionLabel ?? 'Version unknown'}
</span> </span>
); );
@@ -183,8 +183,8 @@ function QuotaSection({
if (loading) { if (loading) {
return ( return (
<div className="flex items-center gap-2 py-2"> <div className="flex items-center gap-2 py-2">
<Loader2 className="size-3.5 animate-spin text-zinc-500" /> <Loader2 className="size-3.5 animate-spin text-[var(--color-text-muted)]" />
<span className="text-[12px] text-zinc-500">Loading quota</span> <span className="text-[12px] text-[var(--color-text-muted)]">Loading quota</span>
</div> </div>
); );
} }
@@ -192,8 +192,8 @@ function QuotaSection({
if (error) { if (error) {
return ( return (
<div className="flex items-center gap-2 py-2"> <div className="flex items-center gap-2 py-2">
<XCircle className="size-3.5 text-red-400" /> <XCircle className="size-3.5 text-[var(--color-status-error)]" />
<span className="text-[12px] text-zinc-500">Could not load quota</span> <span className="text-[12px] text-[var(--color-text-muted)]">Could not load quota</span>
</div> </div>
); );
} }
@@ -201,7 +201,7 @@ function QuotaSection({
if (!quotaData || Object.keys(quotaData).length === 0) { if (!quotaData || Object.keys(quotaData).length === 0) {
return ( return (
<div className="py-2"> <div className="py-2">
<span className="text-[12px] text-zinc-600">No quota data available</span> <span className="text-[12px] text-[var(--color-text-muted)]">No quota data available</span>
</div> </div>
); );
} }
@@ -213,42 +213,42 @@ function QuotaSection({
? (snapshot.usedRequests / snapshot.entitlementRequests) * 100 ? (snapshot.usedRequests / snapshot.entitlementRequests) * 100
: 0; : 0;
const barColor = usedPct > 90 const barColor = usedPct > 90
? 'bg-red-500' ? 'bg-[var(--color-status-error)]'
: usedPct > 70 : usedPct > 70
? 'bg-amber-500' ? 'bg-[var(--color-status-warning)]'
: 'bg-indigo-500/60'; : 'bg-[var(--color-accent)]/60';
return ( return (
<div key={key} className="space-y-1.5"> <div key={key} className="space-y-1.5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-[12px] font-medium text-zinc-300"> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">
{formatQuotaTypeLabel(key)} {formatQuotaTypeLabel(key)}
</span> </span>
<span className="text-[11px] tabular-nums text-zinc-500"> <span className="text-[11px] tabular-nums text-[var(--color-text-muted)]">
{Math.round(snapshot.remainingPercentage)}% remaining {Math.round(snapshot.remainingPercentage)}% remaining
</span> </span>
</div> </div>
<div className="h-1.5 overflow-hidden rounded-full bg-zinc-800"> <div className="h-1.5 overflow-hidden rounded-full bg-[var(--color-surface-3)]">
<div <div
className={`h-full rounded-full transition-all ${barColor}`} className={`h-full rounded-full transition-all ${barColor}`}
style={{ width: `${Math.min(100, usedPct)}%` }} style={{ width: `${Math.min(100, usedPct)}%` }}
/> />
</div> </div>
<div className="flex items-center gap-1.5 text-[10px] text-zinc-500"> <div className="flex items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
<span className="tabular-nums"> <span className="tabular-nums">
{Math.round(snapshot.usedRequests)} of {Math.round(snapshot.entitlementRequests)} used {Math.round(snapshot.usedRequests)} of {Math.round(snapshot.entitlementRequests)} used
</span> </span>
{snapshot.overage > 0 && ( {snapshot.overage > 0 && (
<> <>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums text-amber-400"> <span className="tabular-nums text-[var(--color-status-warning)]">
{Math.round(snapshot.overage)} overage {Math.round(snapshot.overage)} overage
</span> </span>
</> </>
)} )}
{snapshot.resetDate && ( {snapshot.resetDate && (
<> <>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span>Resets {formatResetDate(snapshot.resetDate)}</span> <span>Resets {formatResetDate(snapshot.resetDate)}</span>
</> </>
)} )}
@@ -276,34 +276,34 @@ function AccountSection({ connection }: { connection: SidecarConnectionDiagnosti
<div className="space-y-2.5"> <div className="space-y-2.5">
{/* Identity row */} {/* Identity row */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<User className="size-3.5 text-zinc-500" /> <User className="size-3.5 text-[var(--color-text-muted)]" />
{hasLogin ? ( {hasLogin ? (
<div className="flex items-center gap-1.5 text-[12px]"> <div className="flex items-center gap-1.5 text-[12px]">
<span className="font-medium text-zinc-200">{account.login}</span> <span className="font-medium text-[var(--color-text-primary)]">{account.login}</span>
{account.host && ( {account.host && (
<span className="text-zinc-500">· {account.host}</span> <span className="text-[var(--color-text-muted)]">· {account.host}</span>
)} )}
</div> </div>
) : ( ) : (
<span className="text-[12px] text-zinc-500">{account.statusMessage}</span> <span className="text-[12px] text-[var(--color-text-muted)]">{account.statusMessage}</span>
)} )}
</div> </div>
{/* Organizations */} {/* Organizations */}
{hasOrgs && ( {hasOrgs && (
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<Building2 className="mt-0.5 size-3.5 shrink-0 text-zinc-500" /> <Building2 className="mt-0.5 size-3.5 shrink-0 text-[var(--color-text-muted)]" />
<div className="flex flex-wrap items-center gap-1"> <div className="flex flex-wrap items-center gap-1">
{visibleOrgs.map((org) => ( {visibleOrgs.map((org) => (
<span <span
className="rounded-md bg-zinc-800 px-1.5 py-0.5 text-[10px] font-medium text-zinc-400" className="rounded-md bg-[var(--color-surface-3)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-secondary)]"
key={org} key={org}
> >
{org} {org}
</span> </span>
))} ))}
{remainingOrgs > 0 && ( {remainingOrgs > 0 && (
<span className="text-[10px] text-zinc-600">+{remainingOrgs} more</span> <span className="text-[10px] text-[var(--color-text-muted)]">+{remainingOrgs} more</span>
)} )}
</div> </div>
</div> </div>
@@ -323,10 +323,10 @@ export function CopilotStatusCard({
if (!connection) { if (!connection) {
return ( return (
<div className="flex items-center gap-3 rounded-xl border border-[var(--color-border)] bg-zinc-900/40 px-4 py-3"> <div className="flex items-center gap-3 rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] px-4 py-3">
<Cpu className="size-4 text-zinc-600" /> <Cpu className="size-4 text-[var(--color-text-muted)]" />
<span className="text-[13px] text-zinc-500">Checking connection</span> <span className="text-[13px] text-[var(--color-text-muted)]">Checking connection</span>
<RefreshCw className="ml-auto size-3.5 animate-spin text-zinc-600" /> <RefreshCw className="ml-auto size-3.5 animate-spin text-[var(--color-text-muted)]" />
</div> </div>
); );
} }
@@ -347,7 +347,7 @@ export function CopilotStatusCard({
{config.label} {config.label}
</span> </span>
{isHealthy && ( {isHealthy && (
<span className="text-[12px] text-zinc-500"> <span className="text-[12px] text-[var(--color-text-muted)]">
· {modelCount} model{modelCount === 1 ? '' : 's'} available · {modelCount} model{modelCount === 1 ? '' : 's'} available
</span> </span>
)} )}
@@ -359,10 +359,10 @@ export function CopilotStatusCard({
/> />
)} )}
{checkedLabel && ( {checkedLabel && (
<span className="text-[11px] text-zinc-600">{checkedLabel}</span> <span className="text-[11px] text-[var(--color-text-muted)]">{checkedLabel}</span>
)} )}
<button <button
className="flex size-6 items-center justify-center rounded-md text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300 disabled:opacity-50" className="flex size-6 items-center justify-center rounded-md text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)] disabled:opacity-50"
disabled={isRefreshing} disabled={isRefreshing}
onClick={onRefresh} onClick={onRefresh}
title="Refresh connection status" title="Refresh connection status"
@@ -381,11 +381,11 @@ export function CopilotStatusCard({
{/* Usage & Quota (when healthy and callback provided) */} {/* Usage & Quota (when healthy and callback provided) */}
{isHealthy && onGetQuota && ( {isHealthy && onGetQuota && (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-1.5 text-[11px] font-medium text-zinc-400"> <div className="flex items-center gap-1.5 text-[11px] font-medium text-[var(--color-text-secondary)]">
<BarChart3 className="size-3" /> <BarChart3 className="size-3" />
<span>Usage &amp; Quota</span> <span>Usage &amp; Quota</span>
</div> </div>
<div className="rounded-lg border border-zinc-800/60 bg-zinc-900/40 px-3 py-2.5"> <div className="rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)] px-3 py-2.5">
<QuotaSection onGetQuota={onGetQuota} /> <QuotaSection onGetQuota={onGetQuota} />
</div> </div>
</div> </div>
@@ -393,9 +393,9 @@ export function CopilotStatusCard({
{/* Action hint for non-ready states */} {/* Action hint for non-ready states */}
{!isHealthy && config.actionLabel && ( {!isHealthy && config.actionLabel && (
<div className="flex items-start gap-2 rounded-lg border border-zinc-800 bg-zinc-900/60 px-3 py-2.5"> <div className="flex items-start gap-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-glass)] px-3 py-2.5">
<div className="mt-0.5 shrink-0">{config.actionIcon}</div> <div className="mt-0.5 shrink-0">{config.actionIcon}</div>
<p className="text-[12px] leading-relaxed text-zinc-400">{config.actionLabel}</p> <p className="text-[12px] leading-relaxed text-[var(--color-text-secondary)]">{config.actionLabel}</p>
</div> </div>
)} )}
@@ -403,7 +403,7 @@ export function CopilotStatusCard({
{isHealthy && hasDetail && ( {isHealthy && hasDetail && (
<div className="space-y-2"> <div className="space-y-2">
<button <button
className="flex w-full items-center gap-1.5 text-[11px] text-zinc-600 transition hover:text-zinc-400" className="flex w-full items-center gap-1.5 text-[11px] text-[var(--color-text-muted)] transition-all duration-200 hover:text-[var(--color-text-secondary)]"
onClick={() => setShowDetails((prev) => !prev)} onClick={() => setShowDetails((prev) => !prev)}
type="button" type="button"
> >
@@ -412,26 +412,26 @@ export function CopilotStatusCard({
</button> </button>
{showDetails && ( {showDetails && (
<div className="overflow-hidden rounded-lg border border-zinc-800/60 bg-zinc-900/40"> <div className="overflow-hidden rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)]">
{connection.copilotCliPath && ( {connection.copilotCliPath && (
<div className="border-b border-zinc-800/40 px-3 py-2"> <div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">CLI path</span> <span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">CLI path</span>
<p className="mt-0.5 break-all font-mono text-[11px] text-zinc-400" title={connection.copilotCliPath}> <p className="mt-0.5 break-all font-mono text-[11px] text-[var(--color-text-secondary)]" title={connection.copilotCliPath}>
{connection.copilotCliPath} {connection.copilotCliPath}
</p> </p>
</div> </div>
)} )}
{hasVersionInfo && connection.copilotCliVersion!.status === 'outdated' && connection.copilotCliVersion!.latestVersion && ( {hasVersionInfo && connection.copilotCliVersion!.status === 'outdated' && connection.copilotCliVersion!.latestVersion && (
<div className="border-b border-zinc-800/40 px-3 py-2"> <div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Latest version</span> <span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">Latest version</span>
<p className="mt-0.5 font-mono text-[11px] text-zinc-400"> <p className="mt-0.5 font-mono text-[11px] text-[var(--color-text-secondary)]">
{connection.copilotCliVersion!.latestVersion} {connection.copilotCliVersion!.latestVersion}
</p> </p>
</div> </div>
)} )}
<div className="px-3 py-2"> <div className="px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Last checked</span> <span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">Last checked</span>
<p className="mt-0.5 text-[11px] text-zinc-400"> <p className="mt-0.5 text-[11px] text-[var(--color-text-secondary)]">
{new Date(connection.checkedAt).toLocaleString()} {new Date(connection.checkedAt).toLocaleString()}
</p> </p>
</div> </div>
@@ -442,17 +442,17 @@ export function CopilotStatusCard({
{/* Error detail for non-ready states */} {/* Error detail for non-ready states */}
{!isHealthy && hasDetail && ( {!isHealthy && hasDetail && (
<div className="overflow-hidden rounded-lg border border-zinc-800/60 bg-zinc-900/40"> <div className="overflow-hidden rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)]">
<div className="border-b border-zinc-800/40 px-3 py-2"> <div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">CLI path</span> <span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">CLI path</span>
<p className="mt-0.5 break-all font-mono text-[11px] text-zinc-400" title={connection.copilotCliPath}> <p className="mt-0.5 break-all font-mono text-[11px] text-[var(--color-text-secondary)]" title={connection.copilotCliPath}>
{shortenPath(connection.copilotCliPath!)} {shortenPath(connection.copilotCliPath!)}
</p> </p>
</div> </div>
{connection.detail && ( {connection.detail && (
<div className="px-3 py-2"> <div className="px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Error detail</span> <span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">Error detail</span>
<p className="mt-0.5 break-words text-[11px] text-zinc-400">{connection.detail}</p> <p className="mt-0.5 break-words text-[11px] text-[var(--color-text-secondary)]">{connection.detail}</p>
</div> </div>
)} )}
</div> </div>
@@ -81,20 +81,20 @@ export function DiscoveredToolingModal({
<div <div
aria-labelledby="discovered-tooling-title" aria-labelledby="discovered-tooling-title"
aria-modal="true" aria-modal="true"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" className="fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm"
role="dialog" role="dialog"
> >
<div className="flex max-h-[80vh] w-full max-w-lg flex-col rounded-xl border border-zinc-800 bg-zinc-900 shadow-2xl"> <div className="flex max-h-[80vh] w-full max-w-lg flex-col rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between border-b border-zinc-800 px-5 py-4"> <div className="flex items-center justify-between border-b border-[var(--color-border)] px-5 py-4">
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<FileSearch className="size-4 text-indigo-400" /> <FileSearch className="size-4 text-[var(--color-text-accent)]" />
<h2 id="discovered-tooling-title" className="text-[13px] font-semibold text-zinc-100"> <h2 id="discovered-tooling-title" className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">
MCP servers found in config files MCP servers found in config files
</h2> </h2>
</div> </div>
<button <button
className="flex size-7 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="flex size-7 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
onClick={onClose} onClick={onClose}
type="button" type="button"
> >
@@ -104,7 +104,7 @@ export function DiscoveredToolingModal({
{/* Body */} {/* Body */}
<div className="flex-1 overflow-y-auto px-5 py-4"> <div className="flex-1 overflow-y-auto px-5 py-4">
<p className="mb-4 text-[12px] leading-relaxed text-zinc-500"> <p className="mb-4 text-[12px] leading-relaxed text-[var(--color-text-muted)]">
The following MCP servers were found in your config files. Accept the ones you want to The following MCP servers were found in your config files. Accept the ones you want to
use, or dismiss those you don&apos;t need. Accepted servers become available for session tooling. use, or dismiss those you don&apos;t need. Accepted servers become available for session tooling.
</p> </p>
@@ -127,20 +127,20 @@ export function DiscoveredToolingModal({
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-between border-t border-zinc-800 px-5 py-3"> <div className="flex items-center justify-between border-t border-[var(--color-border)] px-5 py-3">
<span className="text-[12px] text-zinc-600"> <span className="text-[12px] text-[var(--color-text-muted)]">
{totalPending} server{totalPending === 1 ? '' : 's'} pending review {totalPending} server{totalPending === 1 ? '' : 's'} pending review
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
className="rounded-lg px-3 py-1.5 text-[13px] text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="rounded-lg px-3 py-1.5 text-[13px] text-[var(--color-text-secondary)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={handleDismissAll} onClick={handleDismissAll}
type="button" type="button"
> >
Dismiss All Dismiss All
</button> </button>
<button <button
className="rounded-lg bg-indigo-600 px-3 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500" className="rounded-lg bg-[var(--color-accent)] px-3 py-1.5 text-[13px] font-medium text-white transition hover:bg-[var(--color-accent-sky)]"
onClick={handleAcceptAll} onClick={handleAcceptAll}
type="button" type="button"
> >
@@ -166,15 +166,15 @@ function DiscoveredGroup({
}) { }) {
return ( return (
<div className="mb-4"> <div className="mb-4">
<div className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-600"> <div className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{scopeLabel} {scopeLabel}
</div> </div>
{groups.map((group) => ( {groups.map((group) => (
<div className="mb-3" key={group.sourceLabel}> <div className="mb-3" key={group.sourceLabel}>
<div className="mb-1.5 flex items-center gap-1.5 text-[11px] text-zinc-500"> <div className="mb-1.5 flex items-center gap-1.5 text-[11px] text-[var(--color-text-muted)]">
<span className="truncate font-medium">{group.sourceLabel}</span> <span className="truncate font-medium">{group.sourceLabel}</span>
<span className="text-zinc-700">·</span> <span className="text-[var(--color-text-muted)]">·</span>
<span className="text-zinc-600"> <span className="text-[var(--color-text-muted)]">
{group.servers.length} server{group.servers.length === 1 ? '' : 's'} {group.servers.length} server{group.servers.length === 1 ? '' : 's'}
</span> </span>
</div> </div>
@@ -211,22 +211,22 @@ function ServerRow({
: server.url || 'No URL'; : server.url || 'No URL';
return ( return (
<div className="group flex items-center gap-3 rounded-lg border border-zinc-800/60 bg-zinc-800/20 px-3 py-2.5"> <div className="group flex items-center gap-3 rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)] px-3 py-2.5">
<Server className="size-3.5 shrink-0 text-zinc-600" /> <Server className="size-3.5 shrink-0 text-[var(--color-text-muted)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="truncate text-[13px] font-medium text-zinc-200"> <span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">
{server.name} {server.name}
</span> </span>
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-500"> <span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]">
{server.transport} {server.transport}
</span> </span>
</div> </div>
<p className="mt-0.5 truncate text-[12px] text-zinc-500">{detail}</p> <p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{detail}</p>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
className="flex size-7 items-center justify-center rounded-md text-zinc-600 transition hover:bg-red-500/10 hover:text-red-400" className="flex size-7 items-center justify-center rounded-md text-[var(--color-text-muted)] transition hover:bg-[var(--color-status-error)]/10 hover:text-[var(--color-status-error)]"
onClick={onDismiss} onClick={onDismiss}
title="Dismiss" title="Dismiss"
type="button" type="button"
@@ -234,7 +234,7 @@ function ServerRow({
<XCircle className="size-3.5" /> <XCircle className="size-3.5" />
</button> </button>
<button <button
className="flex size-7 items-center justify-center rounded-md text-zinc-600 transition hover:bg-emerald-500/10 hover:text-emerald-400" className="flex size-7 items-center justify-center rounded-md text-[var(--color-text-muted)] transition hover:bg-[var(--color-status-success)]/10 hover:text-[var(--color-status-success)]"
onClick={onAccept} onClick={onAccept}
title="Accept" title="Accept"
type="button" type="button"
+4 -4
View File
@@ -677,11 +677,11 @@ function ToolbarPlugin({ disabled }: { disabled: boolean }) {
}, [editor]); }, [editor]);
return ( return (
<div className="flex items-center gap-0.5 border-b border-zinc-700/50 px-2 py-1"> <div className="flex items-center gap-0.5 border-b border-[var(--color-border)]/50 px-2 py-1">
<ToolbarButton active={state.isBold} disabled={disabled} icon={<Bold className="size-3.5" />} onClick={formatBold} onMouseDown={preventFocus} title="Bold (Ctrl+B)" /> <ToolbarButton active={state.isBold} disabled={disabled} icon={<Bold className="size-3.5" />} onClick={formatBold} onMouseDown={preventFocus} title="Bold (Ctrl+B)" />
<ToolbarButton active={state.isItalic} disabled={disabled} icon={<Italic className="size-3.5" />} onClick={formatItalic} onMouseDown={preventFocus} title="Italic (Ctrl+I)" /> <ToolbarButton active={state.isItalic} disabled={disabled} icon={<Italic className="size-3.5" />} onClick={formatItalic} onMouseDown={preventFocus} title="Italic (Ctrl+I)" />
<ToolbarButton active={state.isCode} disabled={disabled} icon={<Code className="size-3.5" />} onClick={formatInlineCode} onMouseDown={preventFocus} title="Inline Code" /> <ToolbarButton active={state.isCode} disabled={disabled} icon={<Code className="size-3.5" />} onClick={formatInlineCode} onMouseDown={preventFocus} title="Inline Code" />
<div className="mx-1 h-4 w-px bg-zinc-700/50" /> <div className="mx-1 h-4 w-px bg-[var(--color-border)]/50" />
<ToolbarButton active={state.blockType === 'ul'} disabled={disabled} icon={<List className="size-3.5" />} onClick={toggleBulletList} onMouseDown={preventFocus} title="Bullet List" /> <ToolbarButton active={state.blockType === 'ul'} disabled={disabled} icon={<List className="size-3.5" />} onClick={toggleBulletList} onMouseDown={preventFocus} title="Bullet List" />
<ToolbarButton active={state.blockType === 'ol'} disabled={disabled} icon={<ListOrdered className="size-3.5" />} onClick={toggleNumberedList} onMouseDown={preventFocus} title="Numbered List" /> <ToolbarButton active={state.blockType === 'ol'} disabled={disabled} icon={<ListOrdered className="size-3.5" />} onClick={toggleNumberedList} onMouseDown={preventFocus} title="Numbered List" />
<ToolbarButton active={state.blockType === 'code'} disabled={disabled} icon={<Braces className="size-3.5" />} onClick={toggleCodeBlock} onMouseDown={preventFocus} title="Code Block" /> <ToolbarButton active={state.blockType === 'code'} disabled={disabled} icon={<Braces className="size-3.5" />} onClick={toggleCodeBlock} onMouseDown={preventFocus} title="Code Block" />
@@ -709,8 +709,8 @@ function ToolbarButton({
aria-pressed={active} aria-pressed={active}
className={`flex size-7 items-center justify-center rounded transition ${ className={`flex size-7 items-center justify-center rounded transition ${
active active
? 'bg-indigo-600/30 text-indigo-300' ? 'bg-[var(--color-accent)]/30 text-[var(--color-accent-sky)]'
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300' : 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]'
} ${disabled ? 'pointer-events-none opacity-50' : ''}`} } ${disabled ? 'pointer-events-none opacity-50' : ''}`}
disabled={disabled} disabled={disabled}
onClick={onClick} onClick={onClick}
+18 -18
View File
@@ -57,13 +57,13 @@ export function NewSessionModal({
const canCreate = projectId && patternId; const canCreate = projectId && patternId;
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="new-session-title"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="new-session-title">
<div className="w-full max-w-md rounded-xl border border-zinc-800 bg-zinc-900 shadow-2xl"> <div className="w-full max-w-md rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between border-b border-zinc-800 px-5 py-4"> <div className="flex items-center justify-between border-b border-[var(--color-border)] px-5 py-4">
<h2 id="new-session-title" className="text-[13px] font-semibold text-zinc-100">New Session</h2> <h2 id="new-session-title" className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">New Session</h2>
<button <button
className="flex size-7 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="flex size-7 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
onClick={onClose} onClick={onClose}
type="button" type="button"
> >
@@ -74,9 +74,9 @@ export function NewSessionModal({
{/* Body */} {/* Body */}
<div className="space-y-4 px-5 py-5"> <div className="space-y-4 px-5 py-5">
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">Project</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">Project</span>
<select <select
className="w-full rounded-lg border border-zinc-700 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition focus:border-indigo-500/50" className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-0)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition focus:border-[var(--color-accent)]/50"
onChange={(e) => setProjectId(e.target.value)} onChange={(e) => setProjectId(e.target.value)}
value={projectId} value={projectId}
> >
@@ -89,28 +89,28 @@ export function NewSessionModal({
</label> </label>
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">Pattern</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">Pattern</span>
<div className="space-y-1 rounded-lg border border-zinc-700 bg-zinc-950 p-1.5"> <div className="space-y-1 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-0)] p-1.5">
{availablePatterns.map((p) => ( {availablePatterns.map((p) => (
<div <div
key={p.id} key={p.id}
className={`flex cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] transition ${ className={`flex cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] transition ${
patternId === p.id patternId === p.id
? 'bg-indigo-500/15 text-zinc-100 ring-1 ring-indigo-500/25' ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-primary)] ring-1 ring-[var(--color-border-glow)]'
: 'text-zinc-300 hover:bg-zinc-800/60' : 'text-[var(--color-text-secondary)] hover:bg-[var(--color-glass-hover)]'
}`} }`}
onClick={() => setPatternId(p.id)} onClick={() => setPatternId(p.id)}
> >
<span className="flex-1 truncate"> <span className="flex-1 truncate">
{p.name} {p.name}
<span className="ml-1.5 text-[11px] text-zinc-500">({p.mode})</span> <span className="ml-1.5 text-[11px] text-[var(--color-text-muted)]">({p.mode})</span>
</span> </span>
{onTogglePatternFavorite && ( {onTogglePatternFavorite && (
<button <button
className={`shrink-0 transition ${ className={`shrink-0 transition ${
p.isFavorite p.isFavorite
? 'text-amber-400 hover:text-amber-300' ? 'text-[var(--color-status-warning)] hover:text-[var(--color-status-warning)]'
: 'text-zinc-700 hover:text-zinc-400' : 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
}`} }`}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@@ -126,7 +126,7 @@ export function NewSessionModal({
))} ))}
</div> </div>
{patternId && ( {patternId && (
<p className="text-[12px] text-zinc-600"> <p className="text-[12px] text-[var(--color-text-muted)]">
{availablePatterns.find((p) => p.id === patternId)?.description} {availablePatterns.find((p) => p.id === patternId)?.description}
</p> </p>
)} )}
@@ -134,16 +134,16 @@ export function NewSessionModal({
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-end gap-2 border-t border-zinc-800 px-5 py-3"> <div className="flex items-center justify-end gap-2 border-t border-[var(--color-border)] px-5 py-3">
<button <button
className="rounded-lg px-4 py-1.5 text-[13px] text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="rounded-lg px-4 py-1.5 text-[13px] text-[var(--color-text-secondary)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={onClose} onClick={onClose}
type="button" type="button"
> >
Cancel Cancel
</button> </button>
<button <button
className="rounded-lg bg-indigo-600 px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-40" className="rounded-lg bg-[var(--color-accent)] px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-[var(--color-accent-sky)] disabled:cursor-not-allowed disabled:opacity-40"
disabled={!canCreate} disabled={!canCreate}
onClick={() => canCreate && onCreate(projectId, patternId)} onClick={() => canCreate && onCreate(projectId, patternId)}
type="button" type="button"
+50 -51
View File
@@ -105,10 +105,10 @@ function InputField({
placeholder?: string; placeholder?: string;
}) { }) {
const baseClasses = const baseClasses =
'w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 placeholder-zinc-600 outline-none transition focus:border-indigo-500/50'; 'w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition-all duration-200 focus:border-[var(--color-accent)]/50';
return ( return (
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">{label}</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
{multiline ? ( {multiline ? (
<textarea <textarea
className={`${baseClasses} min-h-20 resize-y`} className={`${baseClasses} min-h-20 resize-y`}
@@ -242,17 +242,17 @@ export function PatternEditor({
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] pb-3 pl-5 pr-36 pt-3"> <div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] pb-3 pl-5 pr-36 pt-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={onBack} onClick={onBack}
type="button" type="button"
> >
<ChevronLeft className="size-4" /> <ChevronLeft className="size-4" />
</button> </button>
<div> <div>
<h3 className="text-[13px] font-semibold text-zinc-100"> <h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">
{pattern.name || 'Untitled pattern'} {pattern.name || 'Untitled pattern'}
</h3> </h3>
<p className="text-[12px] text-zinc-500"> <p className="text-[12px] text-[var(--color-text-muted)]">
{isBuiltin ? 'Built-in pattern' : 'Custom pattern'} {isBuiltin ? 'Built-in pattern' : 'Custom pattern'}
</p> </p>
</div> </div>
@@ -260,7 +260,7 @@ export function PatternEditor({
<div className="no-drag flex items-center gap-2"> <div className="no-drag flex items-center gap-2">
{!isBuiltin && onDelete && ( {!isBuiltin && onDelete && (
<button <button
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-red-400 transition hover:bg-red-500/10" className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-[var(--color-status-error)] transition-all duration-200 hover:bg-[var(--color-status-error)]/10"
onClick={onDelete} onClick={onDelete}
type="button" type="button"
> >
@@ -269,7 +269,7 @@ export function PatternEditor({
</button> </button>
)} )}
<button <button
className="rounded-lg bg-indigo-600 px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500" className="rounded-lg bg-[var(--color-accent)] px-4 py-1.5 text-[13px] font-medium text-white transition-all duration-200 hover:bg-[var(--color-accent-sky)]"
onClick={onSave} onClick={onSave}
type="button" type="button"
> >
@@ -290,8 +290,8 @@ export function PatternEditor({
<div <div
className={`flex items-start gap-2 rounded-lg px-3 py-2 text-[12px] ${ className={`flex items-start gap-2 rounded-lg px-3 py-2 text-[12px] ${
issue.level === 'error' issue.level === 'error'
? 'bg-red-500/10 text-red-300' ? 'bg-[var(--color-status-error)]/10 text-[var(--color-status-error)]'
: 'bg-amber-500/10 text-amber-300' : 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]'
}`} }`}
key={`${issue.field ?? 'v'}-${i}`} key={`${issue.field ?? 'v'}-${i}`}
> >
@@ -301,7 +301,7 @@ export function PatternEditor({
))} ))}
</div> </div>
) : ( ) : (
<div className="flex items-center gap-2 rounded-lg bg-emerald-500/10 px-3 py-2 text-[12px] text-emerald-300"> <div className="flex items-center gap-2 rounded-lg bg-[var(--color-status-success)]/10 px-3 py-2 text-[12px] text-[var(--color-status-success)]">
<CheckCircle className="size-3.5" /> <CheckCircle className="size-3.5" />
Pattern is valid Pattern is valid
</div> </div>
@@ -310,11 +310,11 @@ export function PatternEditor({
{/* Graph canvas */} {/* Graph canvas */}
<div className="flex items-center justify-between px-5 pt-4"> <div className="flex items-center justify-between px-5 pt-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Topology Topology
</h4> </h4>
<button <button
className="flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-[12px] font-medium text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={addAgent} onClick={addAgent}
type="button" type="button"
> >
@@ -335,11 +335,11 @@ export function PatternEditor({
</div> </div>
{/* Scrollable settings below graph */} {/* Scrollable settings below graph */}
<div className="max-h-[45%] overflow-y-auto border-t border-zinc-800/50 px-5 py-5"> <div className="max-h-[45%] overflow-y-auto border-t border-[var(--color-border-subtle)] px-5 py-5">
<div className="space-y-8"> <div className="space-y-8">
{/* General */} {/* General */}
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
General General
</h4> </h4>
<div className="grid gap-3 sm:grid-cols-2"> <div className="grid gap-3 sm:grid-cols-2">
@@ -360,7 +360,7 @@ export function PatternEditor({
{/* Mode selector */} {/* Mode selector */}
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Orchestration Mode Orchestration Mode
</h4> </h4>
<div className="grid grid-cols-3 gap-2"> <div className="grid grid-cols-3 gap-2">
@@ -372,12 +372,12 @@ export function PatternEditor({
return ( return (
<button <button
className={`flex flex-col rounded-xl border p-2.5 text-left transition ${ className={`flex flex-col rounded-xl border p-2.5 text-left transition-all duration-200 ${
selected selected
? 'border-indigo-500/40 bg-indigo-500/5 ring-1 ring-indigo-500/20' ? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] ring-1 ring-[var(--color-border-glow)]'
: disabled : disabled
? 'cursor-not-allowed border-zinc-800/50 opacity-40' ? 'cursor-not-allowed border-[var(--color-border-subtle)] opacity-40'
: 'border-zinc-800 hover:border-zinc-700 hover:bg-zinc-900/60' : 'border-[var(--color-border)] hover:border-[var(--color-border)] hover:bg-[var(--color-glass)]'
}`} }`}
disabled={disabled} disabled={disabled}
key={mode} key={mode}
@@ -386,17 +386,17 @@ export function PatternEditor({
> >
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Icon <Icon
className={`size-3.5 ${selected ? 'text-indigo-400' : 'text-zinc-500'}`} className={`size-3.5 ${selected ? 'text-[var(--color-text-accent)]' : 'text-[var(--color-text-muted)]'}`}
/> />
<span <span
className={`text-[11px] font-semibold ${ className={`text-[11px] font-semibold ${
selected ? 'text-indigo-200' : 'text-zinc-300' selected ? 'text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
}`} }`}
> >
{info.label} {info.label}
</span> </span>
</div> </div>
<p className="mt-1 text-[10px] leading-snug text-zinc-500"> <p className="mt-1 text-[10px] leading-snug text-[var(--color-text-muted)]">
{info.description} {info.description}
</p> </p>
</button> </button>
@@ -407,12 +407,11 @@ export function PatternEditor({
{/* Approval checkpoints */} {/* Approval checkpoints */}
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Approval Checkpoints Approval Checkpoints
</h4> </h4>
<p className="text-[11px] leading-relaxed text-zinc-600"> <p className="text-[11px] leading-relaxed text-[var(--color-text-muted)]">
Pause the run for human review before risky actions or publishing responses.
</p> </p>
<div className="space-y-3"> <div className="space-y-3">
@@ -426,15 +425,15 @@ export function PatternEditor({
scopedAgentIds={checkpointAgentIds('tool-call')} scopedAgentIds={checkpointAgentIds('tool-call')}
onScopeChange={(agentIds) => setCheckpointAgentScope('tool-call', agentIds)} onScopeChange={(agentIds) => setCheckpointAgentScope('tool-call', agentIds)}
> >
<div className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-zinc-500"> <div className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Auto-approved tools Auto-approved tools
</div> </div>
<p className="mb-3 text-[11px] leading-relaxed text-zinc-600"> <p className="mb-3 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
Tools marked as auto-approved will skip manual review. Tools marked as auto-approved will skip manual review.
Sessions can override these defaults from the Activity panel. Sessions can override these defaults from the Activity panel.
</p> </p>
{approvalTools.length === 0 ? ( {approvalTools.length === 0 ? (
<p className="py-2 text-center text-[11px] text-zinc-600"> <p className="py-2 text-center text-[11px] text-[var(--color-text-muted)]">
No tools available yet. Connect MCP servers or wait for runtime capabilities to load. No tools available yet. Connect MCP servers or wait for runtime capabilities to load.
</p> </p>
) : ( ) : (
@@ -462,9 +461,9 @@ export function PatternEditor({
</div> </div>
{/* Right column: node inspector */} {/* Right column: node inspector */}
<div className="w-[320px] shrink-0 overflow-y-auto border-l border-zinc-800/50 bg-zinc-900/30"> <div className="w-[320px] shrink-0 overflow-y-auto border-l border-[var(--color-border-subtle)] bg-[var(--color-glass)]">
<div className="border-b border-zinc-800/50 px-4 py-3"> <div className="border-b border-[var(--color-border-subtle)] px-4 py-3">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Inspector Inspector
</h4> </h4>
</div> </div>
@@ -518,26 +517,26 @@ function ApprovalCheckpointRow({
} }
return ( return (
<div className="rounded-xl border border-zinc-800 bg-zinc-900/50 p-4"> <div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] p-4">
<button className="flex w-full items-center gap-3 text-left" onClick={() => onToggle(!enabled)} type="button"> <button className="flex w-full items-center gap-3 text-left" onClick={() => onToggle(!enabled)} type="button">
<ShieldCheck className={`size-4 shrink-0 ${enabled ? 'text-indigo-400' : 'text-zinc-600'}`} /> <ShieldCheck className={`size-4 shrink-0 ${enabled ? 'text-[var(--color-text-accent)]' : 'text-[var(--color-text-muted)]'}`} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<span className="text-[12px] font-medium text-zinc-200">{label}</span> <span className="text-[12px] font-medium text-[var(--color-text-primary)]">{label}</span>
<p className="text-[11px] text-zinc-500">{description}</p> <p className="text-[11px] text-[var(--color-text-muted)]">{description}</p>
</div> </div>
<ToggleSwitch enabled={enabled} /> <ToggleSwitch enabled={enabled} />
</button> </button>
{/* Agent scope selector */} {/* Agent scope selector */}
{enabled && agents.length > 1 && ( {enabled && agents.length > 1 && (
<div className="mt-3 border-t border-zinc-800/50 pt-3"> <div className="mt-3 border-t border-[var(--color-border-subtle)] pt-3">
<div className="mb-2 flex items-center gap-2"> <div className="mb-2 flex items-center gap-2">
<span className="text-[11px] font-medium text-zinc-400">Scope</span> <span className="text-[11px] font-medium text-[var(--color-text-secondary)]">Scope</span>
<button <button
className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition ${ className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition-all duration-200 ${
isAllAgents isAllAgents
? 'bg-indigo-500/15 text-indigo-300' ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: 'bg-zinc-800 text-zinc-500 hover:text-zinc-400' : 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
}`} }`}
onClick={() => onScopeChange(undefined)} onClick={() => onScopeChange(undefined)}
type="button" type="button"
@@ -545,10 +544,10 @@ function ApprovalCheckpointRow({
All agents All agents
</button> </button>
<button <button
className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition ${ className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition-all duration-200 ${
!isAllAgents !isAllAgents
? 'bg-indigo-500/15 text-indigo-300' ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: 'bg-zinc-800 text-zinc-500 hover:text-zinc-400' : 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
}`} }`}
onClick={() => onScopeChange([])} onClick={() => onScopeChange([])}
type="button" type="button"
@@ -563,10 +562,10 @@ function ApprovalCheckpointRow({
const isSelected = scopedAgentIds?.includes(agent.id) ?? false; const isSelected = scopedAgentIds?.includes(agent.id) ?? false;
return ( return (
<button <button
className={`rounded-full px-2.5 py-1 text-[11px] font-medium transition ${ className={`rounded-full px-2.5 py-1 text-[11px] font-medium transition-all duration-200 ${
isSelected isSelected
? 'bg-indigo-500/20 text-indigo-300 ring-1 ring-indigo-500/30' ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)] ring-1 ring-[var(--color-border-glow)]'
: 'bg-zinc-800 text-zinc-500 hover:bg-zinc-700 hover:text-zinc-400' : 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]'
}`} }`}
key={agent.id} key={agent.id}
onClick={() => toggleAgentScope(agent.id)} onClick={() => toggleAgentScope(agent.id)}
@@ -583,7 +582,7 @@ function ApprovalCheckpointRow({
{/* Optional additional content (e.g. tool auto-approval list) */} {/* Optional additional content (e.g. tool auto-approval list) */}
{enabled && children && ( {enabled && children && (
<div className="mt-3 border-t border-zinc-800/50 pt-3"> <div className="mt-3 border-t border-[var(--color-border-subtle)] pt-3">
{children} {children}
</div> </div>
)} )}
@@ -620,7 +619,7 @@ function ToolApprovalGroupedList({
{groups.map((group, i) => ( {groups.map((group, i) => (
<div key={group.kind}> <div key={group.kind}>
{showHeaders && ( {showHeaders && (
<div className={`text-[9px] font-semibold uppercase tracking-wider text-zinc-600 ${i > 0 ? 'mt-3' : ''} mb-1`}> <div className={`text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)] ${i > 0 ? 'mt-3' : ''} mb-1`}>
{approvalKindLabels[group.kind]} {approvalKindLabels[group.kind]}
</div> </div>
)} )}
@@ -650,13 +649,13 @@ function ToolApprovalToggleRow({
const detail = tool.description || (tool.providerNames.length > 0 ? tool.providerNames.join(', ') : undefined); const detail = tool.description || (tool.providerNames.length > 0 ? tool.providerNames.join(', ') : undefined);
return ( return (
<button <button
className="flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-left transition hover:bg-zinc-800/60" className="flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]/60"
onClick={onToggle} onClick={onToggle}
type="button" type="button"
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<span className="truncate text-[12px] font-medium text-zinc-300">{tool.label}</span> <span className="truncate text-[12px] font-medium text-[var(--color-text-secondary)]">{tool.label}</span>
{detail && <div className="truncate text-[10px] text-zinc-600">{detail}</div>} {detail && <div className="truncate text-[10px] text-[var(--color-text-muted)]">{detail}</div>}
</div> </div>
<ToggleSwitch enabled={enabled} /> <ToggleSwitch enabled={enabled} />
</button> </button>
@@ -115,16 +115,16 @@ export function ProjectSettingsPanel({
{/* Header */} {/* Header */}
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3"> <div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
<button <button
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={onClose} onClick={onClose}
type="button" type="button"
> >
<ChevronLeft className="size-4" /> <ChevronLeft className="size-4" />
</button> </button>
<h2 className="text-[13px] font-semibold text-zinc-100"> <h2 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">
Project Settings Project Settings
<span className="ml-2 font-normal text-zinc-500">·</span> <span className="ml-2 font-normal text-[var(--color-text-muted)]">·</span>
<span className="ml-2 font-normal text-zinc-400">{project.name}</span> <span className="ml-2 font-normal text-[var(--color-text-secondary)]">{project.name}</span>
</h2> </h2>
</div> </div>
@@ -135,7 +135,7 @@ export function ProjectSettingsPanel({
<div className="space-y-4"> <div className="space-y-4">
{navGroups.map((group) => ( {navGroups.map((group) => (
<div key={group.label}> <div key={group.label}>
<span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-zinc-600"> <span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{group.label} {group.label}
</span> </span>
<div className="space-y-0.5"> <div className="space-y-0.5">
@@ -144,10 +144,10 @@ export function ProjectSettingsPanel({
const badge = sectionBadge(item.id); const badge = sectionBadge(item.id);
return ( return (
<button <button
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition ${ className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition-all duration-200 ${
isActive isActive
? 'bg-zinc-800 font-medium text-zinc-100' ? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-text-primary)]'
: 'text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-300' : 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-secondary)]'
}`} }`}
key={item.id} key={item.id}
onClick={() => { onClick={() => {
@@ -156,7 +156,7 @@ export function ProjectSettingsPanel({
}} }}
type="button" type="button"
> >
<span className={isActive ? 'text-zinc-300' : 'text-zinc-500'}>{item.icon}</span> <span className={isActive ? 'text-[var(--color-text-secondary)]' : 'text-[var(--color-text-muted)]'}>{item.icon}</span>
<span className="flex-1 truncate">{item.label}</span> <span className="flex-1 truncate">{item.label}</span>
{badge} {badge}
</button> </button>
@@ -167,13 +167,13 @@ export function ProjectSettingsPanel({
))} ))}
{/* Danger zone at the bottom */} {/* Danger zone at the bottom */}
<div className="border-t border-zinc-800 pt-3"> <div className="border-t border-[var(--color-border)] pt-3">
<div className="space-y-0.5"> <div className="space-y-0.5">
<button <button
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition ${ className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition-all duration-200 ${
activeSection === 'danger-zone' activeSection === 'danger-zone'
? 'bg-zinc-800 font-medium text-red-400' ? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-status-error)]'
: 'text-zinc-500 hover:bg-zinc-800/50 hover:text-red-400' : 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-status-error)]'
}`} }`}
onClick={() => { onClick={() => {
setActiveSection('danger-zone'); setActiveSection('danger-zone');
@@ -240,7 +240,7 @@ export function ProjectSettingsPanel({
function CountBadge({ count, total }: { count: number; total?: number }) { function CountBadge({ count, total }: { count: number; total?: number }) {
return ( return (
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500"> <span className="rounded-full bg-[var(--color-surface-3)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
{total !== undefined ? `${count}/${total}` : count} {total !== undefined ? `${count}/${total}` : count}
</span> </span>
); );
@@ -248,7 +248,7 @@ function CountBadge({ count, total }: { count: number; total?: number }) {
function PendingBadge({ count }: { count: number }) { function PendingBadge({ count }: { count: number }) {
return ( return (
<span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-medium text-amber-400"> <span className="rounded-full bg-[var(--color-status-warning)]/10 px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-status-warning)]">
{count} new {count} new
</span> </span>
); );
@@ -263,12 +263,12 @@ function OverviewContent({ project }: { project: ProjectRecord }) {
description="Project details and git status." description="Project details and git status."
title="Overview" title="Overview"
/> />
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 px-5 py-4 space-y-3"> <div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] px-5 py-4 space-y-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<FolderOpen className="size-5 shrink-0 text-indigo-400" /> <FolderOpen className="size-5 shrink-0 text-[var(--color-text-accent)]" />
<div className="min-w-0"> <div className="min-w-0">
<div className="text-[13px] font-medium text-zinc-200">{project.name}</div> <div className="text-[13px] font-medium text-[var(--color-text-primary)]">{project.name}</div>
<div className="mt-0.5 truncate text-[12px] text-zinc-500">{project.path}</div> <div className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{project.path}</div>
</div> </div>
</div> </div>
{project.git && <ProjectGitInfo git={project.git} />} {project.git && <ProjectGitInfo git={project.git} />}
@@ -280,7 +280,7 @@ function OverviewContent({ project }: { project: ProjectRecord }) {
function ProjectGitInfo({ git }: { git: ProjectGitContext }) { function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.status === 'not-repository') { if (git.status === 'not-repository') {
return ( return (
<div className="flex items-center gap-2 text-[12px] text-zinc-600"> <div className="flex items-center gap-2 text-[12px] text-[var(--color-text-muted)]">
<GitBranch className="size-3.5" /> <GitBranch className="size-3.5" />
Not a git repository Not a git repository
</div> </div>
@@ -289,7 +289,7 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.status === 'git-missing') { if (git.status === 'git-missing') {
return ( return (
<div className="flex items-center gap-2 text-[12px] text-amber-500/70"> <div className="flex items-center gap-2 text-[12px] text-[var(--color-status-warning)]">
<AlertTriangle className="size-3.5" /> <AlertTriangle className="size-3.5" />
Git is not installed Git is not installed
</div> </div>
@@ -298,7 +298,7 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.status === 'error') { if (git.status === 'error') {
return ( return (
<div className="flex items-center gap-2 text-[12px] text-red-400/70"> <div className="flex items-center gap-2 text-[12px] text-[var(--color-status-error)]">
<AlertTriangle className="size-3.5" /> <AlertTriangle className="size-3.5" />
{git.errorMessage ?? 'Git error'} {git.errorMessage ?? 'Git error'}
</div> </div>
@@ -312,12 +312,12 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.behind) parts.push(`${git.behind} behind`); if (git.behind) parts.push(`${git.behind} behind`);
return ( return (
<div className="flex items-center gap-2 text-[12px] text-zinc-400"> <div className="flex items-center gap-2 text-[12px] text-[var(--color-text-secondary)]">
<GitBranch className="size-3.5 shrink-0" /> <GitBranch className="size-3.5 shrink-0" />
<span>{branchLabel}</span> <span>{branchLabel}</span>
{git.isDirty && <Circle className="size-1.5 shrink-0 fill-amber-500 text-amber-500" />} {git.isDirty && <Circle className="size-1.5 shrink-0 fill-[var(--color-status-warning)] text-[var(--color-status-warning)]" />}
{parts.length > 0 && ( {parts.length > 0 && (
<span className="text-zinc-600">· {parts.join(' · ')}</span> <span className="text-[var(--color-text-muted)]">· {parts.join(' · ')}</span>
)} )}
</div> </div>
); );
@@ -343,7 +343,7 @@ function InstructionsContent({
{instructions.length === 0 ? ( {instructions.length === 0 ? (
<EmptyState> <EmptyState>
No instruction files found. Add a <code className="text-zinc-400">.github/copilot-instructions.md</code> or <code className="text-zinc-400">AGENTS.md</code> file to your project root. No instruction files found. Add a <code className="text-[var(--color-text-secondary)]">.github/copilot-instructions.md</code> or <code className="text-[var(--color-text-secondary)]">AGENTS.md</code> file to your project root.
</EmptyState> </EmptyState>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
@@ -361,28 +361,28 @@ function InstructionCard({ instruction }: { instruction: ProjectInstructionFile
const isLong = instruction.content.length > 300; const isLong = instruction.content.length > 300;
return ( return (
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40"> <div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)]">
<button <button
className="flex w-full items-center gap-3 px-5 py-3.5 text-left transition hover:bg-zinc-900/60" className="flex w-full items-center gap-3 px-5 py-3.5 text-left transition-all duration-200 hover:bg-[var(--color-glass)]"
onClick={() => setExpanded(!expanded)} onClick={() => setExpanded(!expanded)}
type="button" type="button"
> >
<FileCode2 className="size-4 shrink-0 text-indigo-400" /> <FileCode2 className="size-4 shrink-0 text-[var(--color-text-accent)]" />
<span className="flex-1 text-[13px] font-medium text-zinc-200">{instruction.sourcePath}</span> <span className="flex-1 text-[13px] font-medium text-[var(--color-text-primary)]">{instruction.sourcePath}</span>
<ChevronDown <ChevronDown
className={`size-3.5 shrink-0 text-zinc-500 transition-transform ${expanded ? 'rotate-180' : ''}`} className={`size-3.5 shrink-0 text-[var(--color-text-muted)] transition-transform ${expanded ? 'rotate-180' : ''}`}
/> />
</button> </button>
{expanded && ( {expanded && (
<div className="border-t border-zinc-800 px-5 py-4"> <div className="border-t border-[var(--color-border)] px-5 py-4">
<pre className="whitespace-pre-wrap text-[11px] leading-relaxed text-zinc-400"> <pre className="whitespace-pre-wrap text-[11px] leading-relaxed text-[var(--color-text-secondary)]">
{instruction.content} {instruction.content}
</pre> </pre>
</div> </div>
)} )}
{!expanded && ( {!expanded && (
<div className="px-5 pb-3"> <div className="px-5 pb-3">
<p className={`text-[11px] leading-relaxed text-zinc-500 ${isLong ? 'line-clamp-2' : ''}`}> <p className={`text-[11px] leading-relaxed text-[var(--color-text-muted)] ${isLong ? 'line-clamp-2' : ''}`}>
{instruction.content} {instruction.content}
</p> </p>
</div> </div>
@@ -415,12 +415,12 @@ function AgentsContent({
{agents.length === 0 ? ( {agents.length === 0 ? (
<EmptyState> <EmptyState>
No custom agents found. Add <code className="text-zinc-400">.agent.md</code> files to <code className="text-zinc-400">.github/agents/</code> in your project. No custom agents found. Add <code className="text-[var(--color-text-secondary)]">.agent.md</code> files to <code className="text-[var(--color-text-secondary)]">.github/agents/</code> in your project.
</EmptyState> </EmptyState>
) : ( ) : (
<> <>
{agents.length > 1 && ( {agents.length > 1 && (
<div className="mb-3 text-[11px] text-zinc-500"> <div className="mb-3 text-[11px] text-[var(--color-text-muted)]">
{enabledCount} of {agents.length} agent{agents.length === 1 ? '' : 's'} enabled {enabledCount} of {agents.length} agent{agents.length === 1 ? '' : 's'} enabled
</div> </div>
)} )}
@@ -449,26 +449,26 @@ function AgentCard({
return ( return (
<div className={`rounded-xl border px-5 py-4 transition ${ <div className={`rounded-xl border px-5 py-4 transition ${
agent.enabled agent.enabled
? 'border-zinc-800 bg-zinc-900/40' ? 'border-[var(--color-border)] bg-[var(--color-glass)]'
: 'border-zinc-800/50 bg-zinc-900/20 opacity-60' : 'border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]/20 opacity-60'
}`}> }`}>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<Sparkles className={`mt-0.5 size-4 shrink-0 ${agent.enabled ? 'text-amber-400' : 'text-zinc-600'}`} /> <Sparkles className={`mt-0.5 size-4 shrink-0 ${agent.enabled ? 'text-[var(--color-status-warning)]' : 'text-[var(--color-text-muted)]'}`} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[13px] font-medium text-zinc-200"> <span className="text-[13px] font-medium text-[var(--color-text-primary)]">
{agent.displayName ?? agent.name} {agent.displayName ?? agent.name}
</span> </span>
{agent.tools && agent.tools.length > 0 && ( {agent.tools && agent.tools.length > 0 && (
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-500"> <span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
{agent.tools.length} tool{agent.tools.length === 1 ? '' : 's'} {agent.tools.length} tool{agent.tools.length === 1 ? '' : 's'}
</span> </span>
)} )}
</div> </div>
{agent.description && ( {agent.description && (
<p className="mt-1 text-[12px] leading-relaxed text-zinc-500">{agent.description}</p> <p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-muted)]">{agent.description}</p>
)} )}
<p className="mt-1 text-[11px] text-zinc-600">{agent.sourcePath}</p> <p className="mt-1 text-[11px] text-[var(--color-text-muted)]">{agent.sourcePath}</p>
</div> </div>
<button <button
aria-label={agent.enabled ? `Disable ${agent.name}` : `Enable ${agent.name}`} aria-label={agent.enabled ? `Disable ${agent.name}` : `Enable ${agent.name}`}
@@ -504,7 +504,7 @@ function PromptsContent({
{promptFiles.length === 0 ? ( {promptFiles.length === 0 ? (
<EmptyState> <EmptyState>
No prompt files found. Add <code className="text-zinc-400">.prompt.md</code> files to <code className="text-zinc-400">.github/prompts/</code> in your project. No prompt files found. Add <code className="text-[var(--color-text-secondary)]">.prompt.md</code> files to <code className="text-[var(--color-text-secondary)]">.github/prompts/</code> in your project.
</EmptyState> </EmptyState>
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
@@ -519,27 +519,27 @@ function PromptsContent({
function PromptCard({ prompt }: { prompt: ProjectPromptFile }) { function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
return ( return (
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 px-5 py-4"> <div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] px-5 py-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<FileText className="mt-0.5 size-4 shrink-0 text-emerald-400" /> <FileText className="mt-0.5 size-4 shrink-0 text-[var(--color-status-success)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[13px] font-medium text-zinc-200">{prompt.name}</span> <span className="text-[13px] font-medium text-[var(--color-text-primary)]">{prompt.name}</span>
{prompt.variables.length > 0 && ( {prompt.variables.length > 0 && (
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-500"> <span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
{prompt.variables.length} variable{prompt.variables.length === 1 ? '' : 's'} {prompt.variables.length} variable{prompt.variables.length === 1 ? '' : 's'}
</span> </span>
)} )}
</div> </div>
{prompt.description && ( {prompt.description && (
<p className="mt-1 text-[12px] leading-relaxed text-zinc-500">{prompt.description}</p> <p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-muted)]">{prompt.description}</p>
)} )}
{prompt.variables.length > 0 && ( {prompt.variables.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1.5"> <div className="mt-2 flex flex-wrap gap-1.5">
{prompt.variables.map((v) => ( {prompt.variables.map((v) => (
<span <span
key={v.name} key={v.name}
className="rounded-md bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-400" className="rounded-md bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-secondary)]"
title={v.placeholder} title={v.placeholder}
> >
{v.name} {v.name}
@@ -547,7 +547,7 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
))} ))}
</div> </div>
)} )}
<p className="mt-1.5 text-[11px] text-zinc-600">{prompt.sourcePath}</p> <p className="mt-1.5 text-[11px] text-[var(--color-text-muted)]">{prompt.sourcePath}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -607,14 +607,14 @@ function McpServersContent({
{pending.length > 1 && ( {pending.length > 1 && (
<div className="mt-3 flex items-center gap-2"> <div className="mt-3 flex items-center gap-2">
<button <button
className="rounded-lg bg-emerald-500/10 px-3 py-1.5 text-[12px] font-medium text-emerald-400 transition hover:bg-emerald-500/20" className="rounded-lg bg-[var(--color-status-success)]/10 px-3 py-1.5 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/20"
onClick={() => onResolve(pending.map((s) => s.id), 'accept')} onClick={() => onResolve(pending.map((s) => s.id), 'accept')}
type="button" type="button"
> >
Accept all ({pending.length}) Accept all ({pending.length})
</button> </button>
<button <button
className="rounded-lg px-3 py-1.5 text-[12px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="rounded-lg px-3 py-1.5 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
onClick={() => onResolve(pending.map((s) => s.id), 'dismiss')} onClick={() => onResolve(pending.map((s) => s.id), 'dismiss')}
type="button" type="button"
> >
@@ -645,31 +645,31 @@ function DiscoveredServerRow({
: server.url || 'No URL'; : server.url || 'No URL';
const statusBadge = status === 'accepted' const statusBadge = status === 'accepted'
? 'bg-emerald-500/10 text-emerald-400' ? 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]'
: 'bg-amber-500/10 text-amber-400'; : 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
return ( return (
<div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-zinc-800 hover:bg-zinc-900"> <div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]">
<Server className="size-4 shrink-0 text-zinc-600" /> <Server className="size-4 shrink-0 text-[var(--color-text-muted)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="truncate text-[13px] font-medium text-zinc-200">{server.name}</span> <span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{server.name}</span>
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-400"> <span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-secondary)]">
{server.transport} {server.transport}
</span> </span>
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}> <span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
{status} {status}
</span> </span>
</div> </div>
<p className="mt-0.5 truncate text-[12px] text-zinc-500"> <p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">
{detail} {detail}
<span className="ml-2 text-zinc-700">· {server.sourceLabel}</span> <span className="ml-2 text-[var(--color-text-muted)]">· {server.sourceLabel}</span>
</p> </p>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{onAccept && ( {onAccept && (
<button <button
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-emerald-400 transition hover:bg-emerald-500/10" className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/10"
onClick={onAccept} onClick={onAccept}
type="button" type="button"
> >
@@ -678,7 +678,7 @@ function DiscoveredServerRow({
)} )}
{onDismiss && ( {onDismiss && (
<button <button
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
onClick={onDismiss} onClick={onDismiss}
type="button" type="button"
> >
@@ -707,18 +707,18 @@ function DangerZoneContent({
description="Irreversible actions for this project." description="Irreversible actions for this project."
title="Danger Zone" title="Danger Zone"
/> />
<div className="rounded-xl border border-red-500/20 bg-red-500/5 px-5 py-5"> <div className="rounded-xl border border-[var(--color-status-error)]/20 bg-[var(--color-status-error)]/5 px-5 py-5">
<h4 className="text-[13px] font-semibold text-zinc-200">Remove project</h4> <h4 className="text-[13px] font-semibold text-[var(--color-text-primary)]">Remove project</h4>
<p className="mt-1 text-[12px] text-zinc-500"> <p className="mt-1 text-[12px] text-[var(--color-text-muted)]">
Removing a project deletes all its sessions and discovered tooling from Aryx. Removing a project deletes all its sessions and discovered tooling from Aryx.
Your project files on disk are not affected. Your project files on disk are not affected.
</p> </p>
<div className="mt-4 flex items-center gap-3"> <div className="mt-4 flex items-center gap-3">
<button <button
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] font-medium transition ${ className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] font-medium transition-all duration-200 ${
confirmingRemove confirmingRemove
? 'bg-red-600 text-white hover:bg-red-500' ? 'bg-[var(--color-status-error)] text-white hover:bg-[var(--color-status-error)]'
: 'bg-red-500/10 text-red-400 hover:bg-red-500/20' : 'bg-[var(--color-status-error)]/10 text-[var(--color-status-error)] hover:bg-[var(--color-status-error)]/20'
}`} }`}
onClick={onRemove} onClick={onRemove}
type="button" type="button"
@@ -728,7 +728,7 @@ function DangerZoneContent({
</button> </button>
{confirmingRemove && ( {confirmingRemove && (
<button <button
className="rounded-lg px-3 py-1.5 text-[13px] font-medium text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="rounded-lg px-3 py-1.5 text-[13px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={onCancelRemove} onClick={onCancelRemove}
type="button" type="button"
> >
@@ -755,8 +755,8 @@ function SectionHeader({
return ( return (
<div className="mb-4 flex items-center justify-between gap-3"> <div className="mb-4 flex items-center justify-between gap-3">
<div> <div>
<h3 className="text-[13px] font-semibold text-zinc-200">{title}</h3> <h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h3>
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p> <p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
</div> </div>
{children} {children}
</div> </div>
@@ -766,7 +766,7 @@ function SectionHeader({
function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; label?: string }) { function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; label?: string }) {
return ( return (
<button <button
className="flex items-center gap-1.5 rounded-lg bg-zinc-800 px-3 py-1.5 text-[13px] font-medium text-zinc-200 transition hover:bg-zinc-700" className="flex items-center gap-1.5 rounded-lg bg-[var(--color-surface-3)] px-3 py-1.5 text-[13px] font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-surface-3)]"
onClick={onClick} onClick={onClick}
title={label === 'Scan' ? 'Scan for files' : 'Re-scan for changes'} title={label === 'Scan' ? 'Scan for files' : 'Re-scan for changes'}
type="button" type="button"
@@ -779,7 +779,7 @@ function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; lab
function EmptyState({ children }: { children: ReactNode }) { function EmptyState({ children }: { children: ReactNode }) {
return ( return (
<div className="rounded-xl border border-dashed border-zinc-800 bg-zinc-900/20 px-5 py-8 text-center text-[12px] leading-relaxed text-zinc-500"> <div className="rounded-xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface-1)]/20 px-5 py-8 text-center text-[12px] leading-relaxed text-[var(--color-text-muted)]">
{children} {children}
</div> </div>
); );
+3 -3
View File
@@ -51,9 +51,9 @@ const iconComponents: Record<ModelProvider, React.FC<{ className?: string }>> =
}; };
export const providerColors: Record<ModelProvider, string> = { export const providerColors: Record<ModelProvider, string> = {
openai: 'text-emerald-400', openai: 'text-[var(--color-status-success)]',
anthropic: 'text-orange-400', anthropic: 'text-[var(--color-accent-purple)]',
google: 'text-blue-400', google: 'text-[var(--color-status-info)]',
}; };
export function ProviderIcon({ provider, className }: ProviderIconProps) { export function ProviderIcon({ provider, className }: ProviderIconProps) {
+44 -44
View File
@@ -30,21 +30,21 @@ import type { RunTimelineEventRecord, SessionRunRecord } from '@shared/domain/ru
/* ── Mode accent colours (shared with ActivityPanel) ───────── */ /* ── Mode accent colours (shared with ActivityPanel) ───────── */
const modeAccent: Record<OrchestrationMode, { dot: string; ring: string; text: string }> = { const modeAccent: Record<OrchestrationMode, { dot: string; ring: string; text: string }> = {
single: { dot: 'bg-indigo-400', ring: 'ring-indigo-500/30', text: 'text-indigo-400' }, single: { dot: 'bg-[#245CF9]', ring: 'ring-[#245CF9]/30', text: 'text-[#245CF9]' },
sequential: { dot: 'bg-amber-400', ring: 'ring-amber-500/30', text: 'text-amber-400' }, sequential: { dot: 'bg-[var(--color-status-warning)]', ring: 'ring-[var(--color-status-warning)]/30', text: 'text-[var(--color-status-warning)]' },
concurrent: { dot: 'bg-emerald-400', ring: 'ring-emerald-500/30', text: 'text-emerald-400' }, concurrent: { dot: 'bg-[var(--color-status-success)]', ring: 'ring-[var(--color-status-success)]/30', text: 'text-[var(--color-status-success)]' },
handoff: { dot: 'bg-sky-400', ring: 'ring-sky-500/30', text: 'text-sky-400' }, handoff: { dot: 'bg-[var(--color-accent-sky)]', ring: 'ring-[var(--color-accent-sky)]/30', text: 'text-[var(--color-accent-sky)]' },
'group-chat': { dot: 'bg-violet-400', ring: 'ring-violet-500/30', text: 'text-violet-400' }, 'group-chat': { dot: 'bg-[var(--color-accent-purple)]', ring: 'ring-[var(--color-accent-purple)]/30', text: 'text-[var(--color-accent-purple)]' },
magentic: { dot: 'bg-zinc-500', ring: 'ring-zinc-600/30', text: 'text-zinc-500' }, magentic: { dot: 'bg-[var(--color-text-muted)]', ring: 'ring-[var(--color-text-muted)]/30', text: 'text-[var(--color-text-muted)]' },
}; };
/* ── Status badges ─────────────────────────────────────────── */ /* ── Status badges ─────────────────────────────────────────── */
const runStatusStyles: Record<SessionRunRecord['status'], { icon: ReactNode; className: string }> = { const runStatusStyles: Record<SessionRunRecord['status'], { icon: ReactNode; className: string }> = {
running: { icon: <CircleDot className="size-3" />, className: 'text-blue-400' }, running: { icon: <CircleDot className="size-3" />, className: 'text-[var(--color-status-info)]' },
completed: { icon: <CheckCircle2 className="size-3" />, className: 'text-emerald-400' }, completed: { icon: <CheckCircle2 className="size-3" />, className: 'text-[var(--color-status-success)]' },
cancelled: { icon: <XCircle className="size-3" />, className: 'text-zinc-400' }, cancelled: { icon: <XCircle className="size-3" />, className: 'text-[var(--color-text-muted)]' },
error: { icon: <XCircle className="size-3" />, className: 'text-red-400' }, error: { icon: <XCircle className="size-3" />, className: 'text-[var(--color-status-error)]' },
}; };
/* ── Event node icon ───────────────────────────────────────── */ /* ── Event node icon ───────────────────────────────────────── */
@@ -53,23 +53,23 @@ function EventIcon({ kind, status }: { kind: RunTimelineEventRecord['kind']; sta
const base = 'size-3.5'; const base = 'size-3.5';
switch (kind) { switch (kind) {
case 'run-started': case 'run-started':
return <Play className={`${base} text-zinc-500`} />; return <Play className={`${base} text-[var(--color-text-muted)]`} />;
case 'thinking': case 'thinking':
return <Brain className={`${base} ${status === 'running' ? 'text-sky-400 animate-pulse' : 'text-zinc-500'}`} />; return <Brain className={`${base} ${status === 'running' ? 'text-[var(--color-accent-sky)] animate-pulse' : 'text-[var(--color-text-muted)]'}`} />;
case 'handoff': case 'handoff':
return <ArrowRight className={`${base} text-amber-400`} />; return <ArrowRight className={`${base} text-[var(--color-status-warning)]`} />;
case 'tool-call': case 'tool-call':
return <Wrench className={`${base} text-violet-400`} />; return <Wrench className={`${base} text-[var(--color-accent-purple)]`} />;
case 'approval': case 'approval':
return <AlertTriangle className={`${base} ${status === 'running' ? 'text-amber-400 animate-pulse' : status === 'error' ? 'text-red-400' : 'text-emerald-400'}`} />; return <AlertTriangle className={`${base} ${status === 'running' ? 'text-[var(--color-status-warning)] animate-pulse' : status === 'error' ? 'text-[var(--color-status-error)]' : 'text-[var(--color-status-success)]'}`} />;
case 'message': case 'message':
return <MessageSquare className={`${base} ${status === 'running' ? 'text-blue-400 animate-pulse' : status === 'error' ? 'text-red-400' : 'text-emerald-400'}`} />; return <MessageSquare className={`${base} ${status === 'running' ? 'text-[var(--color-status-info)] animate-pulse' : status === 'error' ? 'text-[var(--color-status-error)]' : 'text-[var(--color-status-success)]'}`} />;
case 'run-completed': case 'run-completed':
return <CheckCircle2 className={`${base} text-emerald-400`} />; return <CheckCircle2 className={`${base} text-[var(--color-status-success)]`} />;
case 'run-cancelled': case 'run-cancelled':
return <XCircle className={`${base} text-zinc-400`} />; return <XCircle className={`${base} text-[var(--color-text-muted)]`} />;
case 'run-failed': case 'run-failed':
return <AlertTriangle className={`${base} text-red-400`} />; return <AlertTriangle className={`${base} text-[var(--color-status-error)]`} />;
} }
} }
@@ -92,19 +92,19 @@ function TimelineEventRow({
return ( return (
<button <button
className={`group relative flex w-full gap-2.5 text-left ${terminal ? 'py-1' : 'py-1.5'} ${isClickable ? 'cursor-pointer' : 'cursor-default'}`} className={`group relative flex w-full gap-2.5 text-left transition-all duration-200 ${terminal ? 'py-1' : 'py-1.5'} ${isClickable ? 'cursor-pointer' : 'cursor-default'}`}
disabled={!isClickable} disabled={!isClickable}
onClick={isClickable ? () => onJumpToMessage(event.messageId!) : undefined} onClick={isClickable ? () => onJumpToMessage(event.messageId!) : undefined}
type="button" type="button"
> >
{/* Vertical connector line */} {/* Vertical connector line */}
{!isLast && ( {!isLast && (
<div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-zinc-800" /> <div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-[var(--color-border)]" />
)} )}
{/* Node */} {/* Node */}
<div className="relative z-10 flex shrink-0 items-start pt-0.5"> <div className="relative z-10 flex shrink-0 items-start pt-0.5">
<div className="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-1)]"> <div className={`flex size-[15px] items-center justify-center rounded-full ${event.status === 'running' ? 'brand-gradient-bg' : 'bg-[var(--color-surface-2)]'}`}>
<EventIcon kind={event.kind} status={event.status} /> <EventIcon kind={event.kind} status={event.status} />
</div> </div>
</div> </div>
@@ -112,41 +112,41 @@ function TimelineEventRow({
{/* Content */} {/* Content */}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className={`text-[11px] font-medium ${terminal ? 'text-zinc-600' : 'text-zinc-300'} ${isClickable ? 'group-hover:text-indigo-300' : ''}`}> <span className={`text-[11px] font-medium ${terminal ? 'text-[var(--color-text-muted)]' : 'text-[var(--color-text-secondary)]'} ${isClickable ? 'group-hover:text-[var(--color-text-accent)]' : ''}`}>
{label} {label}
</span> </span>
{/* Approval kind badge */} {/* Approval kind badge */}
{event.kind === 'approval' && event.approvalKind && ( {event.kind === 'approval' && event.approvalKind && (
<span className={`rounded-full px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider ${ <span className={`rounded-full px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider ${
event.status === 'running' event.status === 'running'
? 'bg-amber-500/15 text-amber-400' ? 'bg-[var(--color-status-warning)]/15 text-[var(--color-status-warning)]'
: event.status === 'completed' : event.status === 'completed'
? 'bg-emerald-500/15 text-emerald-400' ? 'bg-[var(--color-status-success)]/15 text-[var(--color-status-success)]'
: 'bg-red-500/15 text-red-400' : 'bg-[var(--color-status-error)]/15 text-[var(--color-status-error)]'
}`}> }`}>
{event.approvalKind === 'final-response' ? 'response' : 'tool'} {event.approvalKind === 'final-response' ? 'response' : 'tool'}
</span> </span>
)} )}
<span className="ml-auto shrink-0 text-[9px] tabular-nums text-zinc-700">{timestamp}</span> <span className="font-mono ml-auto shrink-0 text-[9px] tabular-nums text-[var(--color-text-muted)]">{timestamp}</span>
</div> </div>
{/* Content preview for message events */} {/* Content preview for message events */}
{preview && ( {preview && (
<p className={`mt-0.5 text-[10px] leading-snug text-zinc-600 ${isClickable ? 'group-hover:text-zinc-500' : ''}`}> <p className={`mt-0.5 text-[10px] leading-snug text-[var(--color-text-muted)] ${isClickable ? 'group-hover:text-[var(--color-text-secondary)]' : ''}`}>
{preview} {preview}
</p> </p>
)} )}
{/* Approval detail */} {/* Approval detail */}
{event.kind === 'approval' && event.approvalDetail && ( {event.kind === 'approval' && event.approvalDetail && (
<p className="mt-0.5 text-[10px] leading-snug text-zinc-500"> <p className="mt-0.5 text-[10px] leading-snug text-[var(--color-text-muted)]">
{truncateContent(event.approvalDetail, 120)} {truncateContent(event.approvalDetail, 120)}
</p> </p>
)} )}
{/* Error detail */} {/* Error detail */}
{event.error && ( {event.error && (
<p className="mt-0.5 text-[10px] leading-snug text-red-500/80"> <p className="mt-0.5 text-[10px] leading-snug text-[var(--color-status-error)]/80">
{truncateContent(event.error, 120)} {truncateContent(event.error, 120)}
</p> </p>
)} )}
@@ -169,15 +169,15 @@ function ThinkingGroupRow({
return ( return (
<div className="group relative flex w-full gap-2.5 py-1"> <div className="group relative flex w-full gap-2.5 py-1">
{!isLast && ( {!isLast && (
<div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-zinc-800" /> <div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-[var(--color-border)]" />
)} )}
<div className="relative z-10 flex shrink-0 items-start pt-0.5"> <div className="relative z-10 flex shrink-0 items-start pt-0.5">
<div className="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-1)]"> <div className="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-2)]">
<Brain className="size-3.5 text-zinc-500" /> <Brain className="size-3.5 text-[var(--color-text-muted)]" />
</div> </div>
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<span className="text-[11px] text-zinc-600"> <span className="text-[11px] text-[var(--color-text-muted)]">
{agentName ? `${agentName} thinking` : 'Thinking'} ×{events.length} {agentName ? `${agentName} thinking` : 'Thinking'} ×{events.length}
</span> </span>
</div> </div>
@@ -225,26 +225,26 @@ function RunCard({
); );
return ( return (
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40"> <div className="glass-surface rounded-lg">
{/* Run header */} {/* Run header */}
<button <button
className="flex w-full items-center gap-2 px-3 py-2 text-left transition hover:bg-zinc-800/30" className="flex w-full items-center gap-2 px-3 py-2 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]"
onClick={onToggle} onClick={onToggle}
type="button" type="button"
> >
{expanded {expanded
? <ChevronDown className="size-3 shrink-0 text-zinc-600" /> ? <ChevronDown className="size-3 shrink-0 text-[var(--color-text-muted)]" />
: <ChevronRight className="size-3 shrink-0 text-zinc-600" />} : <ChevronRight className="size-3 shrink-0 text-[var(--color-text-muted)]" />}
<Bot className={`size-3 shrink-0 ${accent.text}`} /> <Bot className={`size-3 shrink-0 ${accent.text}`} />
<span className="min-w-0 flex-1 truncate text-[11px] font-medium text-zinc-300"> <span className="min-w-0 flex-1 truncate text-[11px] font-medium text-[var(--color-text-secondary)]">
{run.patternName} {run.patternName}
</span> </span>
{/* Status */} {/* Status */}
<span className={`flex items-center gap-1 shrink-0 ${statusStyle.className}`}> <span className={`flex items-center gap-1 shrink-0 ${statusStyle.className}`}>
{run.status === 'running' && <span className="size-1.5 animate-pulse rounded-full bg-blue-400" />} {run.status === 'running' && <span className="size-1.5 animate-pulse rounded-full bg-[var(--color-status-info)]" />}
{run.status !== 'running' && statusStyle.icon} {run.status !== 'running' && statusStyle.icon}
<span className="text-[9px] font-medium">{formatRunStatusLabel(run.status)}</span> <span className="text-[9px] font-medium">{formatRunStatusLabel(run.status)}</span>
</span> </span>
@@ -252,13 +252,13 @@ function RunCard({
{/* Expanded timeline */} {/* Expanded timeline */}
{expanded && ( {expanded && (
<div className="border-t border-zinc-800/60 px-3 pb-2 pt-1.5"> <div className="border-t border-[var(--color-border-subtle)] px-3 pb-2 pt-1.5">
{/* Agent badges */} {/* Agent badges */}
{run.agents.length > 1 && ( {run.agents.length > 1 && (
<div className="mb-2 flex flex-wrap gap-1"> <div className="mb-2 flex flex-wrap gap-1">
{run.agents.map((agent) => ( {run.agents.map((agent) => (
<span <span
className="rounded-full bg-zinc-800/80 px-2 py-0.5 text-[9px] font-medium text-zinc-500" className="rounded-full bg-[var(--color-surface-2)] px-2 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]"
key={agent.agentId} key={agent.agentId}
> >
{agent.agentName} {agent.agentName}
@@ -281,7 +281,7 @@ function RunCard({
{/* Duration footer */} {/* Duration footer */}
{duration && ( {duration && (
<div className="mt-1 border-t border-zinc-800/40 pt-1.5 text-[9px] tabular-nums text-zinc-700"> <div className="font-mono mt-1 border-t border-[var(--color-border-subtle)] pt-1.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
Duration: {duration} Duration: {duration}
</div> </div>
)} )}
@@ -295,7 +295,7 @@ function RunCard({
function EmptyTimeline() { function EmptyTimeline() {
return ( return (
<p className="py-4 text-center text-[11px] text-zinc-600"> <p className="py-4 text-center text-[11px] text-[var(--color-text-muted)]">
Send a message to see the run timeline Send a message to see the run timeline
</p> </p>
); );
+59 -59
View File
@@ -93,8 +93,8 @@ const navGroups: NavGroup[] = [
]; ];
function modeBadgeClasses(pattern: PatternDefinition) { function modeBadgeClasses(pattern: PatternDefinition) {
if (pattern.availability === 'unavailable') return 'bg-amber-500/10 text-amber-400'; if (pattern.availability === 'unavailable') return 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
return 'bg-zinc-800 text-zinc-400'; return 'bg-[var(--color-surface-3)] text-[var(--color-text-secondary)]';
} }
export function SettingsPanel({ export function SettingsPanel({
@@ -210,13 +210,13 @@ export function SettingsPanel({
<div className="fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]"> <div className="fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]">
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3"> <div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
<button <button
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={onClose} onClick={onClose}
type="button" type="button"
> >
<ChevronLeft className="size-4" /> <ChevronLeft className="size-4" />
</button> </button>
<h2 className="text-[13px] font-semibold text-zinc-100">Settings</h2> <h2 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">Settings</h2>
</div> </div>
<div className="flex min-h-0 flex-1"> <div className="flex min-h-0 flex-1">
@@ -224,7 +224,7 @@ export function SettingsPanel({
<div className="space-y-4"> <div className="space-y-4">
{navGroups.map((group) => ( {navGroups.map((group) => (
<div key={group.label}> <div key={group.label}>
<span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-zinc-600"> <span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{group.label} {group.label}
</span> </span>
<div className="space-y-0.5"> <div className="space-y-0.5">
@@ -232,16 +232,16 @@ export function SettingsPanel({
const isActive = item.id === activeSection; const isActive = item.id === activeSection;
return ( return (
<button <button
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition ${ className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition-all duration-200 ${
isActive isActive
? 'bg-zinc-800 font-medium text-zinc-100' ? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-text-primary)]'
: 'text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-300' : 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-secondary)]'
}`} }`}
key={item.id} key={item.id}
onClick={() => setActiveSection(item.id)} onClick={() => setActiveSection(item.id)}
type="button" type="button"
> >
<span className={isActive ? 'text-zinc-300' : 'text-zinc-500'}>{item.icon}</span> <span className={isActive ? 'text-[var(--color-text-secondary)]' : 'text-[var(--color-text-muted)]'}>{item.icon}</span>
{item.label} {item.label}
</button> </button>
); );
@@ -322,8 +322,8 @@ function AppearanceSection({
return ( return (
<div> <div>
<div className="mb-1"> <div className="mb-1">
<h3 className="text-[13px] font-semibold text-zinc-200">Appearance</h3> <h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">Appearance</h3>
<p className="mt-0.5 text-[12px] text-zinc-500"> <p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">
Choose how Aryx looks on your device Choose how Aryx looks on your device
</p> </p>
</div> </div>
@@ -333,27 +333,27 @@ function AppearanceSection({
const isSelected = option.value === theme; const isSelected = option.value === theme;
return ( return (
<button <button
className={`flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition ${ className={`flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-all duration-200 ${
isSelected isSelected
? 'border-indigo-500/50 bg-indigo-500/10' ? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)]'
: 'border-[var(--color-border)] hover:border-zinc-600 hover:bg-zinc-800/40' : 'border-[var(--color-border)] hover:border-[var(--color-border)] hover:bg-[var(--color-surface-3)]/40'
}`} }`}
key={option.value} key={option.value}
onClick={() => onSetTheme(option.value)} onClick={() => onSetTheme(option.value)}
type="button" type="button"
> >
<div <div
className={`flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition ${ className={`flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-all duration-200 ${
isSelected ? 'border-indigo-500' : 'border-zinc-600' isSelected ? 'border-[var(--color-accent)]' : 'border-[var(--color-border)]'
}`} }`}
> >
{isSelected && <div className="size-2 rounded-full bg-indigo-500" />} {isSelected && <div className="size-2 rounded-full bg-[var(--color-accent)]" />}
</div> </div>
<div> <div>
<span className={`text-[13px] font-medium ${isSelected ? 'text-zinc-100' : 'text-zinc-300'}`}> <span className={`text-[13px] font-medium ${isSelected ? 'text-[var(--color-text-primary)]' : 'text-[var(--color-text-secondary)]'}`}>
{option.label} {option.label}
</span> </span>
<p className="text-[12px] text-zinc-500">{option.description}</p> <p className="text-[12px] text-[var(--color-text-muted)]">{option.description}</p>
</div> </div>
</button> </button>
); );
@@ -379,12 +379,12 @@ function ConnectionSection({
return ( return (
<div> <div>
<div className="mb-1"> <div className="mb-1">
<h3 className="text-[13px] font-semibold text-zinc-200">GitHub Copilot</h3> <h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">GitHub Copilot</h3>
<p className="mt-0.5 text-[12px] text-zinc-500"> <p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">
Aryx uses your installed GitHub Copilot CLI for AI capabilities Aryx uses your installed GitHub Copilot CLI for AI capabilities
</p> </p>
</div> </div>
<div className="mt-4 rounded-xl border border-[var(--color-border)] bg-zinc-900/30 p-4"> <div className="mt-4 rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] p-4">
<CopilotStatusCard <CopilotStatusCard
connection={connection} connection={connection}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
@@ -418,25 +418,25 @@ function PatternsSection({
<div className="space-y-1"> <div className="space-y-1">
{patterns.map((pattern) => ( {patterns.map((pattern) => (
<button <button
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition hover:border-zinc-800 hover:bg-zinc-900" className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition-all duration-200 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]"
key={pattern.id} key={pattern.id}
onClick={() => onEditPattern(pattern)} onClick={() => onEditPattern(pattern)}
type="button" type="button"
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[13px] font-medium text-zinc-200">{pattern.name}</span> <span className="text-[13px] font-medium text-[var(--color-text-primary)]">{pattern.name}</span>
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide ${modeBadgeClasses(pattern)}`}> <span className={`rounded-full px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide ${modeBadgeClasses(pattern)}`}>
{pattern.mode} {pattern.mode}
</span> </span>
</div> </div>
<p className="mt-0.5 truncate text-[12px] text-zinc-500">{pattern.description}</p> <p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{pattern.description}</p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[12px] text-zinc-600"> <span className="text-[12px] text-[var(--color-text-muted)]">
{pattern.agents.length} agent{pattern.agents.length === 1 ? '' : 's'} {pattern.agents.length} agent{pattern.agents.length === 1 ? '' : 's'}
</span> </span>
<ChevronRight className="size-4 text-zinc-700 transition group-hover:text-zinc-500" /> <ChevronRight className="size-4 text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-muted)]" />
</div> </div>
</button> </button>
))} ))}
@@ -537,8 +537,8 @@ function SectionHeader({
return ( return (
<div className="mb-4 flex items-center justify-between gap-3"> <div className="mb-4 flex items-center justify-between gap-3">
<div> <div>
<h3 className="text-[13px] font-semibold text-zinc-200">{title}</h3> <h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h3>
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p> <p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
</div> </div>
{children} {children}
</div> </div>
@@ -548,7 +548,7 @@ function SectionHeader({
function SectionAction({ label, onClick }: { label: string; onClick: () => void }) { function SectionAction({ label, onClick }: { label: string; onClick: () => void }) {
return ( return (
<button <button
className="flex items-center gap-1.5 rounded-lg bg-zinc-800 px-3 py-1.5 text-[13px] font-medium text-zinc-200 transition hover:bg-zinc-700" className="flex items-center gap-1.5 rounded-lg bg-[var(--color-surface-3)] px-3 py-1.5 text-[13px] font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-surface-3)]"
onClick={onClick} onClick={onClick}
type="button" type="button"
> >
@@ -571,27 +571,27 @@ function ToolingListButton({
}) { }) {
return ( return (
<button <button
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition hover:border-zinc-800 hover:bg-zinc-900" className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition-all duration-200 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]"
onClick={onClick} onClick={onClick}
type="button" type="button"
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="truncate text-[13px] font-medium text-zinc-200">{label}</span> <span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{label}</span>
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-400"> <span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-secondary)]">
{meta} {meta}
</span> </span>
</div> </div>
<p className="mt-0.5 truncate text-[12px] text-zinc-500">{detail}</p> <p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{detail}</p>
</div> </div>
<ChevronRight className="size-4 text-zinc-700 transition group-hover:text-zinc-500" /> <ChevronRight className="size-4 text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-muted)]" />
</button> </button>
); );
} }
function EmptyState({ children }: { children: ReactNode }) { function EmptyState({ children }: { children: ReactNode }) {
return ( return (
<div className="rounded-xl border border-dashed border-zinc-800 bg-zinc-900/20 px-5 py-8 text-center text-[12px] leading-relaxed text-zinc-500"> <div className="rounded-xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface-1)]/20 px-5 py-8 text-center text-[12px] leading-relaxed text-[var(--color-text-muted)]">
{children} {children}
</div> </div>
); );
@@ -648,8 +648,8 @@ function DiscoveredSubSection({
<div className="mb-4"> <div className="mb-4">
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<div> <div>
<span className="text-[12px] font-medium text-zinc-300">{label}</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
<p className="text-[11px] text-zinc-600">{description}</p> <p className="text-[11px] text-[var(--color-text-muted)]">{description}</p>
</div> </div>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
@@ -692,30 +692,30 @@ function DiscoveredServerRow({
: server.url || 'No URL'; : server.url || 'No URL';
const statusBadge = status === 'accepted' const statusBadge = status === 'accepted'
? 'bg-emerald-500/10 text-emerald-400' ? 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]'
: 'bg-amber-500/10 text-amber-400'; : 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
return ( return (
<div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-zinc-800 hover:bg-zinc-900"> <div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="truncate text-[13px] font-medium text-zinc-200">{server.name}</span> <span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{server.name}</span>
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-400"> <span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-secondary)]">
{server.transport} {server.transport}
</span> </span>
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}> <span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
{status} {status}
</span> </span>
</div> </div>
<p className="mt-0.5 truncate text-[12px] text-zinc-500"> <p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">
{detail} {detail}
<span className="ml-2 text-zinc-700">· {server.sourceLabel}</span> <span className="ml-2 text-[var(--color-text-muted)]">· {server.sourceLabel}</span>
</p> </p>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{onAccept && ( {onAccept && (
<button <button
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-emerald-400 transition hover:bg-emerald-500/10" className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/10"
onClick={onAccept} onClick={onAccept}
type="button" type="button"
> >
@@ -724,7 +724,7 @@ function DiscoveredServerRow({
)} )}
{onDismiss && ( {onDismiss && (
<button <button
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
onClick={onDismiss} onClick={onDismiss}
type="button" type="button"
> >
@@ -772,12 +772,12 @@ function TroubleshootingSection({
/> />
</div> </div>
<div className="mt-8 rounded-xl border border-red-500/20 bg-red-500/5 p-5"> <div className="mt-8 rounded-xl border border-[var(--color-status-error)]/20 bg-[var(--color-status-error)]/5 p-5">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<TriangleAlert className="mt-0.5 size-4 shrink-0 text-red-400" /> <TriangleAlert className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h4 className="text-[13px] font-semibold text-red-300">Reset Local Workspace</h4> <h4 className="text-[13px] font-semibold text-[var(--color-status-error)]">Reset Local Workspace</h4>
<p className="mt-1 text-[12px] leading-relaxed text-zinc-400"> <p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-secondary)]">
Restore Aryx to its initial state. This permanently removes all sessions, custom patterns, Restore Aryx to its initial state. This permanently removes all sessions, custom patterns,
MCP server definitions, LSP profiles, and scratchpad contents. Your GitHub Copilot sign-in MCP server definitions, LSP profiles, and scratchpad contents. Your GitHub Copilot sign-in
is not affected. is not affected.
@@ -785,7 +785,7 @@ function TroubleshootingSection({
{!confirmingReset ? ( {!confirmingReset ? (
<button <button
className="mt-3 rounded-lg border border-red-500/30 bg-red-500/10 px-3.5 py-1.5 text-[13px] font-medium text-red-300 transition hover:border-red-500/50 hover:bg-red-500/20" className="mt-3 rounded-lg border border-[var(--color-status-error)]/30 bg-[var(--color-status-error)]/10 px-3.5 py-1.5 text-[13px] font-medium text-[var(--color-status-error)] transition-all duration-200 hover:border-[var(--color-status-error)]/50 hover:bg-[var(--color-status-error)]/20"
onClick={() => setConfirmingReset(true)} onClick={() => setConfirmingReset(true)}
type="button" type="button"
> >
@@ -794,7 +794,7 @@ function TroubleshootingSection({
) : ( ) : (
<div className="mt-3 flex items-center gap-2"> <div className="mt-3 flex items-center gap-2">
<button <button
className="rounded-lg bg-red-600 px-3.5 py-1.5 text-[13px] font-medium text-white transition hover:bg-red-500 disabled:opacity-50" className="rounded-lg bg-[var(--color-status-error)] px-3.5 py-1.5 text-[13px] font-medium text-white transition-all duration-200 hover:bg-[var(--color-status-error)] disabled:opacity-50"
disabled={isResetting} disabled={isResetting}
onClick={() => void handleReset()} onClick={() => void handleReset()}
type="button" type="button"
@@ -802,7 +802,7 @@ function TroubleshootingSection({
{isResetting ? 'Resetting…' : 'Confirm reset'} {isResetting ? 'Resetting…' : 'Confirm reset'}
</button> </button>
<button <button
className="rounded-lg border border-[var(--color-border)] px-3.5 py-1.5 text-[13px] font-medium text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="rounded-lg border border-[var(--color-border)] px-3.5 py-1.5 text-[13px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
disabled={isResetting} disabled={isResetting}
onClick={() => setConfirmingReset(false)} onClick={() => setConfirmingReset(false)}
type="button" type="button"
@@ -831,16 +831,16 @@ function TroubleshootingAction({
}) { }) {
return ( return (
<button <button
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition hover:border-zinc-800 hover:bg-zinc-900" className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition-all duration-200 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]"
onClick={onClick} onClick={onClick}
type="button" type="button"
> >
<span className="text-zinc-500 transition group-hover:text-zinc-300">{icon}</span> <span className="text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-secondary)]">{icon}</span>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<span className="text-[13px] font-medium text-zinc-200">{label}</span> <span className="text-[13px] font-medium text-[var(--color-text-primary)]">{label}</span>
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p> <p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
</div> </div>
<ChevronRight className="size-4 text-zinc-700 transition group-hover:text-zinc-500" /> <ChevronRight className="size-4 text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-muted)]" />
</button> </button>
); );
} }
+62 -62
View File
@@ -54,12 +54,12 @@ interface SidebarProps {
/* ── Mode icon + accent colour mapping ─────────────────────── */ /* ── Mode icon + accent colour mapping ─────────────────────── */
const modeVisuals: Record<OrchestrationMode, { icon: LucideIcon; color: string }> = { const modeVisuals: Record<OrchestrationMode, { icon: LucideIcon; color: string }> = {
single: { icon: MessageSquare, color: 'text-indigo-400' }, single: { icon: MessageSquare, color: 'text-[#245CF9]' },
sequential: { icon: ListOrdered, color: 'text-amber-400' }, sequential: { icon: ListOrdered, color: 'text-[var(--color-status-warning)]' },
concurrent: { icon: GitFork, color: 'text-emerald-400' }, concurrent: { icon: GitFork, color: 'text-[var(--color-status-success)]' },
handoff: { icon: ArrowLeftRight, color: 'text-sky-400' }, handoff: { icon: ArrowLeftRight, color: 'text-[var(--color-accent-sky)]' },
'group-chat': { icon: Users, color: 'text-violet-400' }, 'group-chat': { icon: Users, color: 'text-[var(--color-accent-purple)]' },
magentic: { icon: Lock, color: 'text-zinc-500' }, magentic: { icon: Lock, color: 'text-[var(--color-text-muted)]' },
}; };
/* ── Relative time helper ──────────────────────────────────── */ /* ── Relative time helper ──────────────────────────────────── */
@@ -83,7 +83,7 @@ function relativeTime(iso: string): string {
function GitContextBadge({ git }: { git: ProjectGitContext }) { function GitContextBadge({ git }: { git: ProjectGitContext }) {
if (git.status === 'not-repository') { if (git.status === 'not-repository') {
return ( return (
<span className="text-[10px] text-zinc-600" title="Not a git repository"> <span className="text-[10px] text-[var(--color-text-muted)]" title="Not a git repository">
no repo no repo
</span> </span>
); );
@@ -117,7 +117,7 @@ function GitContextBadge({ git }: { git: ProjectGitContext }) {
if (git.behind) parts.push(`${git.behind}`); if (git.behind) parts.push(`${git.behind}`);
return ( return (
<span className="flex items-center gap-1 text-[10px] text-zinc-500" title={parts.join(' · ') || branchLabel}> <span className="flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]" title={parts.join(' · ') || branchLabel}>
<GitBranch className="size-2.5 shrink-0" /> <GitBranch className="size-2.5 shrink-0" />
<span className="max-w-[80px] truncate">{branchLabel}</span> <span className="max-w-[80px] truncate">{branchLabel}</span>
{git.isDirty && <Circle className="size-1.5 shrink-0 fill-amber-500 text-amber-500" />} {git.isDirty && <Circle className="size-1.5 shrink-0 fill-amber-500 text-amber-500" />}
@@ -140,7 +140,7 @@ function ActionMenuItem({
}) { }) {
return ( return (
<button <button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-[12px] transition hover:bg-zinc-800 ${className ?? 'text-zinc-300'}`} className={`flex w-full items-center gap-2 px-3 py-1.5 text-[12px] transition-all duration-150 hover:bg-[var(--color-surface-2)] ${className ?? 'text-[var(--color-text-primary)]'}`}
onClick={onClick} onClick={onClick}
role="menuitem" role="menuitem"
type="button" type="button"
@@ -213,10 +213,10 @@ function SessionItem({
return ( return (
<div <div
className={`group relative flex w-full cursor-pointer items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-150 ${ className={`group relative flex w-full cursor-pointer items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-200 ${
isActive isActive
? 'bg-indigo-500/10 ring-1 ring-indigo-500/25' ? 'bg-[var(--color-accent-muted)] ring-1 ring-[var(--color-border-glow)]'
: 'hover:bg-zinc-800/60' : 'hover:bg-[var(--color-surface-2)]/60'
} ${isRunning ? 'sidebar-running' : ''} ${session.isArchived ? 'opacity-50' : ''}`} } ${isRunning ? 'sidebar-running' : ''} ${session.isArchived ? 'opacity-50' : ''}`}
onClick={isRenaming ? undefined : onSelect} onClick={isRenaming ? undefined : onSelect}
role="button" role="button"
@@ -225,19 +225,19 @@ function SessionItem({
> >
{/* Running/approval left accent bar */} {/* Running/approval left accent bar */}
{isRunning && !hasPendingApproval && ( {isRunning && !hasPendingApproval && (
<span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full bg-blue-400 sidebar-pulse" /> <span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full accent-flow" />
)} )}
{hasPendingApproval && ( {hasPendingApproval && (
<span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full bg-amber-400" /> <span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full bg-[var(--color-status-warning)]" />
)} )}
{/* Mode icon */} {/* Mode icon */}
<span <span
className={`mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-md ${ className={`mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-md ${
isActive ? 'bg-indigo-500/15' : 'bg-zinc-800/80' isActive ? 'bg-[var(--color-accent-muted)]' : 'bg-[var(--color-surface-2)]'
}`} }`}
> >
<ModeIcon className={`size-3.5 ${isActive ? 'text-indigo-400' : visual.color}`} /> <ModeIcon className={`size-3.5 ${isActive ? 'text-[var(--color-accent)]' : visual.color}`} />
</span> </span>
{/* Content */} {/* Content */}
@@ -247,7 +247,7 @@ function SessionItem({
{isRenaming ? ( {isRenaming ? (
<input <input
ref={inputRef} ref={inputRef}
className="w-full rounded bg-zinc-800 px-1.5 py-0.5 text-[13px] font-medium text-zinc-100 outline-none ring-1 ring-indigo-500/50" className="w-full rounded bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[13px] font-medium text-[var(--color-text-primary)] outline-none ring-1 ring-[var(--color-border-glow)]"
value={renameText} value={renameText}
onChange={(e) => setRenameText(e.target.value)} onChange={(e) => setRenameText(e.target.value)}
onKeyDown={handleRenameKeyDown} onKeyDown={handleRenameKeyDown}
@@ -257,7 +257,7 @@ function SessionItem({
) : ( ) : (
<span <span
className={`truncate text-[13px] font-medium leading-tight ${ className={`truncate text-[13px] font-medium leading-tight ${
isActive ? 'text-indigo-100' : 'text-zinc-200 group-hover:text-zinc-100' isActive ? 'text-[var(--color-text-primary)]' : 'text-[var(--color-text-primary)] group-hover:text-[var(--color-text-primary)]'
}`} }`}
> >
{session.title} {session.title}
@@ -267,36 +267,36 @@ function SessionItem({
<div className="mt-1 flex items-center gap-2"> <div className="mt-1 flex items-center gap-2">
{agentCount > 1 && ( {agentCount > 1 && (
<span className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500"> <span className="inline-flex items-center gap-0.5 text-[10px] text-[var(--color-text-muted)]">
<Users className="size-2.5" /> <Users className="size-2.5" />
{agentCount} {agentCount}
</span> </span>
)} )}
{isRunning && !hasPendingApproval && ( {isRunning && !hasPendingApproval && (
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-blue-400"> <span className="inline-flex items-center gap-1 text-[10px] font-medium text-[var(--color-accent-sky)]">
<span className="size-1.5 rounded-full bg-blue-400 sidebar-pulse" /> <span className="size-1.5 rounded-full bg-[var(--color-accent-sky)] sidebar-pulse" />
Running Running
</span> </span>
)} )}
{hasPendingApproval && ( {hasPendingApproval && (
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-amber-400"> <span className="inline-flex items-center gap-1 text-[10px] font-medium text-[var(--color-status-warning)]">
<span className="size-1.5 rounded-full bg-amber-400 animate-pulse" /> <span className="size-1.5 rounded-full bg-[var(--color-status-warning)] animate-pulse" />
Awaiting approval{queuedCount > 0 && ` (+${queuedCount})`} Awaiting approval{queuedCount > 0 && ` (+${queuedCount})`}
</span> </span>
)} )}
{isError && ( {isError && (
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-red-400"> <span className="inline-flex items-center gap-1 text-[10px] font-medium text-[var(--color-status-error)]">
<span className="size-1.5 rounded-full bg-red-400" /> <span className="size-1.5 rounded-full bg-[var(--color-status-error)]" />
Error Error
</span> </span>
)} )}
{session.isArchived && ( {session.isArchived && (
<span className="inline-flex items-center gap-1 text-[10px] text-zinc-600"> <span className="inline-flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]">
<Archive className="size-2.5" /> <Archive className="size-2.5" />
Archived Archived
</span> </span>
)} )}
<span className="ml-auto text-[10px] text-zinc-600 group-hover:text-zinc-500"> <span className="ml-auto text-[10px] text-[var(--color-text-muted)] group-hover:text-[var(--color-text-secondary)]">
{relativeTime(session.updatedAt)} {relativeTime(session.updatedAt)}
</span> </span>
</div> </div>
@@ -305,7 +305,7 @@ function SessionItem({
{/* Actions button (hidden during rename) */} {/* Actions button (hidden during rename) */}
{!isRenaming && ( {!isRenaming && (
<button <button
className="absolute right-1.5 top-1.5 flex size-6 items-center justify-center rounded-md text-zinc-600 opacity-0 transition hover:bg-zinc-700 hover:text-zinc-300 group-hover:opacity-100" className="absolute right-1.5 top-1.5 flex size-6 items-center justify-center rounded-md text-[var(--color-text-muted)] opacity-0 transition-all duration-150 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] group-hover:opacity-100"
onClick={(e) => { e.stopPropagation(); onOpenMenu(e); }} onClick={(e) => { e.stopPropagation(); onOpenMenu(e); }}
type="button" type="button"
> >
@@ -376,19 +376,19 @@ function ProjectGroup({
return ( return (
<div> <div>
<button <button
className="group flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-[13px] font-semibold text-zinc-400 transition hover:bg-zinc-800/40 hover:text-zinc-200" className="group flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-[13px] font-semibold text-[var(--color-text-secondary)] transition-all duration-150 hover:bg-[var(--color-surface-2)]/40 hover:text-[var(--color-text-primary)]"
onClick={() => setExpanded(!expanded)} onClick={() => setExpanded(!expanded)}
type="button" type="button"
> >
{expanded ? ( {expanded ? (
<ChevronDown className="size-3 shrink-0 text-zinc-500" /> <ChevronDown className="size-3 shrink-0 text-[var(--color-text-muted)]" />
) : ( ) : (
<ChevronRight className="size-3 shrink-0 text-zinc-500" /> <ChevronRight className="size-3 shrink-0 text-[var(--color-text-muted)]" />
)} )}
{isScratchpad ? ( {isScratchpad ? (
<MessageSquare className="size-3.5 shrink-0 text-zinc-500 transition group-hover:text-indigo-400" /> <MessageSquare className="size-3.5 shrink-0 text-[var(--color-text-muted)] transition group-hover:text-[var(--color-accent)]" />
) : ( ) : (
<FolderOpen className="size-3.5 shrink-0 text-zinc-500 transition group-hover:text-indigo-400" /> <FolderOpen className="size-3.5 shrink-0 text-[var(--color-text-muted)] transition group-hover:text-[var(--color-accent)]" />
)} )}
<span className="truncate">{project.name}</span> <span className="truncate">{project.name}</span>
@@ -399,7 +399,7 @@ function ProjectGroup({
<div className="ml-auto flex items-center gap-1.5"> <div className="ml-auto flex items-center gap-1.5">
{!isScratchpad && onOpenProjectSettings && ( {!isScratchpad && onOpenProjectSettings && (
<span <span
className="flex size-5 items-center justify-center rounded text-zinc-600 opacity-0 transition hover:bg-zinc-700 hover:text-zinc-300 group-hover:opacity-100" className="flex size-5 items-center justify-center rounded text-[var(--color-text-muted)] opacity-0 transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] group-hover:opacity-100"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onOpenProjectSettings(project.id); onOpenProjectSettings(project.id);
@@ -412,7 +412,7 @@ function ProjectGroup({
)} )}
{!isScratchpad && onRefreshGitContext && ( {!isScratchpad && onRefreshGitContext && (
<span <span
className="flex size-5 items-center justify-center rounded text-zinc-600 opacity-0 transition hover:bg-zinc-700 hover:text-zinc-300 group-hover:opacity-100" className="flex size-5 items-center justify-center rounded text-[var(--color-text-muted)] opacity-0 transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] group-hover:opacity-100"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onRefreshGitContext(project.id); onRefreshGitContext(project.id);
@@ -424,8 +424,8 @@ function ProjectGroup({
</span> </span>
)} )}
{runningCount > 0 && ( {runningCount > 0 && (
<span className="flex items-center gap-1 rounded-full bg-blue-500/10 px-1.5 py-0.5 text-[10px] font-medium text-blue-400"> <span className="flex items-center gap-1 rounded-full bg-[var(--color-accent-sky)]/10 px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-accent-sky)]">
<span className="size-1.5 rounded-full bg-blue-400 sidebar-pulse" /> <span className="size-1.5 rounded-full bg-[var(--color-accent-sky)] sidebar-pulse" />
{runningCount} {runningCount}
</span> </span>
)} )}
@@ -442,14 +442,14 @@ function ProjectGroup({
{pendingDiscoveryCount} new {pendingDiscoveryCount} new
</span> </span>
)} )}
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500"> <span className="rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
{visibleSessions.length} {visibleSessions.length}
</span> </span>
</div> </div>
</button> </button>
{expanded && ( {expanded && (
<div className="ml-2 mt-0.5 space-y-0.5 border-l border-zinc-800/60 pl-2"> <div className="ml-2 mt-0.5 space-y-0.5 border-l border-[var(--color-border-subtle)] pl-2">
{visibleSessions.length > 0 && {visibleSessions.length > 0 &&
visibleSessions.map((session) => ( visibleSessions.map((session) => (
<SessionItem <SessionItem
@@ -466,7 +466,7 @@ function ProjectGroup({
))} ))}
{onNewSession ? ( {onNewSession ? (
<button <button
className="flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-zinc-700/60 bg-zinc-800/20 px-2.5 py-1.5 text-[12px] font-medium text-zinc-500 transition hover:border-indigo-500/40 hover:bg-indigo-500/5 hover:text-indigo-300" className="flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-[var(--color-border)] bg-[var(--color-surface-1)]/40 px-2.5 py-1.5 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:border-[var(--color-border-glow)] hover:bg-[var(--color-accent-muted)] hover:text-[var(--color-accent)]"
onClick={onNewSession} onClick={onNewSession}
type="button" type="button"
> >
@@ -475,7 +475,7 @@ function ProjectGroup({
</button> </button>
) : ( ) : (
visibleSessions.length === 0 && ( visibleSessions.length === 0 && (
<div className="px-3 py-3 text-center text-[12px] text-zinc-600"> <div className="px-3 py-3 text-center text-[12px] text-[var(--color-text-muted)]">
{isScratchpad ? 'No scratchpad chats yet' : 'No sessions yet'} {isScratchpad ? 'No scratchpad chats yet' : 'No sessions yet'}
</div> </div>
) )
@@ -562,19 +562,19 @@ export function Sidebar({
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
{/* Header — extra top padding clears the title bar overlay zone */} {/* Header — extra top padding clears the title bar overlay zone */}
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] px-4 pb-3 pt-3"> <div className="drag-region flex items-center justify-between border-b border-[var(--color-border-subtle)] px-4 pb-3 pt-3">
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<img alt="aryx" className="size-8 rounded-xl" src={appIconUrl} /> <img alt="aryx" className="size-8 rounded-xl" src={appIconUrl} />
<div> <div>
<span className="text-sm font-semibold text-zinc-100">aryx</span> <span className="font-display text-sm font-semibold text-[var(--color-text-primary)]">aryx</span>
<span className="ml-1.5 rounded bg-zinc-800 px-1 py-0.5 text-[9px] font-medium text-zinc-500"> <span className="ml-1.5 rounded bg-[var(--color-surface-2)] px-1 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]">
ALPHA ALPHA
</span> </span>
</div> </div>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition-all duration-150 hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text-primary)]"
onClick={onOpenSettings} onClick={onOpenSettings}
title="Settings" title="Settings"
type="button" type="button"
@@ -587,16 +587,16 @@ export function Sidebar({
{/* Search + Filters */} {/* Search + Filters */}
<div className="space-y-2 px-3 pt-3 pb-1"> <div className="space-y-2 px-3 pt-3 pb-1">
<div className="relative"> <div className="relative">
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-zinc-500" /> <Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-[var(--color-text-muted)]" />
<input <input
className="w-full rounded-lg border border-zinc-800 bg-zinc-900/60 py-1.5 pl-8 pr-8 text-[12px] text-zinc-200 placeholder-zinc-600 outline-none transition focus:border-zinc-700 focus:bg-zinc-900" className="w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-0)]/60 py-1.5 pl-8 pr-8 text-[12px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition-all duration-200 focus:border-[var(--color-border-glow)] focus:bg-[var(--color-surface-0)] focus:shadow-[0_0_12px_rgba(36,92,249,0.06)]"
placeholder="Search sessions…" placeholder="Search sessions…"
value={searchText} value={searchText}
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
/> />
{searchText && ( {searchText && (
<button <button
className="absolute right-2 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300" className="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]"
onClick={() => setSearchText('')} onClick={() => setSearchText('')}
type="button" type="button"
> >
@@ -615,11 +615,11 @@ export function Sidebar({
{isQueryActive ? ( {isQueryActive ? (
/* ── Flat search / filter results ──────────────────────── */ /* ── Flat search / filter results ──────────────────────── */
<div className="space-y-1"> <div className="space-y-1">
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-zinc-600"> <div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-muted)]">
Results ({queryResults.length}) Results ({queryResults.length})
</div> </div>
{queryResults.length === 0 ? ( {queryResults.length === 0 ? (
<div className="px-3 py-6 text-center text-[12px] text-zinc-600"> <div className="px-3 py-6 text-center text-[12px] text-[var(--color-text-muted)]">
No sessions match your search No sessions match your search
</div> </div>
) : ( ) : (
@@ -643,7 +643,7 @@ export function Sidebar({
<div className="space-y-3"> <div className="space-y-3">
{scratchpadProject && ( {scratchpadProject && (
<div className="space-y-1"> <div className="space-y-1">
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-zinc-600"> <div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-muted)]">
Scratchpad Scratchpad
</div> </div>
<ProjectGroup <ProjectGroup
@@ -666,21 +666,21 @@ export function Sidebar({
{userProjects.length === 0 ? ( {userProjects.length === 0 ? (
<div className="flex flex-col items-center gap-4 px-4 py-8 text-center"> <div className="flex flex-col items-center gap-4 px-4 py-8 text-center">
<div className="relative"> <div className="relative">
<div className="flex size-14 items-center justify-center rounded-2xl bg-zinc-800/50 ring-1 ring-zinc-700/50"> <div className="flex size-14 items-center justify-center rounded-2xl bg-[var(--color-surface-2)] ring-1 ring-[var(--color-border)]">
<FolderOpen className="size-7 text-zinc-600" /> <FolderOpen className="size-7 text-[var(--color-text-muted)]" />
</div> </div>
<div className="absolute -bottom-1 -right-1 flex size-6 items-center justify-center rounded-full bg-indigo-600 ring-2 ring-[var(--color-surface-1)]"> <div className="absolute -bottom-1 -right-1 flex size-6 items-center justify-center rounded-full brand-gradient-bg ring-2 ring-[var(--color-surface-1)]">
<Plus className="size-3 text-white" /> <Plus className="size-3 text-white" />
</div> </div>
</div> </div>
<div> <div>
<p className="text-[13px] font-medium text-zinc-300">No projects yet</p> <p className="text-[13px] font-medium text-[var(--color-text-primary)]">No projects yet</p>
<p className="mt-1 text-[12px] leading-relaxed text-zinc-500"> <p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-muted)]">
Use Scratchpad for ad-hoc chat or add a repo<br />to work against project files Use Scratchpad for ad-hoc chat or add a repo<br />to work against project files
</p> </p>
</div> </div>
<button <button
className="rounded-lg bg-indigo-600 px-4 py-2 text-[13px] font-medium text-white transition hover:bg-indigo-500" className="rounded-lg brand-gradient-bg px-4 py-2 text-[13px] font-medium text-white shadow-[0_2px_12px_rgba(36,92,249,0.25)] transition-all duration-200 hover:shadow-[0_4px_20px_rgba(36,92,249,0.35)]"
onClick={onAddProject} onClick={onAddProject}
type="button" type="button"
> >
@@ -689,7 +689,7 @@ export function Sidebar({
</div> </div>
) : ( ) : (
<div className="space-y-1"> <div className="space-y-1">
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-zinc-600"> <div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-muted)]">
Projects Projects
</div> </div>
{userProjects.map((project) => ( {userProjects.map((project) => (
@@ -717,9 +717,9 @@ export function Sidebar({
{/* Footer */} {/* Footer */}
{userProjects.length > 0 && ( {userProjects.length > 0 && (
<div className="border-t border-[var(--color-border)] px-3 py-2"> <div className="border-t border-[var(--color-border-subtle)] px-3 py-2">
<button <button
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-[12px] text-zinc-500 transition hover:bg-zinc-800/60 hover:text-zinc-300" className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-[12px] text-[var(--color-text-muted)] transition-all duration-150 hover:bg-[var(--color-surface-2)]/60 hover:text-[var(--color-text-primary)]"
onClick={onAddProject} onClick={onAddProject}
type="button" type="button"
> >
@@ -734,7 +734,7 @@ export function Sidebar({
<> <>
<div className="fixed inset-0 z-40" onClick={closeMenu} onKeyDown={(e) => { if (e.key === 'Escape') closeMenu(); }} /> <div className="fixed inset-0 z-40" onClick={closeMenu} onKeyDown={(e) => { if (e.key === 'Escape') closeMenu(); }} />
<div <div
className="fixed z-50 w-40 rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-xl" className="fixed z-50 w-40 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-[0_8px_32px_rgba(0,0,0,0.4)]"
role="menu" role="menu"
style={{ top: menuState.top, left: menuState.left }} style={{ top: menuState.top, left: menuState.left }}
> >
@@ -771,7 +771,7 @@ export function Sidebar({
}} }}
/> />
<ActionMenuItem <ActionMenuItem
className="text-red-400 hover:bg-red-500/10" className="text-[var(--color-status-error)] hover:bg-[var(--color-status-error)]/10"
icon={Trash2} icon={Trash2}
label="Delete" label="Delete"
onClick={() => { onClick={() => {
+31 -31
View File
@@ -11,28 +11,28 @@ import type { TerminalExitInfo } from '@shared/domain/terminal';
/* ── Theme ────────────────────────────────────────────────── */ /* ── Theme ────────────────────────────────────────────────── */
const terminalTheme = { const terminalTheme = {
background: '#09090b', // zinc-950 background: '#07080e',
foreground: '#e4e4e7', // zinc-200 foreground: '#e8eaf0',
cursor: '#818cf8', // indigo-400 cursor: '#245CF9',
cursorAccent: '#09090b', cursorAccent: '#07080e',
selectionBackground: '#6366f14d', // indigo-500/30 selectionBackground: 'rgba(36, 92, 249, 0.2)',
selectionForeground: '#e4e4e7', selectionForeground: '#e8eaf0',
black: '#27272a', // zinc-800 black: '#1e2233',
red: '#f87171', // red-400 red: '#f87171',
green: '#4ade80', // green-400 green: '#4ade80',
yellow: '#facc15', // yellow-400 yellow: '#facc15',
blue: '#60a5fa', // blue-400 blue: '#248CFD',
magenta: '#c084fc', // purple-400 magenta: '#a855f7',
cyan: '#22d3ee', // cyan-400 cyan: '#22d3ee',
white: '#e4e4e7', // zinc-200 white: '#e8eaf0',
brightBlack: '#52525b', // zinc-600 brightBlack: '#4e5368',
brightRed: '#fca5a5', // red-300 brightRed: '#fca5a5',
brightGreen: '#86efac', // green-300 brightGreen: '#86efac',
brightYellow: '#fde047', // yellow-300 brightYellow: '#fde047',
brightBlue: '#93c5fd', // blue-300 brightBlue: '#60a5fa',
brightMagenta: '#d8b4fe',// purple-300 brightMagenta: '#c084fc',
brightCyan: '#67e8f9', // cyan-300 brightCyan: '#67e8f9',
brightWhite: '#fafafa', // zinc-50 brightWhite: '#f8f9fc',
}; };
/* ── Constants ────────────────────────────────────────────── */ /* ── Constants ────────────────────────────────────────────── */
@@ -94,7 +94,7 @@ export function TerminalPanel({
const terminal = new Terminal({ const terminal = new Terminal({
theme: terminalTheme, theme: terminalTheme,
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace', fontFamily: '"JetBrains Mono Variable", "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace',
fontSize: 13, fontSize: 13,
lineHeight: 1.4, lineHeight: 1.4,
cursorBlink: true, cursorBlink: true,
@@ -214,12 +214,12 @@ export function TerminalPanel({
return ( return (
<div <div
className="flex flex-col border-t border-[var(--color-border)] bg-[#09090b]" className="flex flex-col border-t border-[var(--color-border)] bg-[var(--color-surface-0)]"
style={{ height, minHeight: MIN_HEIGHT }} style={{ height, minHeight: MIN_HEIGHT }}
> >
{/* Resize handle */} {/* Resize handle */}
<div <div
className={`h-1 shrink-0 cursor-row-resize transition-colors ${isDragging ? 'bg-indigo-500/40' : 'hover:bg-zinc-700/60'}`} className={`h-1 shrink-0 cursor-row-resize transition-colors ${isDragging ? 'bg-[var(--color-accent)]/40' : 'hover:bg-[var(--color-surface-3)]/60'}`}
onMouseDown={handleDragStart} onMouseDown={handleDragStart}
role="separator" role="separator"
aria-orientation="horizontal" aria-orientation="horizontal"
@@ -237,15 +237,15 @@ export function TerminalPanel({
/> />
{/* Header bar */} {/* Header bar */}
<div className="flex h-7 shrink-0 items-center gap-2 border-b border-zinc-800/60 px-3"> <div className="flex h-7 shrink-0 items-center gap-2 border-b border-[var(--color-border)] px-3">
<span className={`size-1.5 shrink-0 rounded-full ${isRunning ? 'bg-emerald-400' : 'bg-zinc-600'}`} /> <span className={`size-1.5 shrink-0 rounded-full ${isRunning ? 'bg-emerald-400' : 'bg-[var(--color-text-muted)]'}`} />
<span className="min-w-0 flex-1 truncate text-[11px] text-zinc-500"> <span className="min-w-0 flex-1 truncate font-mono text-[11px] text-[var(--color-text-muted)]">
{snapshot ? `${snapshot.shell}${snapshot.cwd}` : 'Terminal'} {snapshot ? `${snapshot.shell}${snapshot.cwd}` : 'Terminal'}
</span> </span>
<div className="flex items-center gap-0.5"> <div className="flex items-center gap-0.5">
<button <button
aria-label="Restart terminal" aria-label="Restart terminal"
className="rounded p-0.5 text-zinc-600 transition hover:bg-zinc-800 hover:text-zinc-400" className="rounded p-0.5 text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
onClick={handleRestart} onClick={handleRestart}
type="button" type="button"
> >
@@ -253,7 +253,7 @@ export function TerminalPanel({
</button> </button>
<button <button
aria-label="Minimize terminal" aria-label="Minimize terminal"
className="rounded p-0.5 text-zinc-600 transition hover:bg-zinc-800 hover:text-zinc-400" className="rounded p-0.5 text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
onClick={onMinimize} onClick={onMinimize}
type="button" type="button"
> >
@@ -261,7 +261,7 @@ export function TerminalPanel({
</button> </button>
<button <button
aria-label="Close terminal" aria-label="Close terminal"
className="rounded p-0.5 text-zinc-600 transition hover:bg-zinc-800 hover:text-red-400" className="rounded p-0.5 text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-red-400"
onClick={handleClose} onClick={handleClose}
type="button" type="button"
> >
+90 -33
View File
@@ -1,4 +1,7 @@
import { MessageSquare, Plus, Settings } from 'lucide-react'; import { FolderPlus, MessageSquarePlus, Settings } from 'lucide-react';
import { motion } from 'motion/react';
import appIconUrl from '../../../assets/icons/icon.png';
interface WelcomePaneProps { interface WelcomePaneProps {
hasProjects: boolean; hasProjects: boolean;
@@ -7,6 +10,42 @@ interface WelcomePaneProps {
onOpenSettings: () => void; onOpenSettings: () => void;
} }
const fadeUp = (delay: number) =>
({
initial: { opacity: 0, y: 12 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.45, ease: [0.25, 0.46, 0.45, 0.94] as const, delay },
}) as const;
interface ActionCardProps {
icon: React.ReactNode;
title: string;
description: string;
onClick: () => void;
}
function ActionCard({ icon, title, description, onClick }: ActionCardProps) {
return (
<button
type="button"
onClick={onClick}
className="group flex w-full cursor-pointer items-center gap-4 rounded-xl border border-[var(--color-glass-border)] bg-[var(--color-glass)] px-5 py-4 text-left backdrop-blur-sm transition-all duration-200 hover:border-[var(--color-border-glow)] hover:shadow-[0_0_20px_rgba(36,92,249,0.08),0_4px_12px_rgba(0,0,0,0.2)]"
>
<div className="brand-gradient-bg flex size-9 shrink-0 items-center justify-center rounded-full">
{icon}
</div>
<div className="min-w-0">
<span className="block text-[13px] font-medium text-[var(--color-text-primary)]">
{title}
</span>
<span className="block text-[12px] leading-relaxed text-[var(--color-text-muted)]">
{description}
</span>
</div>
</button>
);
}
export function WelcomePane({ export function WelcomePane({
hasProjects, hasProjects,
onNewScratchpad, onNewScratchpad,
@@ -14,48 +53,66 @@ export function WelcomePane({
onOpenSettings, onOpenSettings,
}: WelcomePaneProps) { }: WelcomePaneProps) {
return ( return (
<div className="flex h-full flex-col items-center justify-center px-8"> <div className="relative flex h-full flex-col items-center justify-center overflow-hidden px-8">
<div className="flex flex-col items-center gap-6 text-center"> {/* Ambient nebula glow */}
<div className="flex size-16 items-center justify-center rounded-2xl bg-indigo-600/10"> <div
<MessageSquare className="size-8 text-indigo-400" /> className="pointer-events-none absolute inset-0"
</div> aria-hidden="true"
style={{
background: [
'radial-gradient(ellipse 50% 40% at 50% 45%, rgba(36, 92, 249, 0.07) 0%, transparent 70%)',
'radial-gradient(ellipse 40% 35% at 55% 50%, rgba(138, 41, 230, 0.05) 0%, transparent 65%)',
'radial-gradient(ellipse 60% 50% at 45% 48%, rgba(54, 21, 207, 0.04) 0%, transparent 60%)',
].join(', '),
}}
/>
<div> <div className="relative z-10 flex w-full max-w-xs flex-col items-center gap-8 text-center">
<h1 className="text-base font-semibold text-zinc-100">Welcome to aryx</h1> {/* Icon */}
<p className="mt-2 max-w-md text-[13px] leading-relaxed text-zinc-500"> <motion.div {...fadeUp(0)}>
<img
src={appIconUrl}
alt="aryx"
width={64}
height={64}
className="drop-shadow-[0_0_24px_rgba(36,92,249,0.3)]"
/>
</motion.div>
{/* Heading */}
<motion.div {...fadeUp(0.08)}>
<h1 className="font-display brand-gradient-text text-2xl font-bold tracking-tight">
aryx
</h1>
<p className="mt-2 max-w-sm text-[13px] leading-relaxed text-[var(--color-text-secondary)]">
Start a scratchpad conversation for ad-hoc questions or connect a project to work with Start a scratchpad conversation for ad-hoc questions or connect a project to work with
repo-aware Copilot agents. repo-aware Copilot agents.
</p> </p>
</div> </motion.div>
<div className="flex flex-col items-center gap-2"> {/* Action cards */}
<button <motion.div {...fadeUp(0.16)} className="flex w-full flex-col gap-2.5">
className="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-2.5 text-[13px] font-medium text-white transition hover:bg-indigo-500" <ActionCard
icon={<MessageSquarePlus className="size-4 text-white" />}
title="New Scratchpad"
description="Ask anything without a project context"
onClick={onNewScratchpad} onClick={onNewScratchpad}
type="button" />
>
<Plus className="size-4" />
New Scratchpad
</button>
{!hasProjects && ( {!hasProjects && (
<button <ActionCard
className="flex items-center gap-2 rounded-lg px-4 py-2 text-[13px] text-zinc-500 transition hover:bg-zinc-900 hover:text-zinc-300" icon={<FolderPlus className="size-4 text-white" />}
title="Add Your First Project"
description="Connect a repo for full agent support"
onClick={onAddProject} onClick={onAddProject}
type="button" />
>
<Plus className="size-3.5" />
Add Your First Project
</button>
)} )}
<button <ActionCard
className="flex items-center gap-2 rounded-lg px-4 py-2 text-[13px] text-zinc-500 transition hover:bg-zinc-900 hover:text-zinc-300" icon={<Settings className="size-4 text-white" />}
title="Manage Patterns"
description="Customize agent behaviors and workflows"
onClick={onOpenSettings} onClick={onOpenSettings}
type="button" />
> </motion.div>
<Settings className="size-3.5" />
Manage patterns
</button>
</div>
</div> </div>
</div> </div>
); );
+29 -29
View File
@@ -30,50 +30,50 @@ export function ApprovalBanner({
const approvalToolLabel = approvalToolKey ? resolveToolLabel(approvalToolKey) : undefined; const approvalToolLabel = approvalToolKey ? resolveToolLabel(approvalToolKey) : undefined;
return ( return (
<div className="rounded-xl border border-amber-500/30 bg-amber-500/5 px-4 py-3" role="alert"> <div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-status-warning)] bg-[var(--color-glass)] px-4 py-3" role="alert">
{/* Header */} {/* Header */}
<div className="flex items-start gap-2.5"> <div className="flex items-start gap-2.5">
<ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-400" /> <ShieldAlert className="mt-0.5 size-4 shrink-0 text-[var(--color-status-warning)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[13px] font-semibold text-amber-200">{approval.title}</span> <span className="text-[13px] font-semibold text-[var(--color-status-warning)]">{approval.title}</span>
<span className="rounded-full bg-amber-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-amber-400"> <span className="rounded-full bg-[var(--color-status-warning)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-warning)]">
{kindLabel} {kindLabel}
</span> </span>
{showPosition && ( {showPosition && (
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[9px] font-semibold tabular-nums text-zinc-400"> <span className="rounded-full bg-[var(--color-surface-2)] px-2 py-0.5 text-[9px] font-semibold tabular-nums text-[var(--color-text-secondary)]">
{position} of {total} {position} of {total}
</span> </span>
)} )}
</div> </div>
<div className="mt-1 flex flex-wrap items-center gap-2 text-[11px] text-zinc-400"> <div className="mt-1 flex flex-wrap items-center gap-2 text-[11px] text-[var(--color-text-secondary)]">
{approval.agentName && <span>Agent: <span className="text-zinc-300">{approval.agentName}</span></span>} {approval.agentName && <span>Agent: <span className="text-[var(--color-text-primary)]">{approval.agentName}</span></span>}
{approval.toolName && <span>Tool: <span className="text-zinc-300">{approval.toolName}</span></span>} {approval.toolName && <span>Tool: <span className="text-[var(--color-text-primary)]">{approval.toolName}</span></span>}
{approval.permissionKind && <span>Permission: <span className="text-zinc-300">{approval.permissionKind}</span></span>} {approval.permissionKind && <span>Permission: <span className="text-[var(--color-text-primary)]">{approval.permissionKind}</span></span>}
</div> </div>
{approval.permissionDetail {approval.permissionDetail
? <PermissionDetailView detail={approval.permissionDetail} /> ? <PermissionDetailView detail={approval.permissionDetail} />
: approval.detail && ( : approval.detail && (
<p className="mt-1.5 text-[12px] leading-relaxed text-zinc-400">{approval.detail}</p> <p className="mt-1.5 text-[12px] leading-relaxed text-[var(--color-text-secondary)]">{approval.detail}</p>
)} )}
</div> </div>
</div> </div>
{/* Final-response message preview */} {/* Final-response message preview */}
{hasMessages && ( {hasMessages && (
<div className="mt-3 space-y-2 rounded-lg border border-zinc-800 bg-zinc-900/60 p-3"> <div className="mt-3 space-y-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] p-3">
<p className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500"> <p className="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Pending messages not yet published Pending messages not yet published
</p> </p>
{approval.messages!.map((message) => ( {approval.messages!.map((message) => (
<div className="mt-2" key={message.id}> <div className="mt-2" key={message.id}>
<div className="mb-1 flex items-center gap-2 text-[11px] font-medium text-zinc-500"> <div className="mb-1 flex items-center gap-2 text-[11px] font-medium text-[var(--color-text-muted)]">
<Bot className="size-3" /> <Bot className="size-3" />
<span>{message.authorName}</span> <span>{message.authorName}</span>
</div> </div>
<div className="rounded-lg border border-zinc-800/60 bg-zinc-900/40 px-3 py-2 text-[13px] leading-relaxed text-zinc-300"> <div className="rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]/60 px-3 py-2 text-[13px] leading-relaxed text-[var(--color-text-secondary)]">
<MarkdownContent content={message.content} /> <MarkdownContent content={message.content} />
</div> </div>
</div> </div>
@@ -84,7 +84,7 @@ export function ApprovalBanner({
{/* Actions */} {/* Actions */}
<div className="mt-3 flex items-center gap-2"> <div className="mt-3 flex items-center gap-2">
<button <button
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600 px-3.5 py-1.5 text-[12px] font-medium text-white transition hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-50" className="brand-gradient-bg inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-[12px] font-medium text-white transition-all duration-200 hover:brightness-110 disabled:cursor-not-allowed disabled:opacity-50"
disabled={isResolving} disabled={isResolving}
onClick={() => onResolve('approved')} onClick={() => onResolve('approved')}
type="button" type="button"
@@ -95,7 +95,7 @@ export function ApprovalBanner({
{canAlwaysApprove && ( {canAlwaysApprove && (
<button <button
aria-label={`Always approve ${approvalToolLabel}`} aria-label={`Always approve ${approvalToolLabel}`}
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600/20 px-3.5 py-1.5 text-[12px] font-medium text-emerald-300 transition hover:bg-emerald-600/30 disabled:cursor-not-allowed disabled:opacity-50" className="inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-status-success)]/15 px-3.5 py-1.5 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/25 disabled:cursor-not-allowed disabled:opacity-50"
disabled={isResolving} disabled={isResolving}
onClick={() => onResolve('approved', true)} onClick={() => onResolve('approved', true)}
title={`Auto-approve "${approvalToolLabel}" for the rest of this session`} title={`Auto-approve "${approvalToolLabel}" for the rest of this session`}
@@ -106,7 +106,7 @@ export function ApprovalBanner({
</button> </button>
)} )}
<button <button
className="inline-flex items-center gap-1.5 rounded-lg bg-zinc-800 px-3.5 py-1.5 text-[12px] font-medium text-zinc-300 transition hover:bg-zinc-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-50" className="inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-surface-2)] px-3.5 py-1.5 text-[12px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] disabled:cursor-not-allowed disabled:opacity-50"
disabled={isResolving} disabled={isResolving}
onClick={() => onResolve('rejected')} onClick={() => onResolve('rejected')}
type="button" type="button"
@@ -115,7 +115,7 @@ export function ApprovalBanner({
Reject Reject
</button> </button>
{showPosition && ( {showPosition && (
<span className="ml-auto text-[10px] text-zinc-600"> <span className="ml-auto text-[10px] text-[var(--color-text-muted)]">
Next approval will appear after this one is resolved Next approval will appear after this one is resolved
</span> </span>
)} )}
@@ -130,42 +130,42 @@ export function QueuedApprovalsList({ approvals }: { approvals: PendingApprovalR
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
return ( return (
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2"> <div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-glass)] px-3 py-2">
<button <button
aria-expanded={expanded} aria-expanded={expanded}
className="flex w-full items-center gap-2 text-left" className="flex w-full items-center gap-2 text-left"
onClick={() => setExpanded(!expanded)} onClick={() => setExpanded(!expanded)}
type="button" type="button"
> >
<ShieldCheck className="size-3 text-zinc-500" /> <ShieldCheck className="size-3 text-[var(--color-text-muted)]" />
<span className="text-[11px] font-medium text-zinc-400"> <span className="text-[11px] font-medium text-[var(--color-text-secondary)]">
{approvals.length} queued approval{approvals.length === 1 ? '' : 's'} {approvals.length} queued approval{approvals.length === 1 ? '' : 's'}
</span> </span>
<ChevronDown <ChevronDown
className={`ml-auto size-3 text-zinc-600 transition-transform ${expanded ? 'rotate-180' : ''}`} className={`ml-auto size-3 text-[var(--color-text-muted)] transition-transform ${expanded ? 'rotate-180' : ''}`}
/> />
</button> </button>
{expanded && ( {expanded && (
<div className="mt-2 space-y-1.5 border-t border-zinc-800/60 pt-2"> <div className="mt-2 space-y-1.5 border-t border-[var(--color-border-subtle)] pt-2">
{approvals.map((approval) => { {approvals.map((approval) => {
const kindLabel = approval.kind === 'final-response' ? 'response' : 'tool'; const kindLabel = approval.kind === 'final-response' ? 'response' : 'tool';
return ( return (
<div <div
className="flex items-center gap-2 rounded-md bg-zinc-800/40 px-2.5 py-1.5" className="flex items-center gap-2 rounded-md bg-[var(--color-surface-2)]/40 px-2.5 py-1.5"
key={approval.id} key={approval.id}
> >
<ShieldAlert className="size-3 shrink-0 text-zinc-600" /> <ShieldAlert className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<span className="min-w-0 flex-1 truncate text-[11px] text-zinc-400"> <span className="min-w-0 flex-1 truncate text-[11px] text-[var(--color-text-secondary)]">
{(approval.permissionDetail && permissionDetailSummary(approval.permissionDetail)) || approval.title} {(approval.permissionDetail && permissionDetailSummary(approval.permissionDetail)) || approval.title}
</span> </span>
<span className="shrink-0 rounded-full bg-zinc-800 px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider text-zinc-500"> <span className="shrink-0 rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{kindLabel} {kindLabel}
</span> </span>
{approval.toolName && ( {approval.toolName && (
<span className="shrink-0 text-[10px] text-zinc-500">{approval.toolName}</span> <span className="shrink-0 text-[10px] text-[var(--color-text-muted)]">{approval.toolName}</span>
)} )}
{approval.agentName && ( {approval.agentName && (
<span className="shrink-0 text-[10px] text-zinc-600">{approval.agentName}</span> <span className="shrink-0 text-[10px] text-[var(--color-text-muted)]">{approval.agentName}</span>
)} )}
</div> </div>
); );
+55 -55
View File
@@ -15,9 +15,9 @@ import { RotateCcw, Server, ShieldCheck } from 'lucide-react';
function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) { function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) {
if (!tier) return null; if (!tier) return null;
const styles = { const styles = {
premium: 'bg-amber-500/10 text-amber-400', premium: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]',
standard: 'bg-zinc-700/50 text-zinc-500', standard: 'bg-[var(--color-surface-3)]/50 text-[var(--color-text-muted)]',
fast: 'bg-emerald-500/10 text-emerald-400', fast: 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]',
}; };
return ( return (
<span className={`ml-auto rounded px-1.5 py-0.5 text-[9px] font-medium ${styles[tier]}`}> <span className={`ml-auto rounded px-1.5 py-0.5 text-[9px] font-medium ${styles[tier]}`}>
@@ -64,10 +64,10 @@ export function InlineModelPill({
<button <button
aria-expanded={open} aria-expanded={open}
aria-haspopup="listbox" aria-haspopup="listbox"
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${ className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
open open
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300' ? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300' : 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
} disabled:cursor-not-allowed disabled:opacity-50`} } disabled:cursor-not-allowed disabled:opacity-50`}
disabled={disabled} disabled={disabled}
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
@@ -79,19 +79,19 @@ export function InlineModelPill({
</button> </button>
{open && !disabled && ( {open && !disabled && (
<div className="absolute bottom-full right-0 z-40 mb-1.5 max-h-72 w-64 overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl" role="listbox"> <div className="absolute bottom-full right-0 z-40 mb-1.5 max-h-72 w-64 overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-2xl" role="listbox">
{groupedModels.map((pg) => ( {groupedModels.map((pg) => (
<div key={pg.id}> <div key={pg.id}>
<div className="flex items-center gap-2 px-3 pb-1 pt-2.5"> <div className="flex items-center gap-2 px-3 pb-1 pt-2.5">
<ProviderIcon provider={pg.id} className="size-3.5" /> <ProviderIcon provider={pg.id} className="size-3.5" />
<span className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500"> <span className="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{pg.label} {pg.label}
</span> </span>
</div> </div>
{pg.models.map((model) => ( {pg.models.map((model) => (
<button <button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${ className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300' model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
}`} }`}
key={model.id} key={model.id}
onClick={() => { onChange(model.id); setOpen(false); }} onClick={() => { onChange(model.id); setOpen(false); }}
@@ -107,13 +107,13 @@ export function InlineModelPill({
))} ))}
{otherModels.length > 0 && ( {otherModels.length > 0 && (
<div> <div>
<div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-zinc-500"> <div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Other Other
</div> </div>
{otherModels.map((model) => ( {otherModels.map((model) => (
<button <button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${ className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300' model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
}`} }`}
key={model.id} key={model.id}
onClick={() => { onChange(model.id); setOpen(false); }} onClick={() => { onChange(model.id); setOpen(false); }}
@@ -155,7 +155,7 @@ export function InlineThinkingPill({
if (supportedEfforts && supportedEfforts.length === 0) { if (supportedEfforts && supportedEfforts.length === 0) {
return ( return (
<span className="inline-flex items-center gap-1 rounded border border-zinc-800/40 bg-zinc-800/20 px-1.5 py-0.5 text-pill text-zinc-600"> <span className="inline-flex items-center gap-1 rounded border border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/20 px-1.5 py-0.5 text-pill text-[var(--color-text-muted)]">
<Sparkles className="size-2.5" /> <Sparkles className="size-2.5" />
N/A N/A
</span> </span>
@@ -169,10 +169,10 @@ export function InlineThinkingPill({
<button <button
aria-expanded={open} aria-expanded={open}
aria-haspopup="listbox" aria-haspopup="listbox"
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${ className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
open open
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300' ? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300' : 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
} disabled:cursor-not-allowed disabled:opacity-50`} } disabled:cursor-not-allowed disabled:opacity-50`}
disabled={disabled} disabled={disabled}
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
@@ -184,11 +184,11 @@ export function InlineThinkingPill({
</button> </button>
{open && !disabled && ( {open && !disabled && (
<div className="absolute bottom-full right-0 z-40 mb-1.5 w-36 overflow-hidden rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl" role="listbox"> <div className="absolute bottom-full right-0 z-40 mb-1.5 w-36 overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-2xl" role="listbox">
{options.map((option) => ( {options.map((option) => (
<button <button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${ className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
option.value === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300' option.value === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
}`} }`}
key={option.value} key={option.value}
onClick={() => { onChange(option.value); setOpen(false); }} onClick={() => { onChange(option.value); setOpen(false); }}
@@ -235,10 +235,10 @@ export function InlineToolsPill({
<button <button
aria-expanded={open} aria-expanded={open}
aria-haspopup="listbox" aria-haspopup="listbox"
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${ className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
open open
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300' ? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300' : 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
} disabled:cursor-not-allowed disabled:opacity-50`} } disabled:cursor-not-allowed disabled:opacity-50`}
disabled={disabled} disabled={disabled}
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
@@ -250,7 +250,7 @@ export function InlineToolsPill({
</button> </button>
{open && !disabled && ( {open && !disabled && (
<div className="absolute bottom-full left-0 z-40 mb-1.5 w-64 overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl"> <div className="absolute bottom-full left-0 z-40 mb-1.5 w-64 overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-2xl">
{workspaceMcpServers.length > 0 && ( {workspaceMcpServers.length > 0 && (
<McpServerGroup <McpServerGroup
label="Workspace MCP" label="Workspace MCP"
@@ -277,7 +277,7 @@ export function InlineToolsPill({
)} )}
{lspProfiles.length > 0 && ( {lspProfiles.length > 0 && (
<div> <div>
<div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-zinc-600"> <div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Language Servers Language Servers
</div> </div>
{lspProfiles.map((profile) => ( {lspProfiles.map((profile) => (
@@ -315,7 +315,7 @@ function McpServerGroup({
}) { }) {
return ( return (
<div> <div>
<div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-zinc-600"> <div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{label} {label}
</div> </div>
{servers.map((server) => ( {servers.map((server) => (
@@ -475,12 +475,12 @@ export function InlineApprovalPill({
<button <button
aria-expanded={open} aria-expanded={open}
aria-haspopup="listbox" aria-haspopup="listbox"
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${ className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
open open
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300' ? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: isOverridden : isOverridden
? 'border-amber-500/30 bg-amber-500/5 text-amber-400 hover:border-amber-500/50' ? 'border-[var(--color-status-warning)]/30 bg-[var(--color-status-warning)]/5 text-[var(--color-status-warning)] hover:border-[var(--color-status-warning)]/50'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300' : 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
} disabled:cursor-not-allowed disabled:opacity-50`} } disabled:cursor-not-allowed disabled:opacity-50`}
disabled={disabled} disabled={disabled}
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
@@ -493,26 +493,26 @@ export function InlineApprovalPill({
)} )}
<span> <span>
{effectiveAutoApprovedCount}/{totalItemCount} auto-approved {effectiveAutoApprovedCount}/{totalItemCount} auto-approved
{isProbingAny && <span className="text-zinc-500"> · probing</span>} {isProbingAny && <span className="text-[var(--color-text-muted)]"> · probing</span>}
</span> </span>
<ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} /> <ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} />
</button> </button>
{open && !disabled && ( {open && !disabled && (
<div className="absolute bottom-full left-0 z-40 mb-1.5 max-h-[28rem] w-80 overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 shadow-2xl"> <div className="absolute bottom-full left-0 z-40 mb-1.5 max-h-[28rem] w-80 overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-2xl">
{/* Header: session override / pattern defaults */} {/* Header: session override / pattern defaults */}
<div className="sticky top-0 z-10 border-b border-zinc-800 bg-zinc-900"> <div className="sticky top-0 z-10 border-b border-[var(--color-border)] bg-[var(--color-surface-1)]">
<div className="flex items-center gap-2 px-3 py-2"> <div className="flex items-center gap-2 px-3 py-2">
<span className={`rounded-full px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider ${ <span className={`rounded-full px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider ${
isOverridden isOverridden
? 'bg-amber-500/15 text-amber-400' ? 'bg-[var(--color-status-warning)]/15 text-[var(--color-status-warning)]'
: 'bg-zinc-800 text-zinc-500' : 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
}`}> }`}>
{isOverridden ? 'Session override' : 'Pattern defaults'} {isOverridden ? 'Session override' : 'Pattern defaults'}
</span> </span>
{isOverridden && ( {isOverridden && (
<button <button
className="flex items-center gap-1 rounded-full px-2 py-0.5 text-[9px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="flex items-center gap-1 rounded-full px-2 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={() => onUpdate({})} onClick={() => onUpdate({})}
type="button" type="button"
> >
@@ -524,12 +524,12 @@ export function InlineApprovalPill({
{/* Search */} {/* Search */}
{showSearch && ( {showSearch && (
<div className="border-t border-zinc-800/50 px-3 py-1.5"> <div className="border-t border-[var(--color-border-subtle)] px-3 py-1.5">
<div className="flex items-center gap-2 rounded border border-zinc-800 bg-zinc-800/30 px-2 py-1"> <div className="flex items-center gap-2 rounded border border-[var(--color-border)] bg-[var(--color-surface-2)]/30 px-2 py-1">
<Search className="size-3 shrink-0 text-zinc-600" /> <Search className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<input <input
autoFocus autoFocus
className="w-full bg-transparent text-[12px] text-zinc-300 placeholder-zinc-600 outline-none" className="w-full bg-transparent text-[12px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none"
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
placeholder="Filter tools…" placeholder="Filter tools…"
type="text" type="text"
@@ -558,31 +558,31 @@ export function InlineApprovalPill({
<div key={group.id}> <div key={group.id}>
{/* Group header */} {/* Group header */}
{isBuiltin ? ( {isBuiltin ? (
<div className={`px-3 pb-1 ${groupIdx > 0 ? 'pt-2.5' : 'pt-1'} text-[9px] font-semibold uppercase tracking-wider text-zinc-600`}> <div className={`px-3 pb-1 ${groupIdx > 0 ? 'pt-2.5' : 'pt-1'} text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]`}>
{group.label} {group.label}
</div> </div>
) : ( ) : (
<div <div
className={`flex w-full cursor-pointer items-center gap-1.5 px-2.5 py-1.5 text-left transition hover:bg-zinc-800/60 ${groupIdx > 0 ? 'mt-0.5' : ''}`} className={`flex w-full cursor-pointer items-center gap-1.5 px-2.5 py-1.5 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]/60 ${groupIdx > 0 ? 'mt-0.5' : ''}`}
onClick={() => toggleExpanded(group.id)} onClick={() => toggleExpanded(group.id)}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleExpanded(group.id); } }} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleExpanded(group.id); } }}
role="button" role="button"
tabIndex={0} tabIndex={0}
> >
{probing ? ( {probing ? (
<Loader2 className="size-3 shrink-0 animate-spin text-indigo-400" aria-label="Probing server" /> <Loader2 className="size-3 shrink-0 animate-spin text-[var(--color-text-accent)]" aria-label="Probing server" />
) : group.tools.length > 0 ? ( ) : group.tools.length > 0 ? (
<ChevronRight className={`size-3 shrink-0 text-zinc-600 transition ${expanded ? 'rotate-90' : ''}`} /> <ChevronRight className={`size-3 shrink-0 text-[var(--color-text-muted)] transition ${expanded ? 'rotate-90' : ''}`} />
) : ( ) : (
<Server className="size-3 shrink-0 text-zinc-600" /> <Server className="size-3 shrink-0 text-[var(--color-text-muted)]" />
)} )}
<span className="min-w-0 flex-1 truncate text-[12px] font-medium text-zinc-300">{group.label}</span> <span className="min-w-0 flex-1 truncate text-[12px] font-medium text-[var(--color-text-primary)]">{group.label}</span>
{probing ? ( {probing ? (
<span className="shrink-0 rounded-full bg-indigo-500/10 px-1.5 py-px text-[9px] font-medium text-indigo-400"> <span className="shrink-0 rounded-full bg-[var(--color-accent-muted)] px-1.5 py-px text-[9px] font-medium text-[var(--color-text-accent)]">
probing probing
</span> </span>
) : ( ) : (
<span className="shrink-0 rounded-full bg-zinc-800/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-zinc-500"> <span className="shrink-0 rounded-full bg-[var(--color-surface-2)]/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-[var(--color-text-muted)]">
{approvedLabel} {approvedLabel}
</span> </span>
)} )}
@@ -619,7 +619,7 @@ export function InlineApprovalPill({
})} })}
{filteredGroups.length === 0 && searchLower && ( {filteredGroups.length === 0 && searchLower && (
<div className="px-3 py-4 text-center text-[12px] text-zinc-600"> <div className="px-3 py-4 text-center text-[12px] text-[var(--color-text-muted)]">
No tools match "{search}" No tools match "{search}"
</div> </div>
)} )}
@@ -643,13 +643,13 @@ function GroupToggle({
<button <button
aria-pressed={allApproved} aria-pressed={allApproved}
className={`relative inline-flex h-[16px] w-[28px] shrink-0 items-center rounded-full transition-colors ${ className={`relative inline-flex h-[16px] w-[28px] shrink-0 items-center rounded-full transition-colors ${
allApproved ? 'bg-indigo-500' : someApproved ? 'bg-zinc-600' : 'bg-zinc-700' allApproved ? 'bg-[var(--color-accent)]' : someApproved ? 'bg-[var(--color-surface-3)]' : 'bg-[var(--color-surface-3)]'
}`} }`}
onClick={onToggle} onClick={onToggle}
type="button" type="button"
> >
{someApproved ? ( {someApproved ? (
<Minus className="absolute left-1/2 size-2 -translate-x-1/2 text-zinc-300" strokeWidth={3} /> <Minus className="absolute left-1/2 size-2 -translate-x-1/2 text-[var(--color-text-primary)]" strokeWidth={3} />
) : ( ) : (
<span <span
className={`inline-block size-[12px] rounded-full bg-white shadow transition-transform ${ className={`inline-block size-[12px] rounded-full bg-white shadow transition-transform ${
@@ -677,16 +677,16 @@ export function InlineTerminalPill({
return ( return (
<button <button
aria-pressed={isOpen} aria-pressed={isOpen}
className={`inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium transition ${ className={`inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium transition-all duration-200 ${
isOpen isOpen
? 'bg-indigo-500/15 text-indigo-300 hover:bg-indigo-500/20' ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)] hover:bg-[var(--color-accent)]/20'
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300' : 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]'
} disabled:cursor-not-allowed disabled:opacity-50`} } disabled:cursor-not-allowed disabled:opacity-50`}
disabled={disabled} disabled={disabled}
onClick={onToggle} onClick={onToggle}
type="button" type="button"
> >
{isRunning && <span className="size-1.5 shrink-0 rounded-full bg-emerald-400" />} {isRunning && <span className="size-1.5 shrink-0 rounded-full bg-[var(--color-status-success)]" />}
<TerminalSquare className="size-3" /> <TerminalSquare className="size-3" />
<span>Terminal</span> <span>Terminal</span>
</button> </button>
@@ -66,19 +66,19 @@ export function InlinePromptPill({
<button <button
aria-expanded={open} aria-expanded={open}
aria-haspopup="listbox" aria-haspopup="listbox"
className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
disabled={disabled} disabled={disabled}
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
type="button" type="button"
> >
<FileText className="size-3" /> <FileText className="size-3" />
Prompts Prompts
<span className="text-zinc-600">({promptFiles.length})</span> <span className="text-[var(--color-text-muted)]">({promptFiles.length})</span>
</button> </button>
{open && !disabled && ( {open && !disabled && (
<div <div
className="absolute bottom-full left-0 z-40 mb-1.5 w-80 rounded-lg border border-zinc-700 bg-zinc-900 shadow-xl" className="absolute bottom-full left-0 z-40 mb-1.5 w-80 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-xl"
role="listbox" role="listbox"
> >
{selectedPrompt ? ( {selectedPrompt ? (
@@ -114,24 +114,24 @@ function PromptList({
}) { }) {
return ( return (
<div className="max-h-64 overflow-y-auto py-1"> <div className="max-h-64 overflow-y-auto py-1">
<div className="px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-500"> <div className="px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Prompt files Prompt files
</div> </div>
{promptFiles.map((prompt) => ( {promptFiles.map((prompt) => (
<button <button
key={prompt.id} key={prompt.id}
className="flex w-full items-start gap-2.5 px-3 py-2 text-left transition hover:bg-zinc-800" className="flex w-full items-start gap-2.5 px-3 py-2 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]"
onClick={() => onSelect(prompt)} onClick={() => onSelect(prompt)}
role="option" role="option"
type="button" type="button"
> >
<FileText className="mt-0.5 size-3.5 shrink-0 text-zinc-500" /> <FileText className="mt-0.5 size-3.5 shrink-0 text-[var(--color-text-muted)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="truncate text-[12px] font-medium text-zinc-200"> <div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">
{prompt.name} {prompt.name}
</div> </div>
{prompt.description && ( {prompt.description && (
<div className="mt-0.5 truncate text-[11px] text-zinc-500"> <div className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
{prompt.description} {prompt.description}
</div> </div>
)} )}
@@ -140,7 +140,7 @@ function PromptList({
{prompt.variables.map((v) => ( {prompt.variables.map((v) => (
<span <span
key={v.name} key={v.name}
className="rounded bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500" className="rounded bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[10px] text-[var(--color-text-muted)]"
> >
{v.name} {v.name}
</span> </span>
@@ -173,7 +173,7 @@ function PromptVariableForm({
<div className="p-3"> <div className="p-3">
<div className="mb-3 flex items-center gap-2"> <div className="mb-3 flex items-center gap-2">
<button <button
className="flex size-5 items-center justify-center rounded text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300" className="flex size-5 items-center justify-center rounded text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={onBack} onClick={onBack}
type="button" type="button"
aria-label="Back to prompt list" aria-label="Back to prompt list"
@@ -181,11 +181,11 @@ function PromptVariableForm({
<X className="size-3" /> <X className="size-3" />
</button> </button>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="truncate text-[12px] font-medium text-zinc-200"> <div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">
{prompt.name} {prompt.name}
</div> </div>
{prompt.description && ( {prompt.description && (
<div className="truncate text-[10px] text-zinc-500">{prompt.description}</div> <div className="truncate text-[10px] text-[var(--color-text-muted)]">{prompt.description}</div>
)} )}
</div> </div>
</div> </div>
@@ -203,10 +203,10 @@ function PromptVariableForm({
</div> </div>
<button <button
className={`mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-[12px] font-medium transition ${ className={`mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-[12px] font-medium transition-all duration-200 ${
submitDisabled submitDisabled
? 'bg-zinc-800 text-zinc-600' ? 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
: 'bg-indigo-600 text-white hover:bg-indigo-500' : 'brand-gradient-bg text-white hover:brightness-110'
}`} }`}
disabled={submitDisabled} disabled={submitDisabled}
onClick={onSubmit} onClick={onSubmit}
@@ -232,11 +232,11 @@ function PromptVariableInput({
}) { }) {
return ( return (
<div> <div>
<label className="mb-1 block text-[11px] font-medium text-zinc-400"> <label className="mb-1 block text-[11px] font-medium text-[var(--color-text-secondary)]">
{variable.name} {variable.name}
</label> </label>
<input <input
className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-1.5 text-[12px] text-zinc-200 placeholder-zinc-600 transition focus:border-indigo-500/50 focus:outline-none" className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2.5 py-1.5 text-[12px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] transition-all duration-200 focus:border-[var(--color-border-glow)] focus:outline-none"
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter' && onSubmit) { if (e.key === 'Enter' && onSubmit) {
+11 -11
View File
@@ -24,20 +24,20 @@ export function McpAuthBanner({
const hasFailed = mcpAuth.status === 'failed'; const hasFailed = mcpAuth.status === 'failed';
return ( return (
<div className="rounded-xl border border-amber-500/30 bg-amber-500/5 px-4 py-3" role="alert"> <div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-status-warning)] bg-[var(--color-glass)] px-4 py-3" role="alert">
<div className="flex items-start gap-2.5"> <div className="flex items-start gap-2.5">
<KeyRound className="mt-0.5 size-4 shrink-0 text-amber-400" /> <KeyRound className="mt-0.5 size-4 shrink-0 text-[var(--color-status-warning)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[13px] font-semibold text-amber-200">Authentication required</span> <span className="text-[13px] font-semibold text-[var(--color-status-warning)]">Authentication required</span>
<span className="rounded-full bg-amber-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-amber-400"> <span className="rounded-full bg-[var(--color-status-warning)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-warning)]">
MCP MCP
</span> </span>
</div> </div>
<button <button
aria-label="Dismiss authentication prompt" aria-label="Dismiss authentication prompt"
className="rounded p-0.5 text-zinc-500 transition hover:bg-zinc-700/50 hover:text-zinc-300" className="rounded p-0.5 text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-primary)]"
onClick={handleDismiss} onClick={handleDismiss}
type="button" type="button"
> >
@@ -45,21 +45,21 @@ export function McpAuthBanner({
</button> </button>
</div> </div>
<p className="mt-2 text-[13px] leading-relaxed text-zinc-200"> <p className="mt-2 text-[13px] leading-relaxed text-[var(--color-text-primary)]">
The MCP server{' '} The MCP server{' '}
<span className="font-medium text-amber-200">{mcpAuth.serverName}</span>{' '} <span className="font-medium text-[var(--color-status-warning)]">{mcpAuth.serverName}</span>{' '}
requires OAuth authentication to connect. requires OAuth authentication to connect.
</p> </p>
<p className="mt-1 text-[11px] text-zinc-500">{mcpAuth.serverUrl}</p> <p className="mt-1 text-[11px] text-[var(--color-text-muted)]">{mcpAuth.serverUrl}</p>
{hasFailed && mcpAuth.errorMessage && ( {hasFailed && mcpAuth.errorMessage && (
<p className="mt-2 text-[12px] text-red-400">{mcpAuth.errorMessage}</p> <p className="mt-2 text-[12px] text-[var(--color-status-error)]">{mcpAuth.errorMessage}</p>
)} )}
<div className="mt-3 flex items-center gap-3"> <div className="mt-3 flex items-center gap-3">
<button <button
className="inline-flex items-center gap-1.5 rounded-lg bg-amber-500/20 px-3 py-1.5 text-[12px] font-medium text-amber-200 transition hover:bg-amber-500/30 disabled:opacity-50" className="brand-gradient-bg inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[12px] font-medium text-white transition-all duration-200 hover:brightness-110 disabled:opacity-50"
disabled={isAuthenticating} disabled={isAuthenticating}
onClick={handleAuthenticate} onClick={handleAuthenticate}
type="button" type="button"
@@ -75,7 +75,7 @@ export function McpAuthBanner({
'Authenticate in browser' 'Authenticate in browser'
)} )}
</button> </button>
<span className="text-[11px] text-zinc-500"> <span className="text-[11px] text-[var(--color-text-muted)]">
{isAuthenticating {isAuthenticating
? 'Waiting for consent in the browser…' ? 'Waiting for consent in the browser…'
: 'Opens your browser for OAuth consent. Token is stored for this session only.'} : 'Opens your browser for OAuth consent. Token is stored for this session only.'}
@@ -68,7 +68,7 @@ function ShellDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
{detail.intention && <IntentionLine text={detail.intention} />} {detail.intention && <IntentionLine text={detail.intention} />}
{detail.warning && ( {detail.warning && (
<div className="flex items-start gap-1.5 rounded-md bg-red-500/10 px-2.5 py-1.5 text-[11px] text-red-300"> <div className="flex items-start gap-1.5 rounded-md bg-[var(--color-status-error)]/10 px-2.5 py-1.5 text-[11px] text-[var(--color-status-error)]">
<AlertTriangle className="mt-0.5 size-3 shrink-0" /> <AlertTriangle className="mt-0.5 size-3 shrink-0" />
<span>{detail.warning}</span> <span>{detail.warning}</span>
</div> </div>
@@ -89,8 +89,8 @@ function WriteDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
{detail.intention && <IntentionLine text={detail.intention} />} {detail.intention && <IntentionLine text={detail.intention} />}
{detail.fileName && ( {detail.fileName && (
<div className="flex items-center gap-1.5 text-[11px] text-zinc-300"> <div className="flex items-center gap-1.5 text-[11px] text-[var(--color-text-primary)]">
<FileEdit className="size-3 shrink-0 text-zinc-500" /> <FileEdit className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<code className="font-mono">{detail.fileName}</code> <code className="font-mono">{detail.fileName}</code>
</div> </div>
)} )}
@@ -107,8 +107,8 @@ function ReadDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
{detail.intention && <IntentionLine text={detail.intention} />} {detail.intention && <IntentionLine text={detail.intention} />}
{detail.path && ( {detail.path && (
<div className="flex items-center gap-1.5 text-[11px] text-zinc-300"> <div className="flex items-center gap-1.5 text-[11px] text-[var(--color-text-primary)]">
<FileText className="size-3 shrink-0 text-zinc-500" /> <FileText className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<code className="font-mono">{detail.path}</code> <code className="font-mono">{detail.path}</code>
</div> </div>
)} )}
@@ -121,14 +121,14 @@ function McpDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
<div className="flex flex-wrap items-center gap-2 text-[11px]"> <div className="flex flex-wrap items-center gap-2 text-[11px]">
{detail.serverName && ( {detail.serverName && (
<span className="inline-flex items-center gap-1 rounded-full bg-indigo-500/15 px-2 py-0.5 text-indigo-300"> <span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-muted)] px-2 py-0.5 text-[var(--color-text-accent)]">
<Server className="size-2.5" /> <Server className="size-2.5" />
{detail.serverName} {detail.serverName}
</span> </span>
)} )}
{detail.toolTitle && <span className="text-zinc-300">{detail.toolTitle}</span>} {detail.toolTitle && <span className="text-[var(--color-text-primary)]">{detail.toolTitle}</span>}
{detail.readOnly && ( {detail.readOnly && (
<span className="rounded-full bg-emerald-500/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-emerald-400"> <span className="rounded-full bg-[var(--color-status-success)]/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-success)]">
read-only read-only
</span> </span>
)} )}
@@ -145,10 +145,10 @@ function UrlDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
{detail.intention && <IntentionLine text={detail.intention} />} {detail.intention && <IntentionLine text={detail.intention} />}
{detail.url && ( {detail.url && (
<div className="flex items-center gap-1.5 rounded-md bg-zinc-800/60 px-2.5 py-1.5 text-[11px] text-blue-300"> <div className="flex items-center gap-1.5 rounded-md bg-[var(--color-surface-2)]/60 px-2.5 py-1.5 text-[11px] text-[var(--color-accent-sky)]">
<Globe className="size-3 shrink-0" /> <Globe className="size-3 shrink-0" />
<code className="min-w-0 flex-1 break-all font-mono">{detail.url}</code> <code className="min-w-0 flex-1 break-all font-mono">{detail.url}</code>
<ExternalLink className="size-3 shrink-0 text-zinc-500" /> <ExternalLink className="size-3 shrink-0 text-[var(--color-text-muted)]" />
</div> </div>
)} )}
</div> </div>
@@ -160,18 +160,18 @@ function MemoryDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-1.5"> <div className="mt-2.5 space-y-1.5">
{detail.subject && ( {detail.subject && (
<div className="flex items-center gap-1.5 text-[11px]"> <div className="flex items-center gap-1.5 text-[11px]">
<BookOpen className="size-3 shrink-0 text-zinc-500" /> <BookOpen className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<span className="font-medium text-zinc-300">{detail.subject}</span> <span className="font-medium text-[var(--color-text-primary)]">{detail.subject}</span>
</div> </div>
)} )}
{detail.fact && ( {detail.fact && (
<p className="rounded-md bg-zinc-800/60 px-2.5 py-1.5 text-[11px] leading-relaxed text-zinc-300"> <p className="rounded-md bg-[var(--color-surface-2)]/60 px-2.5 py-1.5 text-[11px] leading-relaxed text-[var(--color-text-primary)]">
{detail.fact} {detail.fact}
</p> </p>
)} )}
{detail.citations && ( {detail.citations && (
<p className="text-[10px] text-zinc-500"> <p className="text-[10px] text-[var(--color-text-muted)]">
Source: <span className="text-zinc-400">{detail.citations}</span> Source: <span className="text-[var(--color-text-secondary)]">{detail.citations}</span>
</p> </p>
)} )}
</div> </div>
@@ -182,7 +182,7 @@ function CustomToolDetail({ detail }: { detail: PermissionDetail }) {
return ( return (
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
{detail.toolDescription && ( {detail.toolDescription && (
<p className="text-[11px] text-zinc-400">{detail.toolDescription}</p> <p className="text-[11px] text-[var(--color-text-secondary)]">{detail.toolDescription}</p>
)} )}
{detail.args && Object.keys(detail.args).length > 0 && ( {detail.args && Object.keys(detail.args).length > 0 && (
<CollapsibleCode label="Arguments" text={JSON.stringify(detail.args, null, 2)} /> <CollapsibleCode label="Arguments" text={JSON.stringify(detail.args, null, 2)} />
@@ -195,7 +195,7 @@ function HookDetail({ detail }: { detail: PermissionDetail }) {
return ( return (
<div className="mt-2.5 space-y-2"> <div className="mt-2.5 space-y-2">
{detail.hookMessage && ( {detail.hookMessage && (
<div className="flex items-start gap-1.5 rounded-md bg-amber-500/10 px-2.5 py-1.5 text-[11px] text-amber-200"> <div className="flex items-start gap-1.5 rounded-md bg-[var(--color-status-warning)]/10 px-2.5 py-1.5 text-[11px] text-[var(--color-status-warning)]">
<AlertTriangle className="mt-0.5 size-3 shrink-0" /> <AlertTriangle className="mt-0.5 size-3 shrink-0" />
<span>{detail.hookMessage}</span> <span>{detail.hookMessage}</span>
</div> </div>
@@ -210,12 +210,12 @@ function HookDetail({ detail }: { detail: PermissionDetail }) {
/* ── Shared primitives ──────────────────────────────────────── */ /* ── Shared primitives ──────────────────────────────────────── */
function IntentionLine({ text }: { text: string }) { function IntentionLine({ text }: { text: string }) {
return <p className="text-[11px] italic text-zinc-400">{text}</p>; return <p className="text-[11px] italic text-[var(--color-text-secondary)]">{text}</p>;
} }
function CommandBlock({ text }: { text: string }) { function CommandBlock({ text }: { text: string }) {
return ( return (
<pre className="overflow-x-auto rounded-md bg-zinc-900/80 px-3 py-2 font-mono text-[11px] leading-relaxed text-emerald-300"> <pre className="overflow-x-auto rounded-md bg-[var(--color-surface-1)] px-3 py-2 font-mono text-[11px] leading-relaxed text-[var(--color-status-success)]">
{text} {text}
</pre> </pre>
); );
@@ -225,12 +225,12 @@ function DiffBlock({ text }: { text: string }) {
const lines = text.split('\n'); const lines = text.split('\n');
return ( return (
<CollapsibleCode label="Diff" text={text} defaultExpanded> <CollapsibleCode label="Diff" text={text} defaultExpanded>
<pre className="max-h-48 overflow-auto rounded-md bg-zinc-900/80 px-3 py-2 font-mono text-[10px] leading-relaxed"> <pre className="max-h-48 overflow-auto rounded-md bg-[var(--color-surface-1)] px-3 py-2 font-mono text-[10px] leading-relaxed">
{lines.map((line, i) => { {lines.map((line, i) => {
let color = 'text-zinc-400'; let color = 'text-[var(--color-text-secondary)]';
if (line.startsWith('+')) color = 'text-emerald-400'; if (line.startsWith('+')) color = 'text-[var(--color-status-success)]';
else if (line.startsWith('-')) color = 'text-red-400'; else if (line.startsWith('-')) color = 'text-[var(--color-status-error)]';
else if (line.startsWith('@@')) color = 'text-blue-400'; else if (line.startsWith('@@')) color = 'text-[var(--color-accent-sky)]';
return ( return (
<div className={color} key={i}> <div className={color} key={i}>
{line} {line}
@@ -256,10 +256,10 @@ function CollapsibleCode({
const [expanded, setExpanded] = useState(defaultExpanded); const [expanded, setExpanded] = useState(defaultExpanded);
return ( return (
<div className="rounded-md border border-zinc-800/60 bg-zinc-900/40"> <div className="rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]">
<button <button
aria-expanded={expanded} aria-expanded={expanded}
className="flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-[10px] font-medium text-zinc-500 hover:text-zinc-400" className="flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-[10px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:text-[var(--color-text-secondary)]"
onClick={() => setExpanded(!expanded)} onClick={() => setExpanded(!expanded)}
type="button" type="button"
> >
@@ -269,9 +269,9 @@ function CollapsibleCode({
{label} {label}
</button> </button>
{expanded && ( {expanded && (
<div className="border-t border-zinc-800/40 px-2.5 py-1.5"> <div className="border-t border-[var(--color-border-subtle)] px-2.5 py-1.5">
{children ?? ( {children ?? (
<pre className="max-h-48 overflow-auto font-mono text-[10px] leading-relaxed text-zinc-300"> <pre className="max-h-48 overflow-auto font-mono text-[10px] leading-relaxed text-[var(--color-text-primary)]">
{text} {text}
</pre> </pre>
)} )}
@@ -283,9 +283,9 @@ function CollapsibleCode({
function MetaList({ label, items }: { label: string; items: string[] }) { function MetaList({ label, items }: { label: string; items: string[] }) {
return ( return (
<div className="text-[10px] text-zinc-500"> <div className="text-[10px] text-[var(--color-text-muted)]">
<span className="font-medium">{label}:</span>{' '} <span className="font-medium">{label}:</span>{' '}
<span className="text-zinc-400">{items.join(', ')}</span> <span className="text-[var(--color-text-secondary)]">{items.join(', ')}</span>
</div> </div>
); );
} }
@@ -16,21 +16,21 @@ export function PlanReviewBanner({
}, [planReview, onDismiss]); }, [planReview, onDismiss]);
return ( return (
<div className="rounded-xl border border-emerald-500/30 bg-emerald-500/5 px-4 py-3" role="alert"> <div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-status-success)] bg-[var(--color-glass)] px-4 py-3" role="alert">
{/* Header */} {/* Header */}
<div className="flex items-start gap-2.5"> <div className="flex items-start gap-2.5">
<ClipboardList className="mt-0.5 size-4 shrink-0 text-emerald-400" /> <ClipboardList className="mt-0.5 size-4 shrink-0 text-[var(--color-status-success)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[13px] font-semibold text-emerald-200">Plan ready for review</span> <span className="text-[13px] font-semibold text-[var(--color-status-success)]">Plan ready for review</span>
<span className="rounded-full bg-emerald-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-emerald-400"> <span className="rounded-full bg-[var(--color-status-success)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-success)]">
Plan mode Plan mode
</span> </span>
</div> </div>
<button <button
aria-label="Dismiss plan" aria-label="Dismiss plan"
className="rounded p-0.5 text-zinc-500 transition hover:bg-zinc-700/50 hover:text-zinc-300" className="rounded p-0.5 text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-primary)]"
onClick={handleDismiss} onClick={handleDismiss}
type="button" type="button"
> >
@@ -39,30 +39,30 @@ export function PlanReviewBanner({
</div> </div>
{planReview.agentName && ( {planReview.agentName && (
<div className="mt-1 text-[11px] text-zinc-400"> <div className="mt-1 text-[11px] text-[var(--color-text-secondary)]">
Agent: <span className="text-zinc-300">{planReview.agentName}</span> Agent: <span className="text-[var(--color-text-primary)]">{planReview.agentName}</span>
</div> </div>
)} )}
{/* Summary */} {/* Summary */}
{planReview.summary && ( {planReview.summary && (
<p className="mt-2 text-[13px] leading-relaxed text-zinc-200"> <p className="mt-2 text-[13px] leading-relaxed text-[var(--color-text-primary)]">
{planReview.summary} {planReview.summary}
</p> </p>
)} )}
{/* Plan content (rendered markdown) */} {/* Plan content (rendered markdown) */}
{planReview.planContent && ( {planReview.planContent && (
<div className="mt-3 max-h-80 overflow-y-auto rounded-lg border border-zinc-700/50 bg-zinc-900/60 p-3"> <div className="mt-3 max-h-80 overflow-y-auto rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-surface-1)] p-3">
<MarkdownContent content={planReview.planContent} /> <MarkdownContent content={planReview.planContent} />
</div> </div>
)} )}
{/* Guidance */} {/* Guidance */}
<p className="mt-3 text-[12px] leading-relaxed text-zinc-400"> <p className="mt-3 text-[12px] leading-relaxed text-[var(--color-text-secondary)]">
Send a follow-up message to proceed e.g.{' '} Send a follow-up message to proceed e.g.{' '}
<span className="text-zinc-300">&quot;implement the plan&quot;</span>,{' '} <span className="text-[var(--color-text-primary)]">&quot;implement the plan&quot;</span>,{' '}
<span className="text-zinc-300">&quot;adjust step 3&quot;</span>, or ask for a different approach. <span className="text-[var(--color-text-primary)]">&quot;adjust step 3&quot;</span>, or ask for a different approach.
</p> </p>
</div> </div>
</div> </div>
@@ -14,11 +14,11 @@ function formatElapsed(startedAt: string): string {
function StatusIcon({ status }: { status: ActiveSubagent['status'] }) { function StatusIcon({ status }: { status: ActiveSubagent['status'] }) {
switch (status) { switch (status) {
case 'running': case 'running':
return <Loader2 className="size-3.5 animate-spin text-sky-400" aria-label="Running" />; return <Loader2 className="size-3.5 animate-spin text-[var(--color-accent-sky)]" aria-label="Running" />;
case 'completed': case 'completed':
return <CheckCircle2 className="size-3.5 text-emerald-400" aria-label="Completed" />; return <CheckCircle2 className="size-3.5 text-[var(--color-status-success)]" aria-label="Completed" />;
case 'failed': case 'failed':
return <XCircle className="size-3.5 text-red-400" aria-label="Failed" />; return <XCircle className="size-3.5 text-[var(--color-status-error)]" aria-label="Failed" />;
} }
} }
@@ -31,7 +31,7 @@ function ElapsedTimer({ startedAt }: { startedAt: string }) {
}, [startedAt]); }, [startedAt]);
return ( return (
<span className="ml-auto shrink-0 text-[10px] tabular-nums text-zinc-600">{elapsed}</span> <span className="ml-auto shrink-0 text-[10px] tabular-nums text-[var(--color-text-muted)]">{elapsed}</span>
); );
} }
@@ -42,25 +42,25 @@ interface SubagentActivityCardProps {
function SubagentActivityCard({ subagent }: SubagentActivityCardProps) { function SubagentActivityCard({ subagent }: SubagentActivityCardProps) {
const borderClass = const borderClass =
subagent.status === 'running' subagent.status === 'running'
? 'border-sky-500/20' ? 'border-[var(--color-accent-sky)]/20'
: subagent.status === 'failed' : subagent.status === 'failed'
? 'border-red-500/20' ? 'border-[var(--color-status-error)]/20'
: 'border-emerald-500/20'; : 'border-[var(--color-status-success)]/20';
return ( return (
<div <div
className={`flex items-center gap-2 rounded-lg border bg-zinc-900/60 px-3 py-1.5 ${borderClass}`} className={`flex items-center gap-2 rounded-lg border bg-[var(--color-glass)] px-3 py-1.5 transition-all duration-200 ${borderClass}`}
role="status" role="status"
aria-label={`Sub-agent ${subagent.name}: ${subagent.activityLabel}`} aria-label={`Sub-agent ${subagent.name}: ${subagent.activityLabel}`}
> >
<StatusIcon status={subagent.status} /> <StatusIcon status={subagent.status} />
<Bot className="size-3 text-zinc-500" /> <Bot className="size-3 text-[var(--color-text-muted)]" />
<span className="text-[11px] font-medium text-zinc-300">{subagent.name}</span> <span className="text-[11px] font-medium text-[var(--color-text-primary)]">{subagent.name}</span>
<span className="text-[10px] text-zinc-600"></span> <span className="text-[10px] text-[var(--color-text-muted)]"></span>
<span className="text-[10px] text-zinc-500">{subagent.activityLabel}</span> <span className="text-[10px] text-[var(--color-text-secondary)]">{subagent.activityLabel}</span>
{subagent.status === 'running' && <ElapsedTimer startedAt={subagent.startedAt} />} {subagent.status === 'running' && <ElapsedTimer startedAt={subagent.startedAt} />}
{subagent.error && ( {subagent.error && (
<span className="truncate text-[10px] text-red-400" title={subagent.error}> <span className="truncate text-[10px] text-[var(--color-status-error)]" title={subagent.error}>
{subagent.error} {subagent.error}
</span> </span>
)} )}
@@ -1,9 +1,9 @@
export function ThinkingDots() { export function ThinkingDots() {
return ( return (
<div className="flex items-center gap-1.5" aria-label="Thinking"> <div className="flex items-center gap-1.5" aria-label="Thinking">
<span className="thinking-dot size-2 rounded-full bg-zinc-500" /> <span className="thinking-dot size-2 rounded-full bg-[var(--color-accent)]" />
<span className="thinking-dot size-2 rounded-full bg-zinc-500" /> <span className="thinking-dot size-2 rounded-full bg-[var(--color-accent-sky)]" />
<span className="thinking-dot size-2 rounded-full bg-zinc-500" /> <span className="thinking-dot size-2 rounded-full bg-[var(--color-accent-purple)]" />
</div> </div>
); );
} }
@@ -42,25 +42,25 @@ export function UserInputBanner({
); );
return ( return (
<div className="rounded-xl border border-blue-500/30 bg-blue-500/5 px-4 py-3" role="alert"> <div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-accent-sky)] bg-[var(--color-glass)] px-4 py-3" role="alert">
{/* Header */} {/* Header */}
<div className="flex items-start gap-2.5"> <div className="flex items-start gap-2.5">
<MessageCircleQuestion className="mt-0.5 size-4 shrink-0 text-blue-400" /> <MessageCircleQuestion className="mt-0.5 size-4 shrink-0 text-[var(--color-accent-sky)]" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[13px] font-semibold text-blue-200">Agent question</span> <span className="text-[13px] font-semibold text-[var(--color-accent-sky)]">Agent question</span>
<span className="rounded-full bg-blue-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-blue-400"> <span className="rounded-full bg-[var(--color-accent-sky)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-accent-sky)]">
User input User input
</span> </span>
</div> </div>
{userInput.agentName && ( {userInput.agentName && (
<div className="mt-1 text-[11px] text-zinc-400"> <div className="mt-1 text-[11px] text-[var(--color-text-secondary)]">
Agent: <span className="text-zinc-300">{userInput.agentName}</span> Agent: <span className="text-[var(--color-text-primary)]">{userInput.agentName}</span>
</div> </div>
)} )}
<p className="mt-2 text-[13px] leading-relaxed text-zinc-200 whitespace-pre-wrap"> <p className="mt-2 text-[13px] leading-relaxed text-[var(--color-text-primary)] whitespace-pre-wrap">
{userInput.question} {userInput.question}
</p> </p>
</div> </div>
@@ -71,7 +71,7 @@ export function UserInputBanner({
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
{userInput.choices!.map((choice) => ( {userInput.choices!.map((choice) => (
<button <button
className="rounded-lg border border-blue-500/30 bg-blue-500/10 px-3.5 py-1.5 text-[12px] font-medium text-blue-200 transition hover:border-blue-400/50 hover:bg-blue-500/20 hover:text-white disabled:cursor-not-allowed disabled:opacity-50" className="rounded-lg border border-[var(--color-accent-sky)]/30 bg-[var(--color-accent-sky)]/10 px-3.5 py-1.5 text-[12px] font-medium text-[var(--color-accent-sky)] transition-all duration-200 hover:border-[var(--color-accent-sky)]/50 hover:bg-[var(--color-accent-sky)]/20 hover:text-[var(--color-text-primary)] disabled:cursor-not-allowed disabled:opacity-50"
disabled={isSubmitting} disabled={isSubmitting}
key={choice} key={choice}
onClick={() => handleChoiceClick(choice)} onClick={() => handleChoiceClick(choice)}
@@ -88,7 +88,7 @@ export function UserInputBanner({
<div className="mt-3 flex items-center gap-2"> <div className="mt-3 flex items-center gap-2">
<input <input
aria-label="Type your answer" aria-label="Type your answer"
className="min-w-0 flex-1 rounded-lg border border-zinc-700 bg-zinc-900/60 px-3 py-1.5 text-[13px] text-zinc-200 placeholder-zinc-500 outline-none transition focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/30 disabled:cursor-not-allowed disabled:opacity-50" className="min-w-0 flex-1 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-1.5 text-[13px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition-all duration-200 focus:border-[var(--color-border-glow)] focus:ring-1 focus:ring-[var(--color-border-glow)] disabled:cursor-not-allowed disabled:opacity-50"
disabled={isSubmitting} disabled={isSubmitting}
onChange={(e) => setFreeformText(e.target.value)} onChange={(e) => setFreeformText(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
@@ -98,7 +98,7 @@ export function UserInputBanner({
/> />
<button <button
aria-label="Submit answer" aria-label="Submit answer"
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-3.5 py-1.5 text-[12px] font-medium text-white transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50" className="brand-gradient-bg inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-[12px] font-medium text-white transition-all duration-200 hover:brightness-110 disabled:cursor-not-allowed disabled:opacity-50"
disabled={isSubmitting || !freeformText.trim()} disabled={isSubmitting || !freeformText.trim()}
onClick={handleFreeformSubmit} onClick={handleFreeformSubmit}
type="button" type="button"
@@ -16,9 +16,9 @@ const kindIcons: Record<PatternGraphNodeKind, typeof CircleUser> = {
}; };
const kindColors: Record<PatternGraphNodeKind, { bg: string; border: string; text: string }> = { const kindColors: Record<PatternGraphNodeKind, { bg: string; border: string; text: string }> = {
'user-input': { bg: 'bg-indigo-500/10', border: 'border-indigo-500/30', text: 'text-indigo-400' }, 'user-input': { bg: 'bg-[var(--color-accent)]/10', border: 'border-[var(--color-accent)]/30', text: 'text-[var(--color-accent-sky)]' },
'user-output': { bg: 'bg-indigo-500/10', border: 'border-indigo-500/30', text: 'text-indigo-400' }, 'user-output': { bg: 'bg-[var(--color-accent)]/10', border: 'border-[var(--color-accent)]/30', text: 'text-[var(--color-accent-sky)]' },
agent: { bg: 'bg-zinc-800/80', border: 'border-zinc-600/40', text: 'text-zinc-200' }, agent: { bg: 'bg-[var(--color-surface-2)]/80', border: 'border-[var(--color-border)]/40', text: 'text-[var(--color-text-primary)]' },
distributor: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' }, distributor: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' },
collector: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' }, collector: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' },
orchestrator: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400' }, orchestrator: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400' },
@@ -38,9 +38,9 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
return ( return (
<div <div
className={`flex min-w-[120px] items-center gap-2 rounded-xl border px-3 py-2 shadow-md transition ${ className={`flex min-w-[120px] items-center gap-2 rounded-xl border px-3 py-2 shadow-md backdrop-blur-sm transition ${
colors.bg colors.bg
} ${selected ? 'ring-2 ring-indigo-500/50' : ''} ${colors.border}`} } ${selected ? 'ring-2 ring-[var(--color-accent)]/50' : ''} ${colors.border}`}
> >
{renderIcon()} {renderIcon()}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@@ -48,11 +48,11 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
{data.label} {data.label}
</div> </div>
{isAgent && data.modelLabel && ( {isAgent && data.modelLabel && (
<div className="truncate text-[10px] text-zinc-500">{data.modelLabel}</div> <div className="truncate text-[10px] text-[var(--color-text-muted)]">{data.modelLabel}</div>
)} )}
</div> </div>
{data.readOnly && ( {data.readOnly && (
<span className="ml-1 rounded bg-zinc-700/50 px-1 py-0.5 text-[8px] font-medium text-zinc-500"> <span className="ml-1 rounded bg-[var(--color-surface-3)]/50 px-1 py-0.5 text-[8px] font-medium text-[var(--color-text-muted)]">
SYS SYS
</span> </span>
)} )}
@@ -61,8 +61,8 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
} }
const handleStyles = { const handleStyles = {
system: '!size-2 !border-zinc-600 !bg-zinc-400', system: '!size-2 !border-[var(--color-border)] !bg-[var(--color-text-secondary)]',
agent: '!size-2 !border-indigo-400 !bg-indigo-500', agent: '!size-2 !border-[var(--color-accent-sky)] !bg-[var(--color-accent)]',
hidden: '!size-0 !border-0 !bg-transparent !min-w-0 !min-h-0', hidden: '!size-0 !border-0 !bg-transparent !min-w-0 !min-h-0',
}; };
@@ -169,7 +169,7 @@ function PatternGraphCanvasInner({
}, [graph, onGraphChange, fitView]); }, [graph, onGraphChange, fitView]);
return ( return (
<div className="h-full w-full rounded-xl border border-zinc-800 bg-zinc-950/50"> <div className="h-full w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-0)]/50">
<ReactFlow <ReactFlow
nodes={nodes.map((n) => ({ nodes={nodes.map((n) => ({
...n, ...n,
@@ -189,18 +189,18 @@ function PatternGraphCanvasInner({
proOptions={{ hideAttribution: true }} proOptions={{ hideAttribution: true }}
defaultEdgeOptions={{ defaultEdgeOptions={{
type: 'default', type: 'default',
style: { stroke: '#6366f1', strokeWidth: 1.5 }, style: { stroke: '#245CF9', strokeWidth: 1.5 },
markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color: '#6366f1' }, markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color: '#245CF9' },
}} }}
connectionLineStyle={{ stroke: '#6366f1', strokeWidth: 1.5 }} connectionLineStyle={{ stroke: '#245CF9', strokeWidth: 1.5 }}
deleteKeyCode="Delete" deleteKeyCode="Delete"
> >
<Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#27272a" /> <Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#1a1e2e" />
<Panel position="top-right"> <Panel position="top-right">
<button <button
type="button" type="button"
onClick={handleAutoLayout} onClick={handleAutoLayout}
className="flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800/90 px-2.5 py-1.5 text-[11px] font-medium text-zinc-300 shadow-sm backdrop-blur transition hover:border-zinc-600 hover:bg-zinc-700/90 hover:text-zinc-100" className="flex items-center gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)]/90 px-2.5 py-1.5 text-[11px] font-medium text-[var(--color-text-secondary)] shadow-sm backdrop-blur transition hover:border-[var(--color-border-glow)] hover:bg-[var(--color-surface-3)]/90 hover:text-[var(--color-text-primary)]"
title="Auto-layout nodes" title="Auto-layout nodes"
> >
<LayoutGrid className="size-3.5" /> <LayoutGrid className="size-3.5" />
@@ -44,10 +44,10 @@ function InputField({
placeholder?: string; placeholder?: string;
}) { }) {
const base = const base =
'w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 placeholder-zinc-600 outline-none transition focus:border-indigo-500/50'; 'w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition focus:border-[var(--color-accent)]/50';
return ( return (
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">{label}</span> <span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
{multiline ? ( {multiline ? (
<textarea <textarea
className={`${base} min-h-20 resize-y`} className={`${base} min-h-20 resize-y`}
@@ -97,17 +97,17 @@ function SystemNodeInspector({ kind }: { kind: PatternGraphNodeKind }) {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<div className="flex size-8 items-center justify-center rounded-lg bg-indigo-500/10"> <div className="flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent)]/10">
<Icon className="size-4 text-indigo-400" /> <Icon className="size-4 text-[var(--color-accent-sky)]" />
</div> </div>
<div> <div>
<div className="text-[13px] font-semibold text-zinc-200">{kindLabels[kind]}</div> <div className="text-[13px] font-semibold text-[var(--color-text-primary)]">{kindLabels[kind]}</div>
<span className="rounded bg-zinc-700/50 px-1.5 py-0.5 text-[9px] font-medium text-zinc-500"> <span className="rounded bg-[var(--color-surface-3)]/50 px-1.5 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]">
System node System node
</span> </span>
</div> </div>
</div> </div>
<p className="text-[12px] leading-relaxed text-zinc-500">{kindDescriptions[kind]}</p> <p className="text-[12px] leading-relaxed text-[var(--color-text-muted)]">{kindDescriptions[kind]}</p>
</div> </div>
); );
} }
@@ -140,16 +140,16 @@ function AgentNodeInspector({
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<div className="flex size-8 items-center justify-center rounded-lg bg-zinc-800"> <div className="flex size-8 items-center justify-center rounded-lg bg-[var(--color-surface-2)]">
<Bot className="size-4 text-zinc-300" /> <Bot className="size-4 text-[var(--color-text-secondary)]" />
</div> </div>
<div className="text-[13px] font-semibold text-zinc-200">{agent.name || 'Unnamed'}</div> <div className="text-[13px] font-semibold text-[var(--color-text-primary)]">{agent.name || 'Unnamed'}</div>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{showReorder && ( {showReorder && (
<> <>
<button <button
className="flex size-6 items-center justify-center rounded text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-zinc-500" className="flex size-6 items-center justify-center rounded text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)] disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-[var(--color-text-muted)]"
disabled={!canUp} disabled={!canUp}
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'up'))} onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'up'))}
title="Move earlier in sequence" title="Move earlier in sequence"
@@ -158,7 +158,7 @@ function AgentNodeInspector({
<ChevronUp className="size-3.5" /> <ChevronUp className="size-3.5" />
</button> </button>
<button <button
className="flex size-6 items-center justify-center rounded text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-zinc-500" className="flex size-6 items-center justify-center rounded text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)] disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-[var(--color-text-muted)]"
disabled={!canDown} disabled={!canDown}
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'down'))} onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'down'))}
title="Move later in sequence" title="Move later in sequence"
@@ -169,7 +169,7 @@ function AgentNodeInspector({
</> </>
)} )}
<button <button
className="flex items-center gap-1 text-[12px] text-zinc-600 transition hover:text-red-400" className="flex items-center gap-1 text-[12px] text-[var(--color-text-muted)] transition hover:text-red-400"
onClick={() => onAgentRemove(agent.id)} onClick={() => onAgentRemove(agent.id)}
type="button" type="button"
> >
@@ -235,7 +235,7 @@ export function PatternGraphInspector({
if (!selectedNodeId) { if (!selectedNodeId) {
return ( return (
<div className="flex h-full items-center justify-center p-4"> <div className="flex h-full items-center justify-center p-4">
<p className="text-center text-[12px] text-zinc-600"> <p className="text-center text-[12px] text-[var(--color-text-muted)]">
Select a node on the graph to inspect it Select a node on the graph to inspect it
</p> </p>
</div> </div>
@@ -29,7 +29,7 @@ export function LspProfileEditor({
title={profile.name || 'Untitled LSP Profile'} title={profile.name || 'Untitled LSP Profile'}
> >
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
General General
</h4> </h4>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
@@ -50,7 +50,7 @@ export function LspProfileEditor({
</section> </section>
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Server Server
</h4> </h4>
<FormField label="Command" required> <FormField label="Command" required>
@@ -71,7 +71,7 @@ export function LspProfileEditor({
</section> </section>
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
File matching File matching
</h4> </h4>
<FormField label="File extensions" required> <FormField label="File extensions" required>
@@ -29,7 +29,7 @@ export function McpServerEditor({
title={server.name || 'Untitled MCP Server'} title={server.name || 'Untitled MCP Server'}
> >
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
General General
</h4> </h4>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
@@ -54,7 +54,7 @@ export function McpServerEditor({
</section> </section>
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
{server.transport === 'local' ? 'Process' : 'Endpoint'} {server.transport === 'local' ? 'Process' : 'Endpoint'}
</h4> </h4>
{server.transport === 'local' ? ( {server.transport === 'local' ? (
@@ -94,7 +94,7 @@ export function McpServerEditor({
</section> </section>
<section className="space-y-4"> <section className="space-y-4">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500"> <h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Advanced Advanced
</h4> </h4>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
@@ -25,21 +25,21 @@ export function ToolingEditorShell({
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] px-5 pb-3 pt-3"> <div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] px-5 pb-3 pt-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200" className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
onClick={onBack} onClick={onBack}
type="button" type="button"
> >
<ChevronLeft className="size-4" /> <ChevronLeft className="size-4" />
</button> </button>
<div> <div>
<h2 className="text-[13px] font-semibold text-zinc-100">{title}</h2> <h2 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h2>
<p className="text-[12px] text-zinc-500">{subtitle}</p> <p className="text-[12px] text-[var(--color-text-muted)]">{subtitle}</p>
</div> </div>
</div> </div>
<div className="no-drag flex items-center gap-2"> <div className="no-drag flex items-center gap-2">
{onDelete && ( {onDelete && (
<button <button
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-red-400 transition hover:bg-red-500/10" className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-[var(--color-status-error)] transition-all duration-200 hover:bg-[var(--color-status-error)]/10"
onClick={() => void onDelete()} onClick={() => void onDelete()}
type="button" type="button"
> >
@@ -48,7 +48,7 @@ export function ToolingEditorShell({
</button> </button>
)} )}
<button <button
className="rounded-lg bg-indigo-600 px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-40" className="rounded-lg bg-[var(--color-accent)] px-4 py-1.5 text-[13px] font-medium text-white transition-all duration-200 hover:bg-[var(--color-accent-sky)] disabled:cursor-not-allowed disabled:opacity-40"
disabled={disableSave} disabled={disableSave}
onClick={() => void onSave()} onClick={() => void onSave()}
type="button" type="button"
@@ -61,7 +61,7 @@ export function ToolingEditorShell({
<div className="flex-1 overflow-y-auto px-6 py-5"> <div className="flex-1 overflow-y-auto px-6 py-5">
<div className="mx-auto max-w-2xl space-y-8"> <div className="mx-auto max-w-2xl space-y-8">
{error && ( {error && (
<div className="flex items-start gap-2 rounded-lg bg-amber-500/10 px-3 py-2 text-[13px] text-amber-300"> <div className="flex items-start gap-2 rounded-lg bg-[var(--color-status-warning)]/10 px-3 py-2 text-[13px] text-[var(--color-status-warning)]">
<AlertCircle className="mt-0.5 size-3.5 shrink-0" /> <AlertCircle className="mt-0.5 size-3.5 shrink-0" />
{error} {error}
</div> </div>
+2 -2
View File
@@ -11,9 +11,9 @@ export function FormField({
}) { }) {
return ( return (
<label className="block"> <label className="block">
<span className="mb-1.5 block text-[12px] font-medium text-zinc-400"> <span className="mb-1.5 block text-[12px] font-medium text-[var(--color-text-secondary)]">
{label} {label}
{required && <span className="ml-1 text-amber-400">*</span>} {required && <span className="ml-1 text-[var(--color-status-warning)]">*</span>}
</span> </span>
{children} {children}
</label> </label>
+2 -2
View File
@@ -3,8 +3,8 @@ import type { ReactNode } from 'react';
export function InfoCallout({ children }: { children: ReactNode }) { export function InfoCallout({ children }: { children: ReactNode }) {
return ( return (
<div className="flex items-start gap-2.5 rounded-lg border border-zinc-800 bg-zinc-900/30 px-3 py-2.5 text-[12px] leading-relaxed text-zinc-500"> <div className="flex items-start gap-2.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-glass)] px-3 py-2.5 text-[12px] leading-relaxed text-[var(--color-text-secondary)] backdrop-blur-sm">
<Info className="mt-0.5 size-3.5 shrink-0 text-zinc-600" /> <Info className="mt-0.5 size-3.5 shrink-0 text-[var(--color-accent)]" />
<span>{children}</span> <span>{children}</span>
</div> </div>
); );
@@ -10,13 +10,13 @@ export interface PopoverToggleRowProps {
export function PopoverToggleRow({ label, detail, enabled, onToggle }: PopoverToggleRowProps) { export function PopoverToggleRow({ label, detail, enabled, onToggle }: PopoverToggleRowProps) {
return ( return (
<button <button
className="flex w-full items-center gap-2 px-3 py-1.5 text-left transition hover:bg-zinc-800" className="flex w-full items-center gap-2 px-3 py-1.5 text-left transition-all duration-150 hover:bg-[var(--color-surface-2)]"
onClick={onToggle} onClick={onToggle}
type="button" type="button"
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="truncate text-[12px] font-medium text-zinc-300">{label}</div> <div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">{label}</div>
{detail && <div className="truncate text-[10px] text-zinc-600">{detail}</div>} {detail && <div className="truncate text-[10px] text-[var(--color-text-muted)]">{detail}</div>}
</div> </div>
<ToggleSwitch enabled={enabled} size="sm" /> <ToggleSwitch enabled={enabled} size="sm" />
</button> </button>
+1 -1
View File
@@ -9,7 +9,7 @@ export function SelectInput({
}) { }) {
return ( return (
<select <select
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 outline-none transition focus:border-indigo-500/50" className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 focus:border-[var(--color-border-glow)] focus:shadow-[0_0_0_1px_rgba(36,92,249,0.15),0_0_12px_rgba(36,92,249,0.08)]"
onChange={(event) => onChange(event.target.value)} onChange={(event) => onChange(event.target.value)}
value={value} value={value}
> >
+1 -1
View File
@@ -13,7 +13,7 @@ export function TextInput({
}) { }) {
return ( return (
<input <input
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-indigo-500/50" className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-border-glow)] focus:shadow-[0_0_0_1px_rgba(36,92,249,0.15),0_0_12px_rgba(36,92,249,0.08)]"
inputMode={inputMode} inputMode={inputMode}
onChange={(event) => onChange(event.target.value)} onChange={(event) => onChange(event.target.value)}
placeholder={placeholder} placeholder={placeholder}
+1 -1
View File
@@ -11,7 +11,7 @@ export function TextareaInput({
}) { }) {
return ( return (
<textarea <textarea
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-indigo-500/50" className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-border-glow)] focus:shadow-[0_0_0_1px_rgba(36,92,249,0.15),0_0_12px_rgba(36,92,249,0.08)]"
onChange={(event) => onChange(event.target.value)} onChange={(event) => onChange(event.target.value)}
placeholder={placeholder} placeholder={placeholder}
rows={rows} rows={rows}
+2 -2
View File
@@ -10,8 +10,8 @@ export function ToggleSwitch({ enabled, size = 'md' }: ToggleSwitchProps) {
return ( return (
<span <span
className={`relative inline-flex ${trackSize} shrink-0 items-center rounded-full transition-colors ${ className={`relative inline-flex ${trackSize} shrink-0 items-center rounded-full transition-all duration-200 ${
enabled ? 'bg-indigo-500' : 'bg-zinc-700' enabled ? 'brand-gradient-bg shadow-[0_0_8px_rgba(36,92,249,0.3)]' : 'bg-[var(--color-surface-3)]'
}`} }`}
> >
<span <span
+1 -1
View File
@@ -8,7 +8,7 @@
/> />
<title>aryx</title> <title>aryx</title>
</head> </head>
<body class="bg-slate-950 text-slate-100"> <body class="bg-[var(--color-surface-0)] text-[var(--color-text-primary)]">
<div id="root"></div> <div id="root"></div>
<script <script
type="module" type="module"
+4
View File
@@ -1,6 +1,10 @@
import { StrictMode } from 'react'; import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import '@fontsource-variable/outfit';
import '@fontsource-variable/dm-sans';
import '@fontsource-variable/jetbrains-mono';
import App from '@renderer/App'; import App from '@renderer/App';
import '@renderer/styles.css'; import '@renderer/styles.css';
+279 -129
View File
@@ -1,79 +1,133 @@
@import "tailwindcss"; @import "tailwindcss";
/* ── Design tokens: "Luminous Depth" ────────────────────────── */
@theme { @theme {
--color-surface-0: #09090b; /* Surface palette — blue-tinted darks */
--color-surface-1: #18181b; --color-surface-0: #07080e;
--color-surface-2: #27272a; --color-surface-1: #0e1019;
--color-surface-3: #3f3f46; --color-surface-2: #161924;
--color-border: #27272a; --color-surface-3: #1e2233;
--color-border-subtle: #1e1e22; --color-surface-4: #272b3d;
--color-accent: #6366f1; --color-border: #1a1e2e;
--color-accent-hover: #818cf8; --color-border-subtle: #121520;
--color-accent-muted: rgba(99, 102, 241, 0.15);
/* Brand accents — derived from logo */
--color-accent: #245CF9;
--color-accent-hover: #4b7bfa;
--color-accent-muted: rgba(36, 92, 249, 0.12);
--color-accent-sky: #248CFD;
--color-accent-indigo: #0E13A4;
--color-accent-purple: #8A29E6;
--color-accent-royal: #5A20E0;
--color-accent-violet: #3615CF;
/* Text palette */
--color-text-primary: #e8eaf0;
--color-text-secondary: #8b90a5;
--color-text-muted: #4e5368;
--color-text-accent: #7ba1fc;
/* Status palette */
--color-status-success: #34d399;
--color-status-warning: #fbbf24;
--color-status-error: #f87171;
--color-status-info: #248CFD;
/* Glow / glass effects */
--color-border-glow: rgba(36, 92, 249, 0.25);
--color-glass: rgba(14, 16, 25, 0.7);
--color-glass-hover: rgba(22, 25, 36, 0.8);
--color-glass-border: rgba(26, 30, 46, 0.6);
/* Pill text */
--text-pill: 0.6875rem; --text-pill: 0.6875rem;
--text-pill--line-height: 1.2; --text-pill--line-height: 1.2;
} }
:root { :root {
color-scheme: dark; color-scheme: dark;
font-family: font-family: "DM Sans Variable", "DM Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
--font-display: "Outfit Variable", "Outfit", ui-sans-serif, system-ui, sans-serif;
--font-body: "DM Sans Variable", "DM Sans", ui-sans-serif, system-ui, sans-serif;
--font-mono: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
/* Gradient presets */
--gradient-brand: linear-gradient(135deg, #245CF9, #8A29E6);
--gradient-brand-hover: linear-gradient(135deg, #4b7bfa, #a04ff0);
--gradient-surface: linear-gradient(180deg, #0e1019, #07080e);
--gradient-glow: radial-gradient(ellipse at center, rgba(36, 92, 249, 0.06), transparent 70%);
--gradient-border: linear-gradient(135deg, #245CF9, #3615CF);
} }
/* ── Light mode colour overrides ────────────────────────────── */ /* ── Light mode overrides ────────────────────────────────────── */
[data-theme="light"] { [data-theme="light"] {
color-scheme: light; color-scheme: light;
/* Remap semantic surface tokens */ /* Surfaces — cool-tinted whites */
--color-surface-0: #ffffff; --color-surface-0: #f8f9fc;
--color-surface-1: #f4f4f5; --color-surface-1: #f0f1f6;
--color-surface-2: #e4e4e7; --color-surface-2: #e6e8f0;
--color-surface-3: #d4d4d8; --color-surface-3: #d8dbe6;
--color-border: #e4e4e7; --color-surface-4: #c8cdd9;
--color-border-subtle: #f0f0f2; --color-border: #dfe1ea;
--color-accent-muted: rgba(99, 102, 241, 0.10); --color-border-subtle: #eeeff4;
/* Swap zinc scale: dark values become light equivalents. /* Text — deep blue-blacks */
Tailwind v4 exposes these as --color-zinc-{step}. */ --color-text-primary: #0f1729;
--color-zinc-50: #18181b; --color-text-secondary: #5a6078;
--color-zinc-100: #27272a; --color-text-muted: #9298ad;
--color-zinc-200: #3f3f46; --color-text-accent: #245CF9;
--color-zinc-300: #52525b;
--color-zinc-400: #71717a; /* Glass — light translucency */
--color-zinc-500: #71717a; --color-glass: rgba(240, 241, 246, 0.75);
--color-zinc-600: #a1a1aa; --color-glass-hover: rgba(230, 232, 240, 0.8);
--color-zinc-700: #d4d4d8; --color-glass-border: rgba(223, 225, 234, 0.6);
--color-zinc-800: #e4e4e7; --color-border-glow: rgba(36, 92, 249, 0.2);
--color-zinc-900: #f4f4f5; --color-accent-muted: rgba(36, 92, 249, 0.08);
--color-zinc-950: #ffffff;
/* Gradients — lighter versions */
--gradient-glow: radial-gradient(ellipse at center, rgba(36, 92, 249, 0.04), transparent 70%);
--gradient-surface: linear-gradient(180deg, #f0f1f6, #f8f9fc);
/* Remap Tailwind zinc scale for light mode */
--color-zinc-50: #0f1729;
--color-zinc-100: #1e2233;
--color-zinc-200: #3a3f54;
--color-zinc-300: #5a6078;
--color-zinc-400: #7a7f94;
--color-zinc-500: #8b90a5;
--color-zinc-600: #9298ad;
--color-zinc-700: #c8cdd9;
--color-zinc-800: #dfe1ea;
--color-zinc-900: #f0f1f6;
--color-zinc-950: #f8f9fc;
/* Keep black/white correct for light mode */
--color-black: #000000; --color-black: #000000;
--color-white: #ffffff; --color-white: #ffffff;
} }
/* ── Global base styles ──────────────────────────────────────── */
body { body {
margin: 0; margin: 0;
min-height: 100vh; min-height: 100vh;
background: var(--color-surface-0); background: var(--color-surface-0);
color: var(--color-text-primary);
} }
#root { #root {
min-height: 100vh; min-height: 100vh;
} }
/* Scrollbar styling */ /* ── Scrollbar ───────────────────────────────────────────────── */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 5px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
@@ -86,14 +140,11 @@ body {
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-hover, #52525b); background: var(--color-surface-4);
} }
[data-theme="light"] { /* ── Window drag regions ─────────────────────────────────────── */
--scrollbar-hover: #a1a1aa;
}
/* Window drag regions for custom title bar */
.drag-region { .drag-region {
-webkit-app-region: drag; -webkit-app-region: drag;
} }
@@ -102,22 +153,58 @@ body {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
/* Thinking dots animation */ /* ── Utility classes ─────────────────────────────────────────── */
.glass-surface {
background: var(--color-glass);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--color-glass-border);
}
.glow-border {
border: 1px solid var(--color-border-glow);
box-shadow: 0 0 12px rgba(36, 92, 249, 0.08), inset 0 0 12px rgba(36, 92, 249, 0.04);
}
.brand-gradient-text {
background: var(--gradient-brand);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.brand-gradient-bg {
background: var(--gradient-brand);
}
.brand-gradient-bg:hover {
background: var(--gradient-brand-hover);
}
.font-display {
font-family: var(--font-display);
}
.font-mono {
font-family: var(--font-mono);
}
/* ── Thinking dots animation ─────────────────────────────────── */
@keyframes thinking-dot { @keyframes thinking-dot {
0%, 0%, 80%, 100% {
60%, opacity: 0.25;
100% {
opacity: 0.3;
transform: scale(0.8); transform: scale(0.8);
} }
30% { 40% {
opacity: 1; opacity: 1;
transform: scale(1); transform: scale(1.1);
} }
} }
.thinking-dot { .thinking-dot {
animation: thinking-dot 1.4s ease-in-out infinite; animation: thinking-dot 1.6s ease-in-out infinite;
} }
.thinking-dot:nth-child(2) { .thinking-dot:nth-child(2) {
@@ -128,14 +215,38 @@ body {
animation-delay: 0.4s; animation-delay: 0.4s;
} }
/* Auto-resize textarea helper */ /* Flowing gradient wave for thinking state */
@keyframes thinking-wave {
0% {
background-position: 200% center;
}
100% {
background-position: -200% center;
}
}
.thinking-wave {
background: linear-gradient(
90deg,
transparent 0%,
rgba(36, 92, 249, 0.3) 25%,
rgba(138, 41, 230, 0.3) 50%,
rgba(36, 140, 253, 0.3) 75%,
transparent 100%
);
background-size: 200% 100%;
animation: thinking-wave 3s ease-in-out infinite;
}
/* ── Auto-resize textarea ────────────────────────────────────── */
.auto-resize-textarea { .auto-resize-textarea {
field-sizing: content; field-sizing: content;
min-height: 44px; min-height: 44px;
max-height: 200px; max-height: 200px;
} }
/* ── Markdown composer (Lexical) ────────────────────────── */ /* ── Markdown composer (Lexical) ─────────────────────────────── */
.markdown-composer-content { .markdown-composer-content {
position: relative; position: relative;
@@ -146,9 +257,9 @@ body {
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
padding: 10px 16px 4px 16px; padding: 10px 16px 4px 16px;
font-size: 14px; font-size: 13.5px;
line-height: 1.6; line-height: 1.6;
color: #f4f4f5; color: var(--color-text-primary);
outline: none; outline: none;
cursor: text; cursor: text;
} }
@@ -162,9 +273,9 @@ body {
position: absolute; position: absolute;
top: 10px; top: 10px;
left: 16px; left: 16px;
font-size: 14px; font-size: 13.5px;
line-height: 1.6; line-height: 1.6;
color: #52525b; color: var(--color-text-muted);
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
} }
@@ -176,6 +287,7 @@ body {
/* Headings */ /* Headings */
.mc-h1, .mc-h2, .mc-h3 { .mc-h1, .mc-h2, .mc-h3 {
font-family: var(--font-display);
font-weight: 600; font-weight: 600;
margin: 0; margin: 0;
line-height: 1.4; line-height: 1.4;
@@ -190,11 +302,11 @@ body {
.mc-strikethrough { text-decoration: line-through; } .mc-strikethrough { text-decoration: line-through; }
.mc-underline { text-decoration: underline; } .mc-underline { text-decoration: underline; }
.mc-inline-code { .mc-inline-code {
background: rgba(63, 63, 70, 0.6); background: rgba(30, 34, 51, 0.7);
border-radius: 3px; border-radius: 4px;
padding: 1px 4px; padding: 1px 5px;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; font-family: var(--font-mono);
font-size: 0.9em; font-size: 0.88em;
} }
/* Lists */ /* Lists */
@@ -214,28 +326,28 @@ body {
/* Blockquote */ /* Blockquote */
.mc-blockquote { .mc-blockquote {
border-left: 3px solid #3f3f46; border-left: 3px solid var(--color-surface-4);
padding-left: 0.75em; padding-left: 0.75em;
color: #a1a1aa; color: var(--color-text-secondary);
margin: 2px 0; margin: 2px 0;
} }
/* Code block */ /* Code block */
.mc-code-block { .mc-code-block {
display: block; display: block;
background: rgba(24, 24, 27, 0.8); background: rgba(7, 8, 14, 0.8);
border: 1px solid #27272a; border: 1px solid var(--color-border);
border-radius: 6px; border-radius: 8px;
padding: 26px 10px 6px 10px; padding: 26px 12px 8px 12px;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; font-family: var(--font-mono);
font-size: 0.9em; font-size: 0.88em;
white-space: pre-wrap; white-space: pre-wrap;
tab-size: 2; tab-size: 2;
margin: 4px 0; margin: 4px 0;
overflow-x: auto; overflow-x: auto;
} }
/* Language selector overlays positioned outside Lexical-managed DOM */ /* Language selector overlays */
.mc-code-overlays-container { .mc-code-overlays-container {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -256,74 +368,76 @@ body {
.mc-code-lang-select { .mc-code-lang-select {
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
background: rgba(39, 39, 42, 0.9); background: var(--color-surface-2);
border: 1px solid #3f3f46; border: 1px solid var(--color-border);
border-radius: 4px; border-radius: 4px;
color: #a1a1aa; color: var(--color-text-secondary);
font-family: inherit; font-family: inherit;
font-size: 10px; font-size: 10px;
padding: 2px 18px 2px 6px; padding: 2px 18px 2px 6px;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238b90a5' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 3px center; background-position: right 3px center;
background-size: 10px; background-size: 10px;
} }
.mc-code-lang-select:hover { .mc-code-lang-select:hover {
border-color: #52525b; border-color: var(--color-surface-4);
color: #d4d4d8; color: var(--color-text-primary);
} }
.mc-code-lang-select:focus { .mc-code-lang-select:focus {
border-color: rgba(99, 102, 241, 0.5); border-color: var(--color-border-glow);
} }
/* Link */ /* Link */
.mc-link { .mc-link {
color: #818cf8; color: var(--color-text-accent);
text-decoration: underline; text-decoration: underline;
text-underline-offset: 2px; text-underline-offset: 2px;
} }
/* Syntax highlighting tokens (dark theme) */ /* ── Syntax highlighting tokens ──────────────────────────────── */
.mc-tok-comment { color: #6b7280; font-style: italic; }
.mc-tok-keyword { color: #c084fc; } .mc-tok-comment { color: #5a6078; font-style: italic; }
.mc-tok-string { color: #86efac; } .mc-tok-keyword { color: #b07ef5; }
.mc-tok-number { color: #fbbf24; } .mc-tok-string { color: #6ee7b7; }
.mc-tok-boolean { color: #fbbf24; } .mc-tok-number { color: #f9c846; }
.mc-tok-constant { color: #fbbf24; } .mc-tok-boolean { color: #f9c846; }
.mc-tok-function { color: #93c5fd; } .mc-tok-constant { color: #f9c846; }
.mc-tok-builtin { color: #67e8f9; } .mc-tok-function { color: #7ba1fc; }
.mc-tok-operator { color: #f9a8d4; } .mc-tok-builtin { color: #5ccbee; }
.mc-tok-punctuation { color: #a1a1aa; } .mc-tok-operator { color: #e8a0c8; }
.mc-tok-property { color: #7dd3fc; } .mc-tok-punctuation { color: #8b90a5; }
.mc-tok-variable { color: #fca5a5; } .mc-tok-property { color: #65b8f7; }
.mc-tok-tag { color: #f87171; } .mc-tok-variable { color: #f5a0a0; }
.mc-tok-attr { color: #fdba74; } .mc-tok-tag { color: #f07070; }
.mc-tok-atrule { color: #c084fc; } .mc-tok-attr { color: #f0b070; }
.mc-tok-selector { color: #86efac; } .mc-tok-atrule { color: #b07ef5; }
.mc-tok-regex { color: #fcd34d; } .mc-tok-selector { color: #6ee7b7; }
.mc-tok-symbol { color: #fbbf24; } .mc-tok-regex { color: #f0d860; }
.mc-tok-namespace { color: #f87171; opacity: 0.8; } .mc-tok-symbol { color: #f9c846; }
.mc-tok-deleted { color: #fca5a5; text-decoration: line-through; } .mc-tok-namespace { color: #f07070; opacity: 0.8; }
.mc-tok-inserted { color: #86efac; } .mc-tok-deleted { color: #f5a0a0; text-decoration: line-through; }
.mc-tok-inserted { color: #6ee7b7; }
/* ── React Flow overrides ────────────────────────────────────── */
/* React Flow dark theme overrides */
.react-flow__node { .react-flow__node {
font-family: inherit; font-family: inherit;
} }
.react-flow__edge-path { .react-flow__edge-path {
stroke: #52525b; stroke: var(--color-surface-4);
stroke-width: 1.5; stroke-width: 1.5;
} }
.react-flow__edge.selected .react-flow__edge-path { .react-flow__edge.selected .react-flow__edge-path {
stroke: #818cf8; stroke: #245CF9;
stroke-width: 2.5; stroke-width: 2.5;
filter: drop-shadow(0 0 4px rgba(129, 140, 248, 0.4)); filter: drop-shadow(0 0 6px rgba(36, 92, 249, 0.4));
} }
.react-flow__edge.animated .react-flow__edge-path { .react-flow__edge.animated .react-flow__edge-path {
@@ -338,32 +452,33 @@ body {
} }
.react-flow__controls { .react-flow__controls {
border: 1px solid #27272a; border: 1px solid var(--color-border);
background: #18181b; background: var(--color-surface-1);
border-radius: 8px; border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
} }
.react-flow__controls button { .react-flow__controls button {
background: #18181b; background: var(--color-surface-1);
border-color: #27272a; border-color: var(--color-border);
color: #a1a1aa; color: var(--color-text-secondary);
width: 28px; width: 28px;
height: 28px; height: 28px;
} }
.react-flow__controls button:hover { .react-flow__controls button:hover {
background: #27272a; background: var(--color-surface-2);
color: #e4e4e7; color: var(--color-text-primary);
} }
.react-flow__minimap { .react-flow__minimap {
background: #09090b; background: var(--color-surface-0);
border: 1px solid #27272a; border: 1px solid var(--color-border);
border-radius: 8px; border-radius: 10px;
} }
/* Sidebar running-session pulse animation */ /* ── Sidebar running-session pulse ───────────────────────────── */
@keyframes sidebar-pulse { @keyframes sidebar-pulse {
0%, 100% { opacity: 1; } 0%, 100% { opacity: 1; }
50% { opacity: 0.4; } 50% { opacity: 0.4; }
@@ -373,7 +488,41 @@ body {
animation: sidebar-pulse 2s ease-in-out infinite; animation: sidebar-pulse 2s ease-in-out infinite;
} }
/* Markdown prose styles for chat messages */ /* Flowing accent bar for running sessions */
@keyframes accent-flow {
0% { background-position: 0% 0%; }
100% { background-position: 0% 200%; }
}
.accent-flow {
background: linear-gradient(
180deg,
#245CF9 0%,
#8A29E6 50%,
#245CF9 100%
);
background-size: 100% 200%;
animation: accent-flow 3s ease-in-out infinite;
}
/* ── Ambient glow effect ─────────────────────────────────────── */
@keyframes ambient-pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.7; }
}
.ambient-glow {
position: absolute;
pointer-events: none;
border-radius: 50%;
filter: blur(80px);
opacity: 0.4;
animation: ambient-pulse 6s ease-in-out infinite;
}
/* ── Markdown prose styles ───────────────────────────────────── */
.markdown-content { .markdown-content {
line-height: 1.7; line-height: 1.7;
word-break: break-word; word-break: break-word;
@@ -393,11 +542,12 @@ body {
.markdown-content h4, .markdown-content h4,
.markdown-content h5, .markdown-content h5,
.markdown-content h6 { .markdown-content h6 {
font-family: var(--font-display);
font-weight: 600; font-weight: 600;
color: var(--color-zinc-200); color: var(--color-text-primary);
margin-top: 1.25em; margin-top: 1.25em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
line-height: 1.4; line-height: 1.35;
} }
.markdown-content h1 { font-size: 1.125rem; } .markdown-content h1 { font-size: 1.125rem; }
@@ -430,9 +580,9 @@ body {
} }
.markdown-content blockquote { .markdown-content blockquote {
border-left: 3px solid var(--color-surface-3); border-left: 3px solid var(--color-surface-4);
padding-left: 1em; padding-left: 1em;
color: var(--color-zinc-400); color: var(--color-text-secondary);
margin-top: 0.75em; margin-top: 0.75em;
margin-bottom: 0.75em; margin-bottom: 0.75em;
} }
@@ -455,7 +605,7 @@ body {
.markdown-content th { .markdown-content th {
background: var(--color-surface-1); background: var(--color-surface-1);
font-weight: 600; font-weight: 600;
color: var(--color-zinc-300); color: var(--color-text-primary);
} }
.markdown-content hr { .markdown-content hr {
@@ -466,7 +616,7 @@ body {
.markdown-content strong { .markdown-content strong {
font-weight: 600; font-weight: 600;
color: var(--color-zinc-200); color: var(--color-text-primary);
} }
.markdown-content em { .markdown-content em {