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",
"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",
"motion": "^12.38.0",
"node-pty": "^1.1.0",
},
"devDependencies": {
@@ -157,6 +161,12 @@
"@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=="],
"@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=="],
"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=="],
"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=="],
"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=="],
"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=="],
"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=="],
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
+4
View File
@@ -68,7 +68,11 @@
"yaml": "^2.8.3"
},
"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",
"motion": "^12.38.0",
"node-pty": "^1.1.0"
}
}
+58 -58
View File
@@ -24,12 +24,12 @@ import { ProviderIcon } from './ProviderIcons';
/* ── Mode accent colours ───────────────────────────────────── */
const modeAccent: Record<OrchestrationMode, { dot: string; bar: string; label: string }> = {
single: { dot: 'bg-indigo-400', bar: 'bg-indigo-500/60', label: 'text-indigo-400' },
sequential: { dot: 'bg-amber-400', bar: 'bg-amber-500/60', label: 'text-amber-400' },
concurrent: { dot: 'bg-emerald-400', bar: 'bg-emerald-500/60', label: 'text-emerald-400' },
handoff: { dot: 'bg-sky-400', bar: 'bg-sky-500/60', label: 'text-sky-400' },
'group-chat': { dot: 'bg-violet-400', bar: 'bg-violet-500/60', label: 'text-violet-400' },
magentic: { dot: 'bg-zinc-500', bar: 'bg-zinc-600/60', label: 'text-zinc-500' },
single: { dot: 'bg-[#245CF9]', bar: 'bg-[#245CF9] opacity-60', label: 'text-[#245CF9]' },
sequential: { dot: 'bg-[var(--color-status-warning)]', bar: 'bg-[var(--color-status-warning)] opacity-60', label: 'text-[var(--color-status-warning)]' },
concurrent: { dot: 'bg-[var(--color-status-success)]', bar: 'bg-[var(--color-status-success)] opacity-60', label: 'text-[var(--color-status-success)]' },
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-[var(--color-accent-purple)]', bar: 'bg-[var(--color-accent-purple)] opacity-60', label: 'text-[var(--color-accent-purple)]' },
magentic: { dot: 'bg-[var(--color-text-muted)]', bar: 'bg-[var(--color-text-muted)] opacity-60', label: 'text-[var(--color-text-muted)]' },
};
/* ── Helpers ───────────────────────────────────────────────── */
@@ -62,7 +62,7 @@ const modeLabels: Record<OrchestrationMode, string> = {
function SectionHeader({ children }: { children: ReactNode }) {
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}
</h3>
);
@@ -87,7 +87,7 @@ function AgentRow({
const isCompleted = isAgentActivityCompleted(row.activity);
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 */}
{isActive && (
<div className={`absolute -left-3 bottom-2 top-2 w-[3px] rounded-full ${accent.bar}`} />
@@ -96,12 +96,12 @@ function AgentRow({
{/* Status dot */}
<div className="flex shrink-0 pt-0.5">
<span
className={`size-2 rounded-full ${
className={`size-2 rounded-full transition-all duration-200 ${
isActive
? `animate-pulse ${accent.dot}`
? `animate-pulse ${accent.dot} ring-2 ring-[var(--color-border-glow)]`
: isCompleted
? 'bg-emerald-400'
: 'bg-zinc-700'
? 'bg-[var(--color-status-success)]'
: 'bg-[var(--color-surface-3)]'
}`}
/>
</div>
@@ -109,13 +109,13 @@ function AgentRow({
{/* Content */}
<div className="min-w-0 flex-1">
<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>
{/* Model + effort inline */}
{agent && (
<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);
return prov ? <ProviderIcon provider={prov} className="size-2.5" /> : null;
@@ -124,8 +124,8 @@ function AgentRow({
</span>
{agent.reasoningEffort && (
<>
<span className="text-[10px] text-zinc-700">·</span>
<span className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500">
<span className="text-[10px] text-[var(--color-text-muted)]">·</span>
<span className="inline-flex items-center gap-0.5 text-[10px] text-[var(--color-text-muted)]">
<Sparkles className="size-2" />
{formatEffort(agent.reasoningEffort)}
</span>
@@ -141,8 +141,8 @@ function AgentRow({
isActive
? accent.label
: isCompleted
? 'text-emerald-400'
: 'text-zinc-600'
? 'text-[var(--color-status-success)]'
: 'text-[var(--color-text-muted)]'
}`}
>
{formatAgentActivityLabel(row.activity)}
@@ -151,20 +151,20 @@ function AgentRow({
{/* Per-agent usage summary */}
{agentUsage && agentUsage.requestCount > 0 && (
<div className="mt-0.5 flex items-center gap-1 text-[10px] text-zinc-600">
<span className="tabular-nums">{formatTokenCount(agentUsage.inputTokens)} in</span>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{formatTokenCount(agentUsage.outputTokens)} out</span>
<div className="mt-0.5 flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]">
<span className="font-mono tabular-nums">{formatTokenCount(agentUsage.inputTokens)} in</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span className="font-mono tabular-nums">{formatTokenCount(agentUsage.outputTokens)} out</span>
{agentUsage.cost > 0 && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{agentUsage.cost.toFixed(2)} cost</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span className="font-mono tabular-nums">{agentUsage.cost.toFixed(2)} cost</span>
</>
)}
{agentUsage.durationMs > 0 && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{formatDuration(agentUsage.durationMs)}</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span className="font-mono tabular-nums">{formatDuration(agentUsage.durationMs)}</span>
</>
)}
</div>
@@ -182,15 +182,15 @@ function TurnEventIcon({ kind, phase, success }: { kind: SessionEventKind; phase
const base = 'size-3';
switch (kind) {
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':
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':
return <Sparkles className={`${base} text-violet-400`} />;
return <Sparkles className={`${base} text-[var(--color-accent-purple)]`} />;
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:
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 */}
<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">
<Activity className="size-4 text-zinc-500" />
<span className="text-[12px] font-semibold uppercase tracking-[0.12em] text-zinc-400">
<Activity className="size-4 text-[var(--color-text-muted)]" />
<span className="font-display text-[12px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-secondary)]">
Activity
</span>
{hasPendingApproval ? (
<span className="flex items-center gap-1">
<ShieldAlert className="size-3 text-amber-400" />
<span className="text-[9px] font-semibold uppercase tracking-wider text-amber-400">
<ShieldAlert className="size-3 text-[var(--color-status-warning)]" />
<span className="text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-warning)]">
Approval{totalApprovalCount > 1 ? `s (${totalApprovalCount})` : ''}
</span>
</span>
) : 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}
</div>
</div>
@@ -261,7 +261,7 @@ export function ActivityPanel({
<SectionHeader>
<Users className="size-3" />
<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}
</span>
<span className={`ml-auto text-[9px] font-medium normal-case tracking-normal ${accent.label}`}>
@@ -270,7 +270,7 @@ export function ActivityPanel({
</SectionHeader>
{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) => {
const agentKey = row.activity?.agentId ?? row.key;
const agentUsage = sessionRequestUsage?.perAgent[agentKey]
@@ -288,7 +288,7 @@ export function ActivityPanel({
})}
</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>
@@ -300,32 +300,32 @@ export function ActivityPanel({
<span>Session Usage</span>
</SectionHeader>
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2.5">
<div className="flex flex-wrap items-center gap-1.5 text-[11px] text-zinc-400">
<span className="font-medium tabular-nums">
<div className="glass-surface rounded-lg px-3 py-2.5">
<div className="flex flex-wrap items-center gap-1.5 text-[11px] text-[var(--color-text-secondary)]">
<span className="font-mono font-medium tabular-nums">
{sessionRequestUsage.requestCount} premium request{sessionRequestUsage.requestCount === 1 ? '' : 's'}
</span>
{sessionRequestUsage.totalNanoAiu > 0 && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{formatNanoAiu(sessionRequestUsage.totalNanoAiu)} AIU</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span className="font-mono tabular-nums">{formatNanoAiu(sessionRequestUsage.totalNanoAiu)} AIU</span>
</>
)}
</div>
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[10px] text-zinc-500">
<span className="tabular-nums">{formatTokenCount(sessionRequestUsage.totalInputTokens)} in</span>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{formatTokenCount(sessionRequestUsage.totalOutputTokens)} out</span>
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
<span className="font-mono tabular-nums">{formatTokenCount(sessionRequestUsage.totalInputTokens)} in</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span className="font-mono tabular-nums">{formatTokenCount(sessionRequestUsage.totalOutputTokens)} out</span>
{sessionRequestUsage.totalCost > 0 && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{sessionRequestUsage.totalCost.toFixed(2)} cost</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span className="font-mono tabular-nums">{sessionRequestUsage.totalCost.toFixed(2)} cost</span>
</>
)}
{sessionRequestUsage.totalDurationMs > 0 && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{formatDuration(sessionRequestUsage.totalDurationMs)} total</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span className="font-mono tabular-nums">{formatDuration(sessionRequestUsage.totalDurationMs)} total</span>
</>
)}
</div>
@@ -339,7 +339,7 @@ export function ActivityPanel({
<Clock className="size-3" />
<span>Timeline</span>
{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}
</span>
)}
@@ -354,12 +354,12 @@ export function ActivityPanel({
<SectionHeader>
<Zap className="size-3" />
<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}
</span>
</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) => (
<div key={index} className="flex items-start gap-2 py-1">
<div className="mt-0.5 shrink-0">
@@ -367,13 +367,13 @@ export function ActivityPanel({
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-medium text-zinc-300">{entry.label}</span>
<span className="ml-auto shrink-0 text-[9px] tabular-nums text-zinc-700">
<span className="text-[11px] font-medium text-[var(--color-text-secondary)]">{entry.label}</span>
<span className="font-mono ml-auto shrink-0 text-[9px] tabular-nums text-[var(--color-text-muted)]">
{formatTurnEventTimestamp(entry.occurredAt)}
</span>
</div>
{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>
+19 -19
View File
@@ -18,9 +18,9 @@ function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) {
}
const styles = {
premium: 'bg-amber-500/10 text-amber-400',
standard: 'bg-zinc-700/50 text-zinc-500',
fast: 'bg-emerald-500/10 text-emerald-400',
premium: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]',
standard: 'bg-[var(--color-surface-3)]/50 text-[var(--color-text-muted)]',
fast: 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]',
};
return (
@@ -75,10 +75,10 @@ export function ModelSelect({
return (
<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}>
<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}
onClick={() => setOpen((current) => !current)}
type="button"
@@ -86,25 +86,25 @@ export function ModelSelect({
{provider && <ProviderIcon provider={provider} />}
<span className="flex-1 truncate">{selected?.name ?? (value || 'Select model')}</span>
<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>
{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) => {
return (
<div key={providerGroup.id}>
<div className="flex items-center gap-2 px-3 pb-1 pt-2.5">
<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}
</span>
</div>
{providerGroup.models.map((model) => (
<button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
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-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
}`}
key={model.id}
onClick={() => {
@@ -122,13 +122,13 @@ export function ModelSelect({
})}
{otherModels.length > 0 && (
<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
</div>
{otherModels.map((model) => (
<button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
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-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
}`}
key={model.id}
onClick={() => {
@@ -173,15 +173,15 @@ export function ReasoningEffortSelect({
if (supportedEfforts && supportedEfforts.length === 0) {
return (
<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">
<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
readOnly
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>
</label>
);
@@ -189,10 +189,10 @@ export function ReasoningEffortSelect({
return (
<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">
<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}
onChange={(event) => onChange(event.target.value as ReasoningEffort)}
value={selectedValue}
@@ -203,7 +203,7 @@ export function ReasoningEffortSelect({
</option>
))}
</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>
</label>
);
+15 -4
View File
@@ -10,22 +10,33 @@ interface AppShellProps {
export function AppShell({ sidebar, content, detailPanel, terminalPanel, overlay }: AppShellProps) {
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 */}
<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}
</aside>
{/* Main content + terminal */}
<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}
</main>
{/* Detail panel */}
{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}
</aside>
)}
{overlay}
</div>
);
+60 -60
View File
@@ -250,16 +250,16 @@ export function ChatPane({
return (
<div className="flex h-full flex-col">
{/* 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="min-w-0">
<h2 className="truncate text-[13px] font-semibold leading-tight text-zinc-100">{session.title}</h2>
<p className="truncate text-[11px] leading-tight text-zinc-500">
<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-[var(--color-text-muted)]">
{isScratchpad
? `Scratchpad · ${pattern.name}`
: `${project.name} · ${pattern.name} · ${pattern.mode}`}
{!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" />
{project.git.branch ?? project.git.head?.shortHash ?? 'HEAD'}
{project.git.isDirty && (
@@ -273,31 +273,31 @@ export function ChatPane({
</div>
<div className="flex items-center gap-2">
{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" />
Awaiting approval
{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
</span>
)}
</div>
)}
{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" />
Awaiting your input
</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' && (
<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" />
Error
</div>
)}
{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'}
</span>
)}
@@ -309,18 +309,18 @@ export function ChatPane({
<div className="flex-1 overflow-y-auto" ref={transcriptRef}>
{session.messages.length === 0 ? (
<div className="flex h-full flex-col items-center justify-center gap-2 px-6 text-center">
<Bot className="size-10 text-zinc-800" />
<p className="text-[13px] text-zinc-500">Send a message to start the conversation</p>
<p className="text-[12px] text-zinc-700">
<Bot className="size-10 text-[var(--color-surface-3)]" />
<p className="text-[13px] text-[var(--color-text-muted)]">Send a message to start the conversation</p>
<p className="text-[12px] text-[var(--color-text-muted)]">
{isScratchpad ? (
<>
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{' '}
<span className="text-zinc-500">{project.name}</span>
Using <span className="text-[var(--color-text-secondary)]">{pattern.name}</span> in{' '}
<span className="text-[var(--color-text-secondary)]">{project.name}</span>
</>
)}
</p>
@@ -333,14 +333,14 @@ export function ChatPane({
const phase = getAssistantMessagePhase(session, message, index);
const assistantContainerClass =
phase === 'thinking'
? 'border-sky-500/20 bg-sky-500/5'
? 'border-[var(--color-accent-sky)]/20 bg-[var(--color-accent-sky)]/5'
: phase === 'final'
? 'border-emerald-500/20 bg-emerald-500/5'
: 'border-zinc-800 bg-zinc-900/40';
? 'border-[var(--color-status-success)]/20 bg-[var(--color-status-success)]/5'
: 'border-[var(--color-border)] bg-[var(--color-surface-1)]/40';
const assistantBadgeClass =
phase === 'thinking'
? 'border-sky-400/20 bg-sky-400/10 text-sky-300'
: 'border-emerald-400/20 bg-emerald-400/10 text-emerald-300';
? 'border-[var(--color-accent-sky)]/20 bg-[var(--color-accent-sky)]/10 text-[var(--color-accent-sky)]'
: 'border-[var(--color-status-success)]/20 bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]';
const phaseLabel =
phase === 'thinking' ? 'Thinking' : phase === 'final' ? 'Final' : undefined;
@@ -349,13 +349,13 @@ export function ChatPane({
<div className="flex gap-3">
<div
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" />}
</div>
<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>
{!isUser && phaseLabel && (
<span
@@ -368,8 +368,8 @@ export function ChatPane({
<div
className={
isUser
? 'text-[14px] leading-relaxed text-zinc-200'
: `rounded-xl border px-4 py-3 text-[14px] leading-relaxed text-zinc-200 ${assistantContainerClass}`
? 'text-[14px] leading-relaxed text-[var(--color-text-primary)]'
: `rounded-xl border px-4 py-3 text-[14px] leading-relaxed text-[var(--color-text-primary)] ${assistantContainerClass}`
}
>
{/* Attachment thumbnails */}
@@ -380,13 +380,13 @@ export function ChatPane({
<img
key={attIdx}
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}`}
/>
) : (
<div
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" />
{getAttachmentDisplayName(att)}
@@ -396,14 +396,14 @@ export function ChatPane({
</div>
)}
{!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}
</div>
) : (
<MarkdownContent content={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>
{message.pending && !message.content && <ThinkingDots />}
@@ -423,24 +423,24 @@ export function ChatPane({
</div>
{/* 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 && (
<div className="mb-3 flex items-start gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-[13px] text-red-300">
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" />
<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-[var(--color-status-error)]" />
<span>{session.lastError}</span>
</div>
)}
{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">
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" />
<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-[var(--color-status-error)]" />
<span>{configError}</span>
</div>
)}
{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">
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" />
<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-[var(--color-status-error)]" />
<span>{approvalError}</span>
</div>
)}
@@ -544,7 +544,7 @@ export function ChatPane({
value={sessionReasoningEffort}
/>
{isUpdatingSessionModelConfig && (
<Loader2 className="size-3 animate-spin text-zinc-500" />
<Loader2 className="size-3 animate-spin text-[var(--color-text-muted)]" />
)}
</div>
)}
@@ -584,13 +584,13 @@ export function ChatPane({
{pendingAttachments.map((attachment, index) => (
<div
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>
<button
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))}
type="button"
>
@@ -601,7 +601,7 @@ export function ChatPane({
</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
ref={composerRef}
disabled={isComposerDisabled}
@@ -651,7 +651,7 @@ export function ChatPane({
{/* Attachment picker */}
<button
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}
onClick={() => {
const input = document.createElement('input');
@@ -685,10 +685,10 @@ export function ChatPane({
<button
aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'}
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
? 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30'
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
? 'bg-[var(--color-status-success)]/20 text-[var(--color-status-success)] hover:bg-[var(--color-status-success)]/30'
: 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text-primary)]'
}`}
disabled={isComposerDisabled}
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
@@ -700,16 +700,16 @@ export function ChatPane({
{/* Send / Stop / Steer 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
? '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
? isSessionBusy
? 'bg-amber-600 text-white hover:bg-amber-500'
? 'bg-[var(--color-status-warning)] text-white hover:brightness-110'
: isPlanMode
? 'bg-emerald-600 text-white hover:bg-emerald-500'
: 'bg-indigo-600 text-white hover:bg-indigo-500'
: 'bg-zinc-800 text-zinc-600'
? 'bg-[var(--color-status-success)] text-white hover:brightness-110'
: '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-[var(--color-surface-2)] text-[var(--color-text-muted)]'
}`}
disabled={!canSubmitInput && !isSessionBusy && pendingAttachments.length === 0}
onClick={() => {
@@ -741,16 +741,16 @@ export function ChatPane({
</MarkdownComposer>
{isPlanMode && !isSessionBusy && (
<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" />
<span className="text-[10px] font-medium text-emerald-400/80">
<div className="size-1.5 rounded-full bg-[var(--color-status-success)]" />
<span className="text-[10px] font-medium text-[var(--color-status-success)]/80">
Plan mode the agent will propose a plan instead of implementing
</span>
</div>
)}
{isSessionBusy && (hasComposerContent || pendingAttachments.length > 0) && (
<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" />
<span className="text-[10px] font-medium text-amber-400/80">
<div className="size-1.5 rounded-full bg-[var(--color-status-warning)]" />
<span className="text-[10px] font-medium text-[var(--color-status-warning)]/80">
Steering your message will be injected into the current turn
</span>
</div>
@@ -760,15 +760,15 @@ export function ChatPane({
{/* Session usage bar */}
{sessionUsage && sessionUsage.tokenLimit > 0 && (
<div className="px-1 pt-1.5">
<div className="flex items-center gap-2 text-[10px] text-zinc-500">
<div className="h-1 flex-1 overflow-hidden rounded-full bg-zinc-800">
<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-[var(--color-surface-2)]">
<div
className={`h-full rounded-full transition-all ${
sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.9
? 'bg-red-500'
? 'bg-[var(--color-status-error)]'
: sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.7
? 'bg-amber-500'
: 'bg-indigo-500/60'
? 'bg-[var(--color-status-warning)]'
: 'bg-[var(--color-accent)]/60'
}`}
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) {
case 'ready':
return {
icon: <CheckCircle2 className="size-4 text-emerald-400" />,
icon: <CheckCircle2 className="size-4 text-[var(--color-status-success)]" />,
label: 'Connected to GitHub Copilot',
accentClasses: 'text-emerald-400',
dotClasses: 'bg-emerald-400',
accentClasses: 'text-[var(--color-status-success)]',
dotClasses: 'bg-[var(--color-status-success)]',
};
case 'copilot-cli-missing':
return {
icon: <Download className="size-4 text-amber-400" />,
icon: <Download className="size-4 text-[var(--color-status-warning)]" />,
label: 'Copilot CLI not found',
accentClasses: 'text-amber-400',
dotClasses: 'bg-amber-400',
accentClasses: 'text-[var(--color-status-warning)]',
dotClasses: 'bg-[var(--color-status-warning)]',
actionIcon: <Terminal className="size-3" />,
actionLabel: 'Install the copilot CLI and ensure it is on your PATH',
};
case 'copilot-auth-required':
return {
icon: <LogIn className="size-4 text-blue-400" />,
icon: <LogIn className="size-4 text-[var(--color-status-info)]" />,
label: 'Sign-in required',
accentClasses: 'text-blue-400',
dotClasses: 'bg-blue-400',
accentClasses: 'text-[var(--color-status-info)]',
dotClasses: 'bg-[var(--color-status-info)]',
actionIcon: <Terminal className="size-3" />,
actionLabel: 'Run copilot auth login in your terminal, then refresh',
};
case 'copilot-error':
return {
icon: <XCircle className="size-4 text-red-400" />,
icon: <XCircle className="size-4 text-[var(--color-status-error)]" />,
label: 'Connection error',
accentClasses: 'text-red-400',
dotClasses: 'bg-red-400',
accentClasses: 'text-[var(--color-status-error)]',
dotClasses: 'bg-[var(--color-status-error)]',
};
}
}
@@ -108,21 +108,21 @@ function VersionBadge({ status, installedVersion }: { status: SidecarCopilotCliV
switch (status) {
case 'latest':
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" />
{versionLabel ?? 'Up to date'}
</span>
);
case 'outdated':
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" />
Update available
</span>
);
case 'unknown':
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'}
</span>
);
@@ -183,8 +183,8 @@ function QuotaSection({
if (loading) {
return (
<div className="flex items-center gap-2 py-2">
<Loader2 className="size-3.5 animate-spin text-zinc-500" />
<span className="text-[12px] text-zinc-500">Loading quota</span>
<Loader2 className="size-3.5 animate-spin text-[var(--color-text-muted)]" />
<span className="text-[12px] text-[var(--color-text-muted)]">Loading quota</span>
</div>
);
}
@@ -192,8 +192,8 @@ function QuotaSection({
if (error) {
return (
<div className="flex items-center gap-2 py-2">
<XCircle className="size-3.5 text-red-400" />
<span className="text-[12px] text-zinc-500">Could not load quota</span>
<XCircle className="size-3.5 text-[var(--color-status-error)]" />
<span className="text-[12px] text-[var(--color-text-muted)]">Could not load quota</span>
</div>
);
}
@@ -201,7 +201,7 @@ function QuotaSection({
if (!quotaData || Object.keys(quotaData).length === 0) {
return (
<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>
);
}
@@ -213,42 +213,42 @@ function QuotaSection({
? (snapshot.usedRequests / snapshot.entitlementRequests) * 100
: 0;
const barColor = usedPct > 90
? 'bg-red-500'
? 'bg-[var(--color-status-error)]'
: usedPct > 70
? 'bg-amber-500'
: 'bg-indigo-500/60';
? 'bg-[var(--color-status-warning)]'
: 'bg-[var(--color-accent)]/60';
return (
<div key={key} className="space-y-1.5">
<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)}
</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
</span>
</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
className={`h-full rounded-full transition-all ${barColor}`}
style={{ width: `${Math.min(100, usedPct)}%` }}
/>
</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">
{Math.round(snapshot.usedRequests)} of {Math.round(snapshot.entitlementRequests)} used
</span>
{snapshot.overage > 0 && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums text-amber-400">
<span className="text-[var(--color-text-muted)]">·</span>
<span className="tabular-nums text-[var(--color-status-warning)]">
{Math.round(snapshot.overage)} overage
</span>
</>
)}
{snapshot.resetDate && (
<>
<span className="text-zinc-700">·</span>
<span className="text-[var(--color-text-muted)]">·</span>
<span>Resets {formatResetDate(snapshot.resetDate)}</span>
</>
)}
@@ -276,34 +276,34 @@ function AccountSection({ connection }: { connection: SidecarConnectionDiagnosti
<div className="space-y-2.5">
{/* Identity row */}
<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 ? (
<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 && (
<span className="text-zinc-500">· {account.host}</span>
<span className="text-[var(--color-text-muted)]">· {account.host}</span>
)}
</div>
) : (
<span className="text-[12px] text-zinc-500">{account.statusMessage}</span>
<span className="text-[12px] text-[var(--color-text-muted)]">{account.statusMessage}</span>
)}
</div>
{/* Organizations */}
{hasOrgs && (
<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">
{visibleOrgs.map((org) => (
<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}
>
{org}
</span>
))}
{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>
@@ -323,10 +323,10 @@ export function CopilotStatusCard({
if (!connection) {
return (
<div className="flex items-center gap-3 rounded-xl border border-[var(--color-border)] bg-zinc-900/40 px-4 py-3">
<Cpu className="size-4 text-zinc-600" />
<span className="text-[13px] text-zinc-500">Checking connection</span>
<RefreshCw className="ml-auto size-3.5 animate-spin text-zinc-600" />
<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-[var(--color-text-muted)]" />
<span className="text-[13px] text-[var(--color-text-muted)]">Checking connection</span>
<RefreshCw className="ml-auto size-3.5 animate-spin text-[var(--color-text-muted)]" />
</div>
);
}
@@ -347,7 +347,7 @@ export function CopilotStatusCard({
{config.label}
</span>
{isHealthy && (
<span className="text-[12px] text-zinc-500">
<span className="text-[12px] text-[var(--color-text-muted)]">
· {modelCount} model{modelCount === 1 ? '' : 's'} available
</span>
)}
@@ -359,10 +359,10 @@ export function CopilotStatusCard({
/>
)}
{checkedLabel && (
<span className="text-[11px] text-zinc-600">{checkedLabel}</span>
<span className="text-[11px] text-[var(--color-text-muted)]">{checkedLabel}</span>
)}
<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}
onClick={onRefresh}
title="Refresh connection status"
@@ -381,11 +381,11 @@ export function CopilotStatusCard({
{/* Usage & Quota (when healthy and callback provided) */}
{isHealthy && onGetQuota && (
<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" />
<span>Usage &amp; Quota</span>
</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} />
</div>
</div>
@@ -393,9 +393,9 @@ export function CopilotStatusCard({
{/* Action hint for non-ready states */}
{!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>
<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>
)}
@@ -403,7 +403,7 @@ export function CopilotStatusCard({
{isHealthy && hasDetail && (
<div className="space-y-2">
<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)}
type="button"
>
@@ -412,26 +412,26 @@ export function CopilotStatusCard({
</button>
{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 && (
<div className="border-b border-zinc-800/40 px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">CLI path</span>
<p className="mt-0.5 break-all font-mono text-[11px] text-zinc-400" title={connection.copilotCliPath}>
<div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
<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-[var(--color-text-secondary)]" title={connection.copilotCliPath}>
{connection.copilotCliPath}
</p>
</div>
)}
{hasVersionInfo && connection.copilotCliVersion!.status === 'outdated' && connection.copilotCliVersion!.latestVersion && (
<div className="border-b border-zinc-800/40 px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Latest version</span>
<p className="mt-0.5 font-mono text-[11px] text-zinc-400">
<div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
<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-[var(--color-text-secondary)]">
{connection.copilotCliVersion!.latestVersion}
</p>
</div>
)}
<div className="px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Last checked</span>
<p className="mt-0.5 text-[11px] text-zinc-400">
<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-[var(--color-text-secondary)]">
{new Date(connection.checkedAt).toLocaleString()}
</p>
</div>
@@ -442,17 +442,17 @@ export function CopilotStatusCard({
{/* Error detail for non-ready states */}
{!isHealthy && hasDetail && (
<div className="overflow-hidden rounded-lg border border-zinc-800/60 bg-zinc-900/40">
<div className="border-b border-zinc-800/40 px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">CLI path</span>
<p className="mt-0.5 break-all font-mono text-[11px] text-zinc-400" title={connection.copilotCliPath}>
<div className="overflow-hidden rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)]">
<div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
<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-[var(--color-text-secondary)]" title={connection.copilotCliPath}>
{shortenPath(connection.copilotCliPath!)}
</p>
</div>
{connection.detail && (
<div className="px-3 py-2">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Error detail</span>
<p className="mt-0.5 break-words text-[11px] text-zinc-400">{connection.detail}</p>
<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-[var(--color-text-secondary)]">{connection.detail}</p>
</div>
)}
</div>
@@ -81,20 +81,20 @@ export function DiscoveredToolingModal({
<div
aria-labelledby="discovered-tooling-title"
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"
>
<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 */}
<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">
<FileSearch className="size-4 text-indigo-400" />
<h2 id="discovered-tooling-title" className="text-[13px] font-semibold text-zinc-100">
<FileSearch className="size-4 text-[var(--color-text-accent)]" />
<h2 id="discovered-tooling-title" className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">
MCP servers found in config files
</h2>
</div>
<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}
type="button"
>
@@ -104,7 +104,7 @@ export function DiscoveredToolingModal({
{/* Body */}
<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
use, or dismiss those you don&apos;t need. Accepted servers become available for session tooling.
</p>
@@ -127,20 +127,20 @@ export function DiscoveredToolingModal({
</div>
{/* Footer */}
<div className="flex items-center justify-between border-t border-zinc-800 px-5 py-3">
<span className="text-[12px] text-zinc-600">
<div className="flex items-center justify-between border-t border-[var(--color-border)] px-5 py-3">
<span className="text-[12px] text-[var(--color-text-muted)]">
{totalPending} server{totalPending === 1 ? '' : 's'} pending review
</span>
<div className="flex items-center gap-2">
<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}
type="button"
>
Dismiss All
</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}
type="button"
>
@@ -166,15 +166,15 @@ function DiscoveredGroup({
}) {
return (
<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}
</div>
{groups.map((group) => (
<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="text-zinc-700">·</span>
<span className="text-zinc-600">
<span className="text-[var(--color-text-muted)]">·</span>
<span className="text-[var(--color-text-muted)]">
{group.servers.length} server{group.servers.length === 1 ? '' : 's'}
</span>
</div>
@@ -211,22 +211,22 @@ function ServerRow({
: server.url || 'No URL';
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">
<Server className="size-3.5 shrink-0 text-zinc-600" />
<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-[var(--color-text-muted)]" />
<div className="min-w-0 flex-1">
<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}
</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}
</span>
</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 className="flex items-center gap-1">
<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}
title="Dismiss"
type="button"
@@ -234,7 +234,7 @@ function ServerRow({
<XCircle className="size-3.5" />
</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}
title="Accept"
type="button"
+4 -4
View File
@@ -677,11 +677,11 @@ function ToolbarPlugin({ disabled }: { disabled: boolean }) {
}, [editor]);
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.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" />
<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 === '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" />
@@ -709,8 +709,8 @@ function ToolbarButton({
aria-pressed={active}
className={`flex size-7 items-center justify-center rounded transition ${
active
? 'bg-indigo-600/30 text-indigo-300'
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
? 'bg-[var(--color-accent)]/30 text-[var(--color-accent-sky)]'
: 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]'
} ${disabled ? 'pointer-events-none opacity-50' : ''}`}
disabled={disabled}
onClick={onClick}
+18 -18
View File
@@ -57,13 +57,13 @@ export function NewSessionModal({
const canCreate = projectId && patternId;
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="w-full max-w-md rounded-xl border border-zinc-800 bg-zinc-900 shadow-2xl">
<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-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
{/* Header */}
<div className="flex items-center justify-between border-b border-zinc-800 px-5 py-4">
<h2 id="new-session-title" className="text-[13px] font-semibold text-zinc-100">New Session</h2>
<div className="flex items-center justify-between border-b border-[var(--color-border)] px-5 py-4">
<h2 id="new-session-title" className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">New Session</h2>
<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}
type="button"
>
@@ -74,9 +74,9 @@ export function NewSessionModal({
{/* Body */}
<div className="space-y-4 px-5 py-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
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)}
value={projectId}
>
@@ -89,28 +89,28 @@ export function NewSessionModal({
</label>
<label className="block space-y-1.5">
<span className="text-[12px] font-medium text-zinc-400">Pattern</span>
<div className="space-y-1 rounded-lg border border-zinc-700 bg-zinc-950 p-1.5">
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">Pattern</span>
<div className="space-y-1 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-0)] p-1.5">
{availablePatterns.map((p) => (
<div
key={p.id}
className={`flex cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] transition ${
patternId === p.id
? 'bg-indigo-500/15 text-zinc-100 ring-1 ring-indigo-500/25'
: 'text-zinc-300 hover:bg-zinc-800/60'
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-primary)] ring-1 ring-[var(--color-border-glow)]'
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-glass-hover)]'
}`}
onClick={() => setPatternId(p.id)}
>
<span className="flex-1 truncate">
{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>
{onTogglePatternFavorite && (
<button
className={`shrink-0 transition ${
p.isFavorite
? 'text-amber-400 hover:text-amber-300'
: 'text-zinc-700 hover:text-zinc-400'
? 'text-[var(--color-status-warning)] hover:text-[var(--color-status-warning)]'
: 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
}`}
onClick={(e) => {
e.stopPropagation();
@@ -126,7 +126,7 @@ export function NewSessionModal({
))}
</div>
{patternId && (
<p className="text-[12px] text-zinc-600">
<p className="text-[12px] text-[var(--color-text-muted)]">
{availablePatterns.find((p) => p.id === patternId)?.description}
</p>
)}
@@ -134,16 +134,16 @@ export function NewSessionModal({
</div>
{/* 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
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}
type="button"
>
Cancel
</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}
onClick={() => canCreate && onCreate(projectId, patternId)}
type="button"
+50 -51
View File
@@ -105,10 +105,10 @@ function InputField({
placeholder?: string;
}) {
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 (
<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 ? (
<textarea
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="flex items-center gap-3">
<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}
type="button"
>
<ChevronLeft className="size-4" />
</button>
<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'}
</h3>
<p className="text-[12px] text-zinc-500">
<p className="text-[12px] text-[var(--color-text-muted)]">
{isBuiltin ? 'Built-in pattern' : 'Custom pattern'}
</p>
</div>
@@ -260,7 +260,7 @@ export function PatternEditor({
<div className="no-drag flex items-center gap-2">
{!isBuiltin && onDelete && (
<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}
type="button"
>
@@ -269,7 +269,7 @@ export function PatternEditor({
</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}
type="button"
>
@@ -290,8 +290,8 @@ export function PatternEditor({
<div
className={`flex items-start gap-2 rounded-lg px-3 py-2 text-[12px] ${
issue.level === 'error'
? 'bg-red-500/10 text-red-300'
: 'bg-amber-500/10 text-amber-300'
? 'bg-[var(--color-status-error)]/10 text-[var(--color-status-error)]'
: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]'
}`}
key={`${issue.field ?? 'v'}-${i}`}
>
@@ -301,7 +301,7 @@ export function PatternEditor({
))}
</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" />
Pattern is valid
</div>
@@ -310,11 +310,11 @@ export function PatternEditor({
{/* Graph canvas */}
<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
</h4>
<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}
type="button"
>
@@ -335,11 +335,11 @@ export function PatternEditor({
</div>
{/* 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">
{/* General */}
<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
</h4>
<div className="grid gap-3 sm:grid-cols-2">
@@ -360,7 +360,7 @@ export function PatternEditor({
{/* Mode selector */}
<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
</h4>
<div className="grid grid-cols-3 gap-2">
@@ -372,12 +372,12 @@ export function PatternEditor({
return (
<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
? '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
? 'cursor-not-allowed border-zinc-800/50 opacity-40'
: 'border-zinc-800 hover:border-zinc-700 hover:bg-zinc-900/60'
? 'cursor-not-allowed border-[var(--color-border-subtle)] opacity-40'
: 'border-[var(--color-border)] hover:border-[var(--color-border)] hover:bg-[var(--color-glass)]'
}`}
disabled={disabled}
key={mode}
@@ -386,17 +386,17 @@ export function PatternEditor({
>
<div className="flex items-center gap-1.5">
<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
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}
</span>
</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}
</p>
</button>
@@ -407,12 +407,11 @@ export function PatternEditor({
{/* Approval checkpoints */}
<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
</h4>
<p className="text-[11px] leading-relaxed text-zinc-600">
Pause the run for human review before risky actions or publishing responses.
<p className="text-[11px] leading-relaxed text-[var(--color-text-muted)]">
</p>
<div className="space-y-3">
@@ -426,15 +425,15 @@ export function PatternEditor({
scopedAgentIds={checkpointAgentIds('tool-call')}
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
</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.
Sessions can override these defaults from the Activity panel.
</p>
{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.
</p>
) : (
@@ -462,9 +461,9 @@ export function PatternEditor({
</div>
{/* 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="border-b border-zinc-800/50 px-4 py-3">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
<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-[var(--color-border-subtle)] px-4 py-3">
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
Inspector
</h4>
</div>
@@ -518,26 +517,26 @@ function ApprovalCheckpointRow({
}
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">
<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">
<span className="text-[12px] font-medium text-zinc-200">{label}</span>
<p className="text-[11px] text-zinc-500">{description}</p>
<span className="text-[12px] font-medium text-[var(--color-text-primary)]">{label}</span>
<p className="text-[11px] text-[var(--color-text-muted)]">{description}</p>
</div>
<ToggleSwitch enabled={enabled} />
</button>
{/* Agent scope selector */}
{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">
<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
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
? 'bg-indigo-500/15 text-indigo-300'
: 'bg-zinc-800 text-zinc-500 hover:text-zinc-400'
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
}`}
onClick={() => onScopeChange(undefined)}
type="button"
@@ -545,10 +544,10 @@ function ApprovalCheckpointRow({
All agents
</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
? 'bg-indigo-500/15 text-indigo-300'
: 'bg-zinc-800 text-zinc-500 hover:text-zinc-400'
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
}`}
onClick={() => onScopeChange([])}
type="button"
@@ -563,10 +562,10 @@ function ApprovalCheckpointRow({
const isSelected = scopedAgentIds?.includes(agent.id) ?? false;
return (
<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
? 'bg-indigo-500/20 text-indigo-300 ring-1 ring-indigo-500/30'
: 'bg-zinc-800 text-zinc-500 hover:bg-zinc-700 hover:text-zinc-400'
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)] ring-1 ring-[var(--color-border-glow)]'
: '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}
onClick={() => toggleAgentScope(agent.id)}
@@ -583,7 +582,7 @@ function ApprovalCheckpointRow({
{/* Optional additional content (e.g. tool auto-approval list) */}
{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}
</div>
)}
@@ -620,7 +619,7 @@ function ToolApprovalGroupedList({
{groups.map((group, i) => (
<div key={group.kind}>
{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]}
</div>
)}
@@ -650,13 +649,13 @@ function ToolApprovalToggleRow({
const detail = tool.description || (tool.providerNames.length > 0 ? tool.providerNames.join(', ') : undefined);
return (
<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}
type="button"
>
<div className="min-w-0 flex-1">
<span className="truncate text-[12px] font-medium text-zinc-300">{tool.label}</span>
{detail && <div className="truncate text-[10px] text-zinc-600">{detail}</div>}
<span className="truncate text-[12px] font-medium text-[var(--color-text-secondary)]">{tool.label}</span>
{detail && <div className="truncate text-[10px] text-[var(--color-text-muted)]">{detail}</div>}
</div>
<ToggleSwitch enabled={enabled} />
</button>
@@ -115,16 +115,16 @@ export function ProjectSettingsPanel({
{/* Header */}
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
<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}
type="button"
>
<ChevronLeft className="size-4" />
</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
<span className="ml-2 font-normal text-zinc-500">·</span>
<span className="ml-2 font-normal text-zinc-400">{project.name}</span>
<span className="ml-2 font-normal text-[var(--color-text-muted)]">·</span>
<span className="ml-2 font-normal text-[var(--color-text-secondary)]">{project.name}</span>
</h2>
</div>
@@ -135,7 +135,7 @@ export function ProjectSettingsPanel({
<div className="space-y-4">
{navGroups.map((group) => (
<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}
</span>
<div className="space-y-0.5">
@@ -144,10 +144,10 @@ export function ProjectSettingsPanel({
const badge = sectionBadge(item.id);
return (
<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
? 'bg-zinc-800 font-medium text-zinc-100'
: 'text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-300'
? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-text-primary)]'
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-secondary)]'
}`}
key={item.id}
onClick={() => {
@@ -156,7 +156,7 @@ export function ProjectSettingsPanel({
}}
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>
{badge}
</button>
@@ -167,13 +167,13 @@ export function ProjectSettingsPanel({
))}
{/* 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">
<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'
? 'bg-zinc-800 font-medium text-red-400'
: 'text-zinc-500 hover:bg-zinc-800/50 hover:text-red-400'
? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-status-error)]'
: 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-status-error)]'
}`}
onClick={() => {
setActiveSection('danger-zone');
@@ -240,7 +240,7 @@ export function ProjectSettingsPanel({
function CountBadge({ count, total }: { count: number; total?: number }) {
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}
</span>
);
@@ -248,7 +248,7 @@ function CountBadge({ count, total }: { count: number; total?: number }) {
function PendingBadge({ count }: { count: number }) {
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
</span>
);
@@ -263,12 +263,12 @@ function OverviewContent({ project }: { project: ProjectRecord }) {
description="Project details and git status."
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">
<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="text-[13px] font-medium text-zinc-200">{project.name}</div>
<div className="mt-0.5 truncate text-[12px] text-zinc-500">{project.path}</div>
<div className="text-[13px] font-medium text-[var(--color-text-primary)]">{project.name}</div>
<div className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{project.path}</div>
</div>
</div>
{project.git && <ProjectGitInfo git={project.git} />}
@@ -280,7 +280,7 @@ function OverviewContent({ project }: { project: ProjectRecord }) {
function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.status === 'not-repository') {
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" />
Not a git repository
</div>
@@ -289,7 +289,7 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.status === 'git-missing') {
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" />
Git is not installed
</div>
@@ -298,7 +298,7 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.status === 'error') {
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" />
{git.errorMessage ?? 'Git error'}
</div>
@@ -312,12 +312,12 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
if (git.behind) parts.push(`${git.behind} behind`);
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" />
<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 && (
<span className="text-zinc-600">· {parts.join(' · ')}</span>
<span className="text-[var(--color-text-muted)]">· {parts.join(' · ')}</span>
)}
</div>
);
@@ -343,7 +343,7 @@ function InstructionsContent({
{instructions.length === 0 ? (
<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>
) : (
<div className="space-y-3">
@@ -361,28 +361,28 @@ function InstructionCard({ instruction }: { instruction: ProjectInstructionFile
const isLong = instruction.content.length > 300;
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
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)}
type="button"
>
<FileCode2 className="size-4 shrink-0 text-indigo-400" />
<span className="flex-1 text-[13px] font-medium text-zinc-200">{instruction.sourcePath}</span>
<FileCode2 className="size-4 shrink-0 text-[var(--color-text-accent)]" />
<span className="flex-1 text-[13px] font-medium text-[var(--color-text-primary)]">{instruction.sourcePath}</span>
<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>
{expanded && (
<div className="border-t border-zinc-800 px-5 py-4">
<pre className="whitespace-pre-wrap text-[11px] leading-relaxed text-zinc-400">
<div className="border-t border-[var(--color-border)] px-5 py-4">
<pre className="whitespace-pre-wrap text-[11px] leading-relaxed text-[var(--color-text-secondary)]">
{instruction.content}
</pre>
</div>
)}
{!expanded && (
<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}
</p>
</div>
@@ -415,12 +415,12 @@ function AgentsContent({
{agents.length === 0 ? (
<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>
) : (
<>
{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
</div>
)}
@@ -449,26 +449,26 @@ function AgentCard({
return (
<div className={`rounded-xl border px-5 py-4 transition ${
agent.enabled
? 'border-zinc-800 bg-zinc-900/40'
: 'border-zinc-800/50 bg-zinc-900/20 opacity-60'
? 'border-[var(--color-border)] bg-[var(--color-glass)]'
: 'border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]/20 opacity-60'
}`}>
<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="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}
</span>
{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'}
</span>
)}
</div>
{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>
<button
aria-label={agent.enabled ? `Disable ${agent.name}` : `Enable ${agent.name}`}
@@ -504,7 +504,7 @@ function PromptsContent({
{promptFiles.length === 0 ? (
<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>
) : (
<div className="space-y-2">
@@ -519,27 +519,27 @@ function PromptsContent({
function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
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">
<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="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 && (
<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'}
</span>
)}
</div>
{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 && (
<div className="mt-2 flex flex-wrap gap-1.5">
{prompt.variables.map((v) => (
<span
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}
>
{v.name}
@@ -547,7 +547,7 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
))}
</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>
@@ -607,14 +607,14 @@ function McpServersContent({
{pending.length > 1 && (
<div className="mt-3 flex items-center gap-2">
<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')}
type="button"
>
Accept all ({pending.length})
</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')}
type="button"
>
@@ -645,31 +645,31 @@ function DiscoveredServerRow({
: server.url || 'No URL';
const statusBadge = status === 'accepted'
? 'bg-emerald-500/10 text-emerald-400'
: 'bg-amber-500/10 text-amber-400';
? 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]'
: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
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">
<Server className="size-4 shrink-0 text-zinc-600" />
<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-[var(--color-text-muted)]" />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="truncate text-[13px] font-medium text-zinc-200">{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="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{server.name}</span>
<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}
</span>
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
{status}
</span>
</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}
<span className="ml-2 text-zinc-700">· {server.sourceLabel}</span>
<span className="ml-2 text-[var(--color-text-muted)]">· {server.sourceLabel}</span>
</p>
</div>
<div className="flex items-center gap-1">
{onAccept && (
<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}
type="button"
>
@@ -678,7 +678,7 @@ function DiscoveredServerRow({
)}
{onDismiss && (
<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}
type="button"
>
@@ -707,18 +707,18 @@ function DangerZoneContent({
description="Irreversible actions for this project."
title="Danger Zone"
/>
<div className="rounded-xl border border-red-500/20 bg-red-500/5 px-5 py-5">
<h4 className="text-[13px] font-semibold text-zinc-200">Remove project</h4>
<p className="mt-1 text-[12px] text-zinc-500">
<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-[var(--color-text-primary)]">Remove project</h4>
<p className="mt-1 text-[12px] text-[var(--color-text-muted)]">
Removing a project deletes all its sessions and discovered tooling from Aryx.
Your project files on disk are not affected.
</p>
<div className="mt-4 flex items-center gap-3">
<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
? 'bg-red-600 text-white hover:bg-red-500'
: 'bg-red-500/10 text-red-400 hover:bg-red-500/20'
? 'bg-[var(--color-status-error)] text-white hover:bg-[var(--color-status-error)]'
: 'bg-[var(--color-status-error)]/10 text-[var(--color-status-error)] hover:bg-[var(--color-status-error)]/20'
}`}
onClick={onRemove}
type="button"
@@ -728,7 +728,7 @@ function DangerZoneContent({
</button>
{confirmingRemove && (
<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}
type="button"
>
@@ -755,8 +755,8 @@ function SectionHeader({
return (
<div className="mb-4 flex items-center justify-between gap-3">
<div>
<h3 className="text-[13px] font-semibold text-zinc-200">{title}</h3>
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p>
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h3>
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
</div>
{children}
</div>
@@ -766,7 +766,7 @@ function SectionHeader({
function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; label?: string }) {
return (
<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}
title={label === 'Scan' ? 'Scan for files' : 'Re-scan for changes'}
type="button"
@@ -779,7 +779,7 @@ function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; lab
function EmptyState({ children }: { children: ReactNode }) {
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}
</div>
);
+3 -3
View File
@@ -51,9 +51,9 @@ const iconComponents: Record<ModelProvider, React.FC<{ className?: string }>> =
};
export const providerColors: Record<ModelProvider, string> = {
openai: 'text-emerald-400',
anthropic: 'text-orange-400',
google: 'text-blue-400',
openai: 'text-[var(--color-status-success)]',
anthropic: 'text-[var(--color-accent-purple)]',
google: 'text-[var(--color-status-info)]',
};
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) ───────── */
const modeAccent: Record<OrchestrationMode, { dot: string; ring: string; text: string }> = {
single: { dot: 'bg-indigo-400', ring: 'ring-indigo-500/30', text: 'text-indigo-400' },
sequential: { dot: 'bg-amber-400', ring: 'ring-amber-500/30', text: 'text-amber-400' },
concurrent: { dot: 'bg-emerald-400', ring: 'ring-emerald-500/30', text: 'text-emerald-400' },
handoff: { dot: 'bg-sky-400', ring: 'ring-sky-500/30', text: 'text-sky-400' },
'group-chat': { dot: 'bg-violet-400', ring: 'ring-violet-500/30', text: 'text-violet-400' },
magentic: { dot: 'bg-zinc-500', ring: 'ring-zinc-600/30', text: 'text-zinc-500' },
single: { dot: 'bg-[#245CF9]', ring: 'ring-[#245CF9]/30', text: 'text-[#245CF9]' },
sequential: { dot: 'bg-[var(--color-status-warning)]', ring: 'ring-[var(--color-status-warning)]/30', text: 'text-[var(--color-status-warning)]' },
concurrent: { dot: 'bg-[var(--color-status-success)]', ring: 'ring-[var(--color-status-success)]/30', text: 'text-[var(--color-status-success)]' },
handoff: { dot: 'bg-[var(--color-accent-sky)]', ring: 'ring-[var(--color-accent-sky)]/30', text: 'text-[var(--color-accent-sky)]' },
'group-chat': { dot: 'bg-[var(--color-accent-purple)]', ring: 'ring-[var(--color-accent-purple)]/30', text: 'text-[var(--color-accent-purple)]' },
magentic: { dot: 'bg-[var(--color-text-muted)]', ring: 'ring-[var(--color-text-muted)]/30', text: 'text-[var(--color-text-muted)]' },
};
/* ── Status badges ─────────────────────────────────────────── */
const runStatusStyles: Record<SessionRunRecord['status'], { icon: ReactNode; className: string }> = {
running: { icon: <CircleDot className="size-3" />, className: 'text-blue-400' },
completed: { icon: <CheckCircle2 className="size-3" />, className: 'text-emerald-400' },
cancelled: { icon: <XCircle className="size-3" />, className: 'text-zinc-400' },
error: { icon: <XCircle className="size-3" />, className: 'text-red-400' },
running: { icon: <CircleDot className="size-3" />, className: 'text-[var(--color-status-info)]' },
completed: { icon: <CheckCircle2 className="size-3" />, className: 'text-[var(--color-status-success)]' },
cancelled: { icon: <XCircle className="size-3" />, className: 'text-[var(--color-text-muted)]' },
error: { icon: <XCircle className="size-3" />, className: 'text-[var(--color-status-error)]' },
};
/* ── Event node icon ───────────────────────────────────────── */
@@ -53,23 +53,23 @@ function EventIcon({ kind, status }: { kind: RunTimelineEventRecord['kind']; sta
const base = 'size-3.5';
switch (kind) {
case 'run-started':
return <Play className={`${base} text-zinc-500`} />;
return <Play className={`${base} text-[var(--color-text-muted)]`} />;
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':
return <ArrowRight className={`${base} text-amber-400`} />;
return <ArrowRight className={`${base} text-[var(--color-status-warning)]`} />;
case 'tool-call':
return <Wrench className={`${base} text-violet-400`} />;
return <Wrench className={`${base} text-[var(--color-accent-purple)]`} />;
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':
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':
return <CheckCircle2 className={`${base} text-emerald-400`} />;
return <CheckCircle2 className={`${base} text-[var(--color-status-success)]`} />;
case 'run-cancelled':
return <XCircle className={`${base} text-zinc-400`} />;
return <XCircle className={`${base} text-[var(--color-text-muted)]`} />;
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 (
<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}
onClick={isClickable ? () => onJumpToMessage(event.messageId!) : undefined}
type="button"
>
{/* Vertical connector line */}
{!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 */}
<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} />
</div>
</div>
@@ -112,41 +112,41 @@ function TimelineEventRow({
{/* Content */}
<div className="min-w-0 flex-1">
<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}
</span>
{/* Approval kind badge */}
{event.kind === 'approval' && event.approvalKind && (
<span className={`rounded-full px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider ${
event.status === 'running'
? 'bg-amber-500/15 text-amber-400'
? 'bg-[var(--color-status-warning)]/15 text-[var(--color-status-warning)]'
: event.status === 'completed'
? 'bg-emerald-500/15 text-emerald-400'
: 'bg-red-500/15 text-red-400'
? 'bg-[var(--color-status-success)]/15 text-[var(--color-status-success)]'
: 'bg-[var(--color-status-error)]/15 text-[var(--color-status-error)]'
}`}>
{event.approvalKind === 'final-response' ? 'response' : 'tool'}
</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>
{/* Content preview for message events */}
{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}
</p>
)}
{/* Approval detail */}
{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)}
</p>
)}
{/* Error detail */}
{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)}
</p>
)}
@@ -169,15 +169,15 @@ function ThinkingGroupRow({
return (
<div className="group relative flex w-full gap-2.5 py-1">
{!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="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-1)]">
<Brain className="size-3.5 text-zinc-500" />
<div className="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-2)]">
<Brain className="size-3.5 text-[var(--color-text-muted)]" />
</div>
</div>
<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}
</span>
</div>
@@ -225,26 +225,26 @@ function RunCard({
);
return (
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40">
<div className="glass-surface rounded-lg">
{/* Run header */}
<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}
type="button"
>
{expanded
? <ChevronDown className="size-3 shrink-0 text-zinc-600" />
: <ChevronRight 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-[var(--color-text-muted)]" />}
<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}
</span>
{/* Status */}
<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}
<span className="text-[9px] font-medium">{formatRunStatusLabel(run.status)}</span>
</span>
@@ -252,13 +252,13 @@ function RunCard({
{/* Expanded timeline */}
{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 */}
{run.agents.length > 1 && (
<div className="mb-2 flex flex-wrap gap-1">
{run.agents.map((agent) => (
<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}
>
{agent.agentName}
@@ -281,7 +281,7 @@ function RunCard({
{/* Duration footer */}
{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}
</div>
)}
@@ -295,7 +295,7 @@ function RunCard({
function EmptyTimeline() {
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
</p>
);
+59 -59
View File
@@ -93,8 +93,8 @@ const navGroups: NavGroup[] = [
];
function modeBadgeClasses(pattern: PatternDefinition) {
if (pattern.availability === 'unavailable') return 'bg-amber-500/10 text-amber-400';
return 'bg-zinc-800 text-zinc-400';
if (pattern.availability === 'unavailable') return 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
return 'bg-[var(--color-surface-3)] text-[var(--color-text-secondary)]';
}
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="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
<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}
type="button"
>
<ChevronLeft className="size-4" />
</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 className="flex min-h-0 flex-1">
@@ -224,7 +224,7 @@ export function SettingsPanel({
<div className="space-y-4">
{navGroups.map((group) => (
<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}
</span>
<div className="space-y-0.5">
@@ -232,16 +232,16 @@ export function SettingsPanel({
const isActive = item.id === activeSection;
return (
<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
? 'bg-zinc-800 font-medium text-zinc-100'
: 'text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-300'
? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-text-primary)]'
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-secondary)]'
}`}
key={item.id}
onClick={() => setActiveSection(item.id)}
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}
</button>
);
@@ -322,8 +322,8 @@ function AppearanceSection({
return (
<div>
<div className="mb-1">
<h3 className="text-[13px] font-semibold text-zinc-200">Appearance</h3>
<p className="mt-0.5 text-[12px] text-zinc-500">
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">Appearance</h3>
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">
Choose how Aryx looks on your device
</p>
</div>
@@ -333,27 +333,27 @@ function AppearanceSection({
const isSelected = option.value === theme;
return (
<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
? 'border-indigo-500/50 bg-indigo-500/10'
: 'border-[var(--color-border)] hover:border-zinc-600 hover:bg-zinc-800/40'
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)]'
: 'border-[var(--color-border)] hover:border-[var(--color-border)] hover:bg-[var(--color-surface-3)]/40'
}`}
key={option.value}
onClick={() => onSetTheme(option.value)}
type="button"
>
<div
className={`flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition ${
isSelected ? 'border-indigo-500' : 'border-zinc-600'
className={`flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-all duration-200 ${
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>
<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}
</span>
<p className="text-[12px] text-zinc-500">{option.description}</p>
<p className="text-[12px] text-[var(--color-text-muted)]">{option.description}</p>
</div>
</button>
);
@@ -379,12 +379,12 @@ function ConnectionSection({
return (
<div>
<div className="mb-1">
<h3 className="text-[13px] font-semibold text-zinc-200">GitHub Copilot</h3>
<p className="mt-0.5 text-[12px] text-zinc-500">
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">GitHub Copilot</h3>
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">
Aryx uses your installed GitHub Copilot CLI for AI capabilities
</p>
</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
connection={connection}
isRefreshing={isRefreshing}
@@ -418,25 +418,25 @@ function PatternsSection({
<div className="space-y-1">
{patterns.map((pattern) => (
<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}
onClick={() => onEditPattern(pattern)}
type="button"
>
<div className="min-w-0 flex-1">
<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)}`}>
{pattern.mode}
</span>
</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 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'}
</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>
</button>
))}
@@ -537,8 +537,8 @@ function SectionHeader({
return (
<div className="mb-4 flex items-center justify-between gap-3">
<div>
<h3 className="text-[13px] font-semibold text-zinc-200">{title}</h3>
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p>
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h3>
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
</div>
{children}
</div>
@@ -548,7 +548,7 @@ function SectionHeader({
function SectionAction({ label, onClick }: { label: string; onClick: () => void }) {
return (
<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}
type="button"
>
@@ -571,27 +571,27 @@ function ToolingListButton({
}) {
return (
<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}
type="button"
>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="truncate text-[13px] font-medium text-zinc-200">{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="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{label}</span>
<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}
</span>
</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>
<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>
);
}
function EmptyState({ children }: { children: ReactNode }) {
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}
</div>
);
@@ -648,8 +648,8 @@ function DiscoveredSubSection({
<div className="mb-4">
<div className="mb-2 flex items-center justify-between">
<div>
<span className="text-[12px] font-medium text-zinc-300">{label}</span>
<p className="text-[11px] text-zinc-600">{description}</p>
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
<p className="text-[11px] text-[var(--color-text-muted)]">{description}</p>
</div>
</div>
<div className="space-y-1">
@@ -692,30 +692,30 @@ function DiscoveredServerRow({
: server.url || 'No URL';
const statusBadge = status === 'accepted'
? 'bg-emerald-500/10 text-emerald-400'
: 'bg-amber-500/10 text-amber-400';
? 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]'
: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
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="flex items-center gap-2">
<span className="truncate text-[13px] font-medium text-zinc-200">{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="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{server.name}</span>
<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}
</span>
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
{status}
</span>
</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}
<span className="ml-2 text-zinc-700">· {server.sourceLabel}</span>
<span className="ml-2 text-[var(--color-text-muted)]">· {server.sourceLabel}</span>
</p>
</div>
<div className="flex items-center gap-1">
{onAccept && (
<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}
type="button"
>
@@ -724,7 +724,7 @@ function DiscoveredServerRow({
)}
{onDismiss && (
<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}
type="button"
>
@@ -772,12 +772,12 @@ function TroubleshootingSection({
/>
</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">
<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">
<h4 className="text-[13px] font-semibold text-red-300">Reset Local Workspace</h4>
<p className="mt-1 text-[12px] leading-relaxed text-zinc-400">
<h4 className="text-[13px] font-semibold text-[var(--color-status-error)]">Reset Local Workspace</h4>
<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,
MCP server definitions, LSP profiles, and scratchpad contents. Your GitHub Copilot sign-in
is not affected.
@@ -785,7 +785,7 @@ function TroubleshootingSection({
{!confirmingReset ? (
<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)}
type="button"
>
@@ -794,7 +794,7 @@ function TroubleshootingSection({
) : (
<div className="mt-3 flex items-center gap-2">
<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}
onClick={() => void handleReset()}
type="button"
@@ -802,7 +802,7 @@ function TroubleshootingSection({
{isResetting ? 'Resetting…' : 'Confirm reset'}
</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}
onClick={() => setConfirmingReset(false)}
type="button"
@@ -831,16 +831,16 @@ function TroubleshootingAction({
}) {
return (
<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}
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">
<span className="text-[13px] font-medium text-zinc-200">{label}</span>
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p>
<span className="text-[13px] font-medium text-[var(--color-text-primary)]">{label}</span>
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
</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>
);
}
+62 -62
View File
@@ -54,12 +54,12 @@ interface SidebarProps {
/* ── Mode icon + accent colour mapping ─────────────────────── */
const modeVisuals: Record<OrchestrationMode, { icon: LucideIcon; color: string }> = {
single: { icon: MessageSquare, color: 'text-indigo-400' },
sequential: { icon: ListOrdered, color: 'text-amber-400' },
concurrent: { icon: GitFork, color: 'text-emerald-400' },
handoff: { icon: ArrowLeftRight, color: 'text-sky-400' },
'group-chat': { icon: Users, color: 'text-violet-400' },
magentic: { icon: Lock, color: 'text-zinc-500' },
single: { icon: MessageSquare, color: 'text-[#245CF9]' },
sequential: { icon: ListOrdered, color: 'text-[var(--color-status-warning)]' },
concurrent: { icon: GitFork, color: 'text-[var(--color-status-success)]' },
handoff: { icon: ArrowLeftRight, color: 'text-[var(--color-accent-sky)]' },
'group-chat': { icon: Users, color: 'text-[var(--color-accent-purple)]' },
magentic: { icon: Lock, color: 'text-[var(--color-text-muted)]' },
};
/* ── Relative time helper ──────────────────────────────────── */
@@ -83,7 +83,7 @@ function relativeTime(iso: string): string {
function GitContextBadge({ git }: { git: ProjectGitContext }) {
if (git.status === 'not-repository') {
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
</span>
);
@@ -117,7 +117,7 @@ function GitContextBadge({ git }: { git: ProjectGitContext }) {
if (git.behind) parts.push(`${git.behind}`);
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" />
<span className="max-w-[80px] truncate">{branchLabel}</span>
{git.isDirty && <Circle className="size-1.5 shrink-0 fill-amber-500 text-amber-500" />}
@@ -140,7 +140,7 @@ function ActionMenuItem({
}) {
return (
<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}
role="menuitem"
type="button"
@@ -213,10 +213,10 @@ function SessionItem({
return (
<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
? 'bg-indigo-500/10 ring-1 ring-indigo-500/25'
: 'hover:bg-zinc-800/60'
? 'bg-[var(--color-accent-muted)] ring-1 ring-[var(--color-border-glow)]'
: 'hover:bg-[var(--color-surface-2)]/60'
} ${isRunning ? 'sidebar-running' : ''} ${session.isArchived ? 'opacity-50' : ''}`}
onClick={isRenaming ? undefined : onSelect}
role="button"
@@ -225,19 +225,19 @@ function SessionItem({
>
{/* Running/approval left accent bar */}
{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 && (
<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 */}
<span
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>
{/* Content */}
@@ -247,7 +247,7 @@ function SessionItem({
{isRenaming ? (
<input
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}
onChange={(e) => setRenameText(e.target.value)}
onKeyDown={handleRenameKeyDown}
@@ -257,7 +257,7 @@ function SessionItem({
) : (
<span
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}
@@ -267,36 +267,36 @@ function SessionItem({
<div className="mt-1 flex items-center gap-2">
{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" />
{agentCount}
</span>
)}
{isRunning && !hasPendingApproval && (
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-blue-400">
<span className="size-1.5 rounded-full bg-blue-400 sidebar-pulse" />
<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-[var(--color-accent-sky)] sidebar-pulse" />
Running
</span>
)}
{hasPendingApproval && (
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-amber-400">
<span className="size-1.5 rounded-full bg-amber-400 animate-pulse" />
<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-[var(--color-status-warning)] animate-pulse" />
Awaiting approval{queuedCount > 0 && ` (+${queuedCount})`}
</span>
)}
{isError && (
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-red-400">
<span className="size-1.5 rounded-full bg-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-[var(--color-status-error)]" />
Error
</span>
)}
{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" />
Archived
</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)}
</span>
</div>
@@ -305,7 +305,7 @@ function SessionItem({
{/* Actions button (hidden during rename) */}
{!isRenaming && (
<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); }}
type="button"
>
@@ -376,19 +376,19 @@ function ProjectGroup({
return (
<div>
<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)}
type="button"
>
{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 ? (
<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>
@@ -399,7 +399,7 @@ function ProjectGroup({
<div className="ml-auto flex items-center gap-1.5">
{!isScratchpad && onOpenProjectSettings && (
<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) => {
e.stopPropagation();
onOpenProjectSettings(project.id);
@@ -412,7 +412,7 @@ function ProjectGroup({
)}
{!isScratchpad && onRefreshGitContext && (
<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) => {
e.stopPropagation();
onRefreshGitContext(project.id);
@@ -424,8 +424,8 @@ function ProjectGroup({
</span>
)}
{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="size-1.5 rounded-full bg-blue-400 sidebar-pulse" />
<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-[var(--color-accent-sky)] sidebar-pulse" />
{runningCount}
</span>
)}
@@ -442,14 +442,14 @@ function ProjectGroup({
{pendingDiscoveryCount} new
</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}
</span>
</div>
</button>
{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.map((session) => (
<SessionItem
@@ -466,7 +466,7 @@ function ProjectGroup({
))}
{onNewSession ? (
<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}
type="button"
>
@@ -475,7 +475,7 @@ function ProjectGroup({
</button>
) : (
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'}
</div>
)
@@ -562,19 +562,19 @@ export function Sidebar({
return (
<div className="flex h-full flex-col">
{/* 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">
<img alt="aryx" className="size-8 rounded-xl" src={appIconUrl} />
<div>
<span className="text-sm font-semibold text-zinc-100">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="font-display text-sm font-semibold text-[var(--color-text-primary)]">aryx</span>
<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
</span>
</div>
</div>
<div className="flex items-center gap-1">
<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}
title="Settings"
type="button"
@@ -587,16 +587,16 @@ export function Sidebar({
{/* Search + Filters */}
<div className="space-y-2 px-3 pt-3 pb-1">
<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
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…"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
{searchText && (
<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('')}
type="button"
>
@@ -615,11 +615,11 @@ export function Sidebar({
{isQueryActive ? (
/* ── Flat search / filter results ──────────────────────── */
<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})
</div>
{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
</div>
) : (
@@ -643,7 +643,7 @@ export function Sidebar({
<div className="space-y-3">
{scratchpadProject && (
<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
</div>
<ProjectGroup
@@ -666,21 +666,21 @@ export function Sidebar({
{userProjects.length === 0 ? (
<div className="flex flex-col items-center gap-4 px-4 py-8 text-center">
<div className="relative">
<div className="flex size-14 items-center justify-center rounded-2xl bg-zinc-800/50 ring-1 ring-zinc-700/50">
<FolderOpen className="size-7 text-zinc-600" />
<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-[var(--color-text-muted)]" />
</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" />
</div>
</div>
<div>
<p className="text-[13px] font-medium text-zinc-300">No projects yet</p>
<p className="mt-1 text-[12px] leading-relaxed text-zinc-500">
<p className="text-[13px] font-medium text-[var(--color-text-primary)]">No projects yet</p>
<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
</p>
</div>
<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}
type="button"
>
@@ -689,7 +689,7 @@ export function Sidebar({
</div>
) : (
<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
</div>
{userProjects.map((project) => (
@@ -717,9 +717,9 @@ export function Sidebar({
{/* Footer */}
{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
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}
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 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"
style={{ top: menuState.top, left: menuState.left }}
>
@@ -771,7 +771,7 @@ export function Sidebar({
}}
/>
<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}
label="Delete"
onClick={() => {
+31 -31
View File
@@ -11,28 +11,28 @@ import type { TerminalExitInfo } from '@shared/domain/terminal';
/* ── Theme ────────────────────────────────────────────────── */
const terminalTheme = {
background: '#09090b', // zinc-950
foreground: '#e4e4e7', // zinc-200
cursor: '#818cf8', // indigo-400
cursorAccent: '#09090b',
selectionBackground: '#6366f14d', // indigo-500/30
selectionForeground: '#e4e4e7',
black: '#27272a', // zinc-800
red: '#f87171', // red-400
green: '#4ade80', // green-400
yellow: '#facc15', // yellow-400
blue: '#60a5fa', // blue-400
magenta: '#c084fc', // purple-400
cyan: '#22d3ee', // cyan-400
white: '#e4e4e7', // zinc-200
brightBlack: '#52525b', // zinc-600
brightRed: '#fca5a5', // red-300
brightGreen: '#86efac', // green-300
brightYellow: '#fde047', // yellow-300
brightBlue: '#93c5fd', // blue-300
brightMagenta: '#d8b4fe',// purple-300
brightCyan: '#67e8f9', // cyan-300
brightWhite: '#fafafa', // zinc-50
background: '#07080e',
foreground: '#e8eaf0',
cursor: '#245CF9',
cursorAccent: '#07080e',
selectionBackground: 'rgba(36, 92, 249, 0.2)',
selectionForeground: '#e8eaf0',
black: '#1e2233',
red: '#f87171',
green: '#4ade80',
yellow: '#facc15',
blue: '#248CFD',
magenta: '#a855f7',
cyan: '#22d3ee',
white: '#e8eaf0',
brightBlack: '#4e5368',
brightRed: '#fca5a5',
brightGreen: '#86efac',
brightYellow: '#fde047',
brightBlue: '#60a5fa',
brightMagenta: '#c084fc',
brightCyan: '#67e8f9',
brightWhite: '#f8f9fc',
};
/* ── Constants ────────────────────────────────────────────── */
@@ -94,7 +94,7 @@ export function TerminalPanel({
const terminal = new Terminal({
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,
lineHeight: 1.4,
cursorBlink: true,
@@ -214,12 +214,12 @@ export function TerminalPanel({
return (
<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 }}
>
{/* Resize handle */}
<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}
role="separator"
aria-orientation="horizontal"
@@ -237,15 +237,15 @@ export function TerminalPanel({
/>
{/* Header bar */}
<div className="flex h-7 shrink-0 items-center gap-2 border-b border-zinc-800/60 px-3">
<span className={`size-1.5 shrink-0 rounded-full ${isRunning ? 'bg-emerald-400' : 'bg-zinc-600'}`} />
<span className="min-w-0 flex-1 truncate text-[11px] text-zinc-500">
<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-[var(--color-text-muted)]'}`} />
<span className="min-w-0 flex-1 truncate font-mono text-[11px] text-[var(--color-text-muted)]">
{snapshot ? `${snapshot.shell}${snapshot.cwd}` : 'Terminal'}
</span>
<div className="flex items-center gap-0.5">
<button
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}
type="button"
>
@@ -253,7 +253,7 @@ export function TerminalPanel({
</button>
<button
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}
type="button"
>
@@ -261,7 +261,7 @@ export function TerminalPanel({
</button>
<button
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}
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 {
hasProjects: boolean;
@@ -7,6 +10,42 @@ interface WelcomePaneProps {
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({
hasProjects,
onNewScratchpad,
@@ -14,48 +53,66 @@ export function WelcomePane({
onOpenSettings,
}: WelcomePaneProps) {
return (
<div className="flex h-full flex-col items-center justify-center px-8">
<div className="flex flex-col items-center gap-6 text-center">
<div className="flex size-16 items-center justify-center rounded-2xl bg-indigo-600/10">
<MessageSquare className="size-8 text-indigo-400" />
</div>
<div className="relative flex h-full flex-col items-center justify-center overflow-hidden px-8">
{/* Ambient nebula glow */}
<div
className="pointer-events-none absolute inset-0"
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>
<h1 className="text-base font-semibold text-zinc-100">Welcome to aryx</h1>
<p className="mt-2 max-w-md text-[13px] leading-relaxed text-zinc-500">
<div className="relative z-10 flex w-full max-w-xs flex-col items-center gap-8 text-center">
{/* Icon */}
<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
repo-aware Copilot agents.
</p>
</div>
</motion.div>
<div className="flex flex-col items-center gap-2">
<button
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"
{/* Action cards */}
<motion.div {...fadeUp(0.16)} className="flex w-full flex-col gap-2.5">
<ActionCard
icon={<MessageSquarePlus className="size-4 text-white" />}
title="New Scratchpad"
description="Ask anything without a project context"
onClick={onNewScratchpad}
type="button"
>
<Plus className="size-4" />
New Scratchpad
</button>
/>
{!hasProjects && (
<button
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"
<ActionCard
icon={<FolderPlus className="size-4 text-white" />}
title="Add Your First Project"
description="Connect a repo for full agent support"
onClick={onAddProject}
type="button"
>
<Plus className="size-3.5" />
Add Your First Project
</button>
/>
)}
<button
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"
<ActionCard
icon={<Settings className="size-4 text-white" />}
title="Manage Patterns"
description="Customize agent behaviors and workflows"
onClick={onOpenSettings}
type="button"
>
<Settings className="size-3.5" />
Manage patterns
</button>
</div>
/>
</motion.div>
</div>
</div>
);
+29 -29
View File
@@ -30,50 +30,50 @@ export function ApprovalBanner({
const approvalToolLabel = approvalToolKey ? resolveToolLabel(approvalToolKey) : undefined;
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 */}
<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="flex items-center gap-2">
<span className="text-[13px] font-semibold text-amber-200">{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="text-[13px] font-semibold text-[var(--color-status-warning)]">{approval.title}</span>
<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}
</span>
{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}
</span>
)}
</div>
<div className="mt-1 flex flex-wrap items-center gap-2 text-[11px] text-zinc-400">
{approval.agentName && <span>Agent: <span className="text-zinc-300">{approval.agentName}</span></span>}
{approval.toolName && <span>Tool: <span className="text-zinc-300">{approval.toolName}</span></span>}
{approval.permissionKind && <span>Permission: <span className="text-zinc-300">{approval.permissionKind}</span></span>}
<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-[var(--color-text-primary)]">{approval.agentName}</span></span>}
{approval.toolName && <span>Tool: <span className="text-[var(--color-text-primary)]">{approval.toolName}</span></span>}
{approval.permissionKind && <span>Permission: <span className="text-[var(--color-text-primary)]">{approval.permissionKind}</span></span>}
</div>
{approval.permissionDetail
? <PermissionDetailView detail={approval.permissionDetail} />
: 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>
{/* Final-response message preview */}
{hasMessages && (
<div className="mt-3 space-y-2 rounded-lg border border-zinc-800 bg-zinc-900/60 p-3">
<p className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
<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-[var(--color-text-muted)]">
Pending messages not yet published
</p>
{approval.messages!.map((message) => (
<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" />
<span>{message.authorName}</span>
</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} />
</div>
</div>
@@ -84,7 +84,7 @@ export function ApprovalBanner({
{/* Actions */}
<div className="mt-3 flex items-center gap-2">
<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}
onClick={() => onResolve('approved')}
type="button"
@@ -95,7 +95,7 @@ export function ApprovalBanner({
{canAlwaysApprove && (
<button
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}
onClick={() => onResolve('approved', true)}
title={`Auto-approve "${approvalToolLabel}" for the rest of this session`}
@@ -106,7 +106,7 @@ export function ApprovalBanner({
</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}
onClick={() => onResolve('rejected')}
type="button"
@@ -115,7 +115,7 @@ export function ApprovalBanner({
Reject
</button>
{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
</span>
)}
@@ -130,42 +130,42 @@ export function QueuedApprovalsList({ approvals }: { approvals: PendingApprovalR
const [expanded, setExpanded] = useState(false);
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
aria-expanded={expanded}
className="flex w-full items-center gap-2 text-left"
onClick={() => setExpanded(!expanded)}
type="button"
>
<ShieldCheck className="size-3 text-zinc-500" />
<span className="text-[11px] font-medium text-zinc-400">
<ShieldCheck className="size-3 text-[var(--color-text-muted)]" />
<span className="text-[11px] font-medium text-[var(--color-text-secondary)]">
{approvals.length} queued approval{approvals.length === 1 ? '' : 's'}
</span>
<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>
{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) => {
const kindLabel = approval.kind === 'final-response' ? 'response' : 'tool';
return (
<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}
>
<ShieldAlert className="size-3 shrink-0 text-zinc-600" />
<span className="min-w-0 flex-1 truncate text-[11px] text-zinc-400">
<ShieldAlert className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<span className="min-w-0 flex-1 truncate text-[11px] text-[var(--color-text-secondary)]">
{(approval.permissionDetail && permissionDetailSummary(approval.permissionDetail)) || approval.title}
</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}
</span>
{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 && (
<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>
);
+55 -55
View File
@@ -15,9 +15,9 @@ import { RotateCcw, Server, ShieldCheck } from 'lucide-react';
function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) {
if (!tier) return null;
const styles = {
premium: 'bg-amber-500/10 text-amber-400',
standard: 'bg-zinc-700/50 text-zinc-500',
fast: 'bg-emerald-500/10 text-emerald-400',
premium: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]',
standard: 'bg-[var(--color-surface-3)]/50 text-[var(--color-text-muted)]',
fast: 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]',
};
return (
<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
aria-expanded={open}
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
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: '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={disabled}
onClick={() => setOpen(!open)}
@@ -79,19 +79,19 @@ export function InlineModelPill({
</button>
{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) => (
<div key={pg.id}>
<div className="flex items-center gap-2 px-3 pb-1 pt-2.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}
</span>
</div>
{pg.models.map((model) => (
<button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
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-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
}`}
key={model.id}
onClick={() => { onChange(model.id); setOpen(false); }}
@@ -107,13 +107,13 @@ export function InlineModelPill({
))}
{otherModels.length > 0 && (
<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
</div>
{otherModels.map((model) => (
<button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
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-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
}`}
key={model.id}
onClick={() => { onChange(model.id); setOpen(false); }}
@@ -155,7 +155,7 @@ export function InlineThinkingPill({
if (supportedEfforts && supportedEfforts.length === 0) {
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" />
N/A
</span>
@@ -169,10 +169,10 @@ export function InlineThinkingPill({
<button
aria-expanded={open}
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
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: '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={disabled}
onClick={() => setOpen(!open)}
@@ -184,11 +184,11 @@ export function InlineThinkingPill({
</button>
{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) => (
<button
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
option.value === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
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-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
}`}
key={option.value}
onClick={() => { onChange(option.value); setOpen(false); }}
@@ -235,10 +235,10 @@ export function InlineToolsPill({
<button
aria-expanded={open}
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
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
: '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={disabled}
onClick={() => setOpen(!open)}
@@ -250,7 +250,7 @@ export function InlineToolsPill({
</button>
{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 && (
<McpServerGroup
label="Workspace MCP"
@@ -277,7 +277,7 @@ export function InlineToolsPill({
)}
{lspProfiles.length > 0 && (
<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
</div>
{lspProfiles.map((profile) => (
@@ -315,7 +315,7 @@ function McpServerGroup({
}) {
return (
<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}
</div>
{servers.map((server) => (
@@ -475,12 +475,12 @@ export function InlineApprovalPill({
<button
aria-expanded={open}
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
? '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
? 'border-amber-500/30 bg-amber-500/5 text-amber-400 hover:border-amber-500/50'
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
? '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-[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={disabled}
onClick={() => setOpen(!open)}
@@ -493,26 +493,26 @@ export function InlineApprovalPill({
)}
<span>
{effectiveAutoApprovedCount}/{totalItemCount} auto-approved
{isProbingAny && <span className="text-zinc-500"> · probing</span>}
{isProbingAny && <span className="text-[var(--color-text-muted)]"> · probing</span>}
</span>
<ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} />
</button>
{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 */}
<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">
<span className={`rounded-full px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider ${
isOverridden
? 'bg-amber-500/15 text-amber-400'
: 'bg-zinc-800 text-zinc-500'
? 'bg-[var(--color-status-warning)]/15 text-[var(--color-status-warning)]'
: 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
}`}>
{isOverridden ? 'Session override' : 'Pattern defaults'}
</span>
{isOverridden && (
<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({})}
type="button"
>
@@ -524,12 +524,12 @@ export function InlineApprovalPill({
{/* Search */}
{showSearch && (
<div className="border-t border-zinc-800/50 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">
<Search className="size-3 shrink-0 text-zinc-600" />
<div className="border-t border-[var(--color-border-subtle)] px-3 py-1.5">
<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-[var(--color-text-muted)]" />
<input
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)}
placeholder="Filter tools…"
type="text"
@@ -558,31 +558,31 @@ export function InlineApprovalPill({
<div key={group.id}>
{/* Group header */}
{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}
</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)}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleExpanded(group.id); } }}
role="button"
tabIndex={0}
>
{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 ? (
<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 ? (
<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
</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}
</span>
)}
@@ -619,7 +619,7 @@ export function InlineApprovalPill({
})}
{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}"
</div>
)}
@@ -643,13 +643,13 @@ function GroupToggle({
<button
aria-pressed={allApproved}
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}
type="button"
>
{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
className={`inline-block size-[12px] rounded-full bg-white shadow transition-transform ${
@@ -677,16 +677,16 @@ export function InlineTerminalPill({
return (
<button
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
? 'bg-indigo-500/15 text-indigo-300 hover:bg-indigo-500/20'
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)] hover:bg-[var(--color-accent)]/20'
: '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={disabled}
onClick={onToggle}
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" />
<span>Terminal</span>
</button>
@@ -66,19 +66,19 @@ export function InlinePromptPill({
<button
aria-expanded={open}
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}
onClick={() => setOpen(!open)}
type="button"
>
<FileText className="size-3" />
Prompts
<span className="text-zinc-600">({promptFiles.length})</span>
<span className="text-[var(--color-text-muted)]">({promptFiles.length})</span>
</button>
{open && !disabled && (
<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"
>
{selectedPrompt ? (
@@ -114,24 +114,24 @@ function PromptList({
}) {
return (
<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
</div>
{promptFiles.map((prompt) => (
<button
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)}
role="option"
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="truncate text-[12px] font-medium text-zinc-200">
<div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">
{prompt.name}
</div>
{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}
</div>
)}
@@ -140,7 +140,7 @@ function PromptList({
{prompt.variables.map((v) => (
<span
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}
</span>
@@ -173,7 +173,7 @@ function PromptVariableForm({
<div className="p-3">
<div className="mb-3 flex items-center gap-2">
<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}
type="button"
aria-label="Back to prompt list"
@@ -181,11 +181,11 @@ function PromptVariableForm({
<X className="size-3" />
</button>
<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}
</div>
{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>
@@ -203,10 +203,10 @@ function PromptVariableForm({
</div>
<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
? 'bg-zinc-800 text-zinc-600'
: 'bg-indigo-600 text-white hover:bg-indigo-500'
? 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
: 'brand-gradient-bg text-white hover:brightness-110'
}`}
disabled={submitDisabled}
onClick={onSubmit}
@@ -232,11 +232,11 @@ function PromptVariableInput({
}) {
return (
<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}
</label>
<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)}
onKeyDown={(e) => {
if (e.key === 'Enter' && onSubmit) {
+11 -11
View File
@@ -24,20 +24,20 @@ export function McpAuthBanner({
const hasFailed = mcpAuth.status === 'failed';
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">
<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="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<span className="text-[13px] font-semibold text-amber-200">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="text-[13px] font-semibold text-[var(--color-status-warning)]">Authentication required</span>
<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
</span>
</div>
<button
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}
type="button"
>
@@ -45,21 +45,21 @@ export function McpAuthBanner({
</button>
</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{' '}
<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.
</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 && (
<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">
<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}
onClick={handleAuthenticate}
type="button"
@@ -75,7 +75,7 @@ export function McpAuthBanner({
'Authenticate in browser'
)}
</button>
<span className="text-[11px] text-zinc-500">
<span className="text-[11px] text-[var(--color-text-muted)]">
{isAuthenticating
? 'Waiting for consent in the browser…'
: '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">
{detail.intention && <IntentionLine text={detail.intention} />}
{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" />
<span>{detail.warning}</span>
</div>
@@ -89,8 +89,8 @@ function WriteDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2">
{detail.intention && <IntentionLine text={detail.intention} />}
{detail.fileName && (
<div className="flex items-center gap-1.5 text-[11px] text-zinc-300">
<FileEdit className="size-3 shrink-0 text-zinc-500" />
<div className="flex items-center gap-1.5 text-[11px] text-[var(--color-text-primary)]">
<FileEdit className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<code className="font-mono">{detail.fileName}</code>
</div>
)}
@@ -107,8 +107,8 @@ function ReadDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2">
{detail.intention && <IntentionLine text={detail.intention} />}
{detail.path && (
<div className="flex items-center gap-1.5 text-[11px] text-zinc-300">
<FileText className="size-3 shrink-0 text-zinc-500" />
<div className="flex items-center gap-1.5 text-[11px] text-[var(--color-text-primary)]">
<FileText className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<code className="font-mono">{detail.path}</code>
</div>
)}
@@ -121,14 +121,14 @@ function McpDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2">
<div className="flex flex-wrap items-center gap-2 text-[11px]">
{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" />
{detail.serverName}
</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 && (
<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
</span>
)}
@@ -145,10 +145,10 @@ function UrlDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-2">
{detail.intention && <IntentionLine text={detail.intention} />}
{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" />
<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>
@@ -160,18 +160,18 @@ function MemoryDetail({ detail }: { detail: PermissionDetail }) {
<div className="mt-2.5 space-y-1.5">
{detail.subject && (
<div className="flex items-center gap-1.5 text-[11px]">
<BookOpen className="size-3 shrink-0 text-zinc-500" />
<span className="font-medium text-zinc-300">{detail.subject}</span>
<BookOpen className="size-3 shrink-0 text-[var(--color-text-muted)]" />
<span className="font-medium text-[var(--color-text-primary)]">{detail.subject}</span>
</div>
)}
{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}
</p>
)}
{detail.citations && (
<p className="text-[10px] text-zinc-500">
Source: <span className="text-zinc-400">{detail.citations}</span>
<p className="text-[10px] text-[var(--color-text-muted)]">
Source: <span className="text-[var(--color-text-secondary)]">{detail.citations}</span>
</p>
)}
</div>
@@ -182,7 +182,7 @@ function CustomToolDetail({ detail }: { detail: PermissionDetail }) {
return (
<div className="mt-2.5 space-y-2">
{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 && (
<CollapsibleCode label="Arguments" text={JSON.stringify(detail.args, null, 2)} />
@@ -195,7 +195,7 @@ function HookDetail({ detail }: { detail: PermissionDetail }) {
return (
<div className="mt-2.5 space-y-2">
{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" />
<span>{detail.hookMessage}</span>
</div>
@@ -210,12 +210,12 @@ function HookDetail({ detail }: { detail: PermissionDetail }) {
/* ── Shared primitives ──────────────────────────────────────── */
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 }) {
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}
</pre>
);
@@ -225,12 +225,12 @@ function DiffBlock({ text }: { text: string }) {
const lines = text.split('\n');
return (
<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) => {
let color = 'text-zinc-400';
if (line.startsWith('+')) color = 'text-emerald-400';
else if (line.startsWith('-')) color = 'text-red-400';
else if (line.startsWith('@@')) color = 'text-blue-400';
let color = 'text-[var(--color-text-secondary)]';
if (line.startsWith('+')) color = 'text-[var(--color-status-success)]';
else if (line.startsWith('-')) color = 'text-[var(--color-status-error)]';
else if (line.startsWith('@@')) color = 'text-[var(--color-accent-sky)]';
return (
<div className={color} key={i}>
{line}
@@ -256,10 +256,10 @@ function CollapsibleCode({
const [expanded, setExpanded] = useState(defaultExpanded);
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
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)}
type="button"
>
@@ -269,9 +269,9 @@ function CollapsibleCode({
{label}
</button>
{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 ?? (
<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}
</pre>
)}
@@ -283,9 +283,9 @@ function CollapsibleCode({
function MetaList({ label, items }: { label: string; items: string[] }) {
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="text-zinc-400">{items.join(', ')}</span>
<span className="text-[var(--color-text-secondary)]">{items.join(', ')}</span>
</div>
);
}
@@ -16,21 +16,21 @@ export function PlanReviewBanner({
}, [planReview, onDismiss]);
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 */}
<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="flex items-center justify-between 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="rounded-full bg-emerald-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-emerald-400">
<span className="text-[13px] font-semibold text-[var(--color-status-success)]">Plan ready for review</span>
<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
</span>
</div>
<button
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}
type="button"
>
@@ -39,30 +39,30 @@ export function PlanReviewBanner({
</div>
{planReview.agentName && (
<div className="mt-1 text-[11px] text-zinc-400">
Agent: <span className="text-zinc-300">{planReview.agentName}</span>
<div className="mt-1 text-[11px] text-[var(--color-text-secondary)]">
Agent: <span className="text-[var(--color-text-primary)]">{planReview.agentName}</span>
</div>
)}
{/* 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}
</p>
)}
{/* Plan content (rendered markdown) */}
{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} />
</div>
)}
{/* 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.{' '}
<span className="text-zinc-300">&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;implement the plan&quot;</span>,{' '}
<span className="text-[var(--color-text-primary)]">&quot;adjust step 3&quot;</span>, or ask for a different approach.
</p>
</div>
</div>
@@ -14,11 +14,11 @@ function formatElapsed(startedAt: string): string {
function StatusIcon({ status }: { status: ActiveSubagent['status'] }) {
switch (status) {
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':
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':
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]);
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) {
const borderClass =
subagent.status === 'running'
? 'border-sky-500/20'
? 'border-[var(--color-accent-sky)]/20'
: subagent.status === 'failed'
? 'border-red-500/20'
: 'border-emerald-500/20';
? 'border-[var(--color-status-error)]/20'
: 'border-[var(--color-status-success)]/20';
return (
<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"
aria-label={`Sub-agent ${subagent.name}: ${subagent.activityLabel}`}
>
<StatusIcon status={subagent.status} />
<Bot className="size-3 text-zinc-500" />
<span className="text-[11px] font-medium text-zinc-300">{subagent.name}</span>
<span className="text-[10px] text-zinc-600"></span>
<span className="text-[10px] text-zinc-500">{subagent.activityLabel}</span>
<Bot className="size-3 text-[var(--color-text-muted)]" />
<span className="text-[11px] font-medium text-[var(--color-text-primary)]">{subagent.name}</span>
<span className="text-[10px] text-[var(--color-text-muted)]"></span>
<span className="text-[10px] text-[var(--color-text-secondary)]">{subagent.activityLabel}</span>
{subagent.status === 'running' && <ElapsedTimer startedAt={subagent.startedAt} />}
{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}
</span>
)}
@@ -1,9 +1,9 @@
export function ThinkingDots() {
return (
<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-zinc-500" />
<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-[var(--color-accent-sky)]" />
<span className="thinking-dot size-2 rounded-full bg-[var(--color-accent-purple)]" />
</div>
);
}
@@ -42,25 +42,25 @@ export function UserInputBanner({
);
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 */}
<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="flex items-center gap-2">
<span className="text-[13px] font-semibold text-blue-200">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="text-[13px] font-semibold text-[var(--color-accent-sky)]">Agent question</span>
<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
</span>
</div>
{userInput.agentName && (
<div className="mt-1 text-[11px] text-zinc-400">
Agent: <span className="text-zinc-300">{userInput.agentName}</span>
<div className="mt-1 text-[11px] text-[var(--color-text-secondary)]">
Agent: <span className="text-[var(--color-text-primary)]">{userInput.agentName}</span>
</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}
</p>
</div>
@@ -71,7 +71,7 @@ export function UserInputBanner({
<div className="mt-3 flex flex-wrap gap-2">
{userInput.choices!.map((choice) => (
<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}
key={choice}
onClick={() => handleChoiceClick(choice)}
@@ -88,7 +88,7 @@ export function UserInputBanner({
<div className="mt-3 flex items-center gap-2">
<input
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}
onChange={(e) => setFreeformText(e.target.value)}
onKeyDown={handleKeyDown}
@@ -98,7 +98,7 @@ export function UserInputBanner({
/>
<button
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()}
onClick={handleFreeformSubmit}
type="button"
@@ -16,9 +16,9 @@ const kindIcons: Record<PatternGraphNodeKind, typeof CircleUser> = {
};
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-output': { bg: 'bg-indigo-500/10', border: 'border-indigo-500/30', text: 'text-indigo-400' },
agent: { bg: 'bg-zinc-800/80', border: 'border-zinc-600/40', text: 'text-zinc-200' },
'user-input': { bg: 'bg-[var(--color-accent)]/10', border: 'border-[var(--color-accent)]/30', text: 'text-[var(--color-accent-sky)]' },
'user-output': { bg: 'bg-[var(--color-accent)]/10', border: 'border-[var(--color-accent)]/30', text: 'text-[var(--color-accent-sky)]' },
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' },
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' },
@@ -38,9 +38,9 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
return (
<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
} ${selected ? 'ring-2 ring-indigo-500/50' : ''} ${colors.border}`}
} ${selected ? 'ring-2 ring-[var(--color-accent)]/50' : ''} ${colors.border}`}
>
{renderIcon()}
<div className="min-w-0 flex-1">
@@ -48,11 +48,11 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
{data.label}
</div>
{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>
{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
</span>
)}
@@ -61,8 +61,8 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
}
const handleStyles = {
system: '!size-2 !border-zinc-600 !bg-zinc-400',
agent: '!size-2 !border-indigo-400 !bg-indigo-500',
system: '!size-2 !border-[var(--color-border)] !bg-[var(--color-text-secondary)]',
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',
};
@@ -169,7 +169,7 @@ function PatternGraphCanvasInner({
}, [graph, onGraphChange, fitView]);
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
nodes={nodes.map((n) => ({
...n,
@@ -189,18 +189,18 @@ function PatternGraphCanvasInner({
proOptions={{ hideAttribution: true }}
defaultEdgeOptions={{
type: 'default',
style: { stroke: '#6366f1', strokeWidth: 1.5 },
markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color: '#6366f1' },
style: { stroke: '#245CF9', strokeWidth: 1.5 },
markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color: '#245CF9' },
}}
connectionLineStyle={{ stroke: '#6366f1', strokeWidth: 1.5 }}
connectionLineStyle={{ stroke: '#245CF9', strokeWidth: 1.5 }}
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">
<button
type="button"
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"
>
<LayoutGrid className="size-3.5" />
@@ -44,10 +44,10 @@ function InputField({
placeholder?: string;
}) {
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 (
<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 ? (
<textarea
className={`${base} min-h-20 resize-y`}
@@ -97,17 +97,17 @@ function SystemNodeInspector({ kind }: { kind: PatternGraphNodeKind }) {
return (
<div className="space-y-3">
<div className="flex items-center gap-2.5">
<div className="flex size-8 items-center justify-center rounded-lg bg-indigo-500/10">
<Icon className="size-4 text-indigo-400" />
<div className="flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent)]/10">
<Icon className="size-4 text-[var(--color-accent-sky)]" />
</div>
<div>
<div className="text-[13px] font-semibold text-zinc-200">{kindLabels[kind]}</div>
<span className="rounded bg-zinc-700/50 px-1.5 py-0.5 text-[9px] font-medium text-zinc-500">
<div className="text-[13px] font-semibold text-[var(--color-text-primary)]">{kindLabels[kind]}</div>
<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
</span>
</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>
);
}
@@ -140,16 +140,16 @@ function AgentNodeInspector({
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className="flex size-8 items-center justify-center rounded-lg bg-zinc-800">
<Bot className="size-4 text-zinc-300" />
<div className="flex size-8 items-center justify-center rounded-lg bg-[var(--color-surface-2)]">
<Bot className="size-4 text-[var(--color-text-secondary)]" />
</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 className="flex items-center gap-1">
{showReorder && (
<>
<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}
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'up'))}
title="Move earlier in sequence"
@@ -158,7 +158,7 @@ function AgentNodeInspector({
<ChevronUp className="size-3.5" />
</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}
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'down'))}
title="Move later in sequence"
@@ -169,7 +169,7 @@ function AgentNodeInspector({
</>
)}
<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)}
type="button"
>
@@ -235,7 +235,7 @@ export function PatternGraphInspector({
if (!selectedNodeId) {
return (
<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
</p>
</div>
@@ -29,7 +29,7 @@ export function LspProfileEditor({
title={profile.name || 'Untitled LSP Profile'}
>
<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
</h4>
<div className="grid gap-4 sm:grid-cols-2">
@@ -50,7 +50,7 @@ export function LspProfileEditor({
</section>
<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
</h4>
<FormField label="Command" required>
@@ -71,7 +71,7 @@ export function LspProfileEditor({
</section>
<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
</h4>
<FormField label="File extensions" required>
@@ -29,7 +29,7 @@ export function McpServerEditor({
title={server.name || 'Untitled MCP Server'}
>
<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
</h4>
<div className="grid gap-4 sm:grid-cols-2">
@@ -54,7 +54,7 @@ export function McpServerEditor({
</section>
<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'}
</h4>
{server.transport === 'local' ? (
@@ -94,7 +94,7 @@ export function McpServerEditor({
</section>
<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
</h4>
<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="flex items-center gap-3">
<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}
type="button"
>
<ChevronLeft className="size-4" />
</button>
<div>
<h2 className="text-[13px] font-semibold text-zinc-100">{title}</h2>
<p className="text-[12px] text-zinc-500">{subtitle}</p>
<h2 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h2>
<p className="text-[12px] text-[var(--color-text-muted)]">{subtitle}</p>
</div>
</div>
<div className="no-drag flex items-center gap-2">
{onDelete && (
<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()}
type="button"
>
@@ -48,7 +48,7 @@ export function ToolingEditorShell({
</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}
onClick={() => void onSave()}
type="button"
@@ -61,7 +61,7 @@ export function ToolingEditorShell({
<div className="flex-1 overflow-y-auto px-6 py-5">
<div className="mx-auto max-w-2xl space-y-8">
{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" />
{error}
</div>
+2 -2
View File
@@ -11,9 +11,9 @@ export function FormField({
}) {
return (
<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}
{required && <span className="ml-1 text-amber-400">*</span>}
{required && <span className="ml-1 text-[var(--color-status-warning)]">*</span>}
</span>
{children}
</label>
+2 -2
View File
@@ -3,8 +3,8 @@ import type { ReactNode } from 'react';
export function InfoCallout({ children }: { children: ReactNode }) {
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">
<Info className="mt-0.5 size-3.5 shrink-0 text-zinc-600" />
<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-[var(--color-accent)]" />
<span>{children}</span>
</div>
);
@@ -10,13 +10,13 @@ export interface PopoverToggleRowProps {
export function PopoverToggleRow({ label, detail, enabled, onToggle }: PopoverToggleRowProps) {
return (
<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}
type="button"
>
<div className="min-w-0 flex-1">
<div className="truncate text-[12px] font-medium text-zinc-300">{label}</div>
{detail && <div className="truncate text-[10px] text-zinc-600">{detail}</div>}
<div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">{label}</div>
{detail && <div className="truncate text-[10px] text-[var(--color-text-muted)]">{detail}</div>}
</div>
<ToggleSwitch enabled={enabled} size="sm" />
</button>
+1 -1
View File
@@ -9,7 +9,7 @@ export function SelectInput({
}) {
return (
<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)}
value={value}
>
+1 -1
View File
@@ -13,7 +13,7 @@ export function TextInput({
}) {
return (
<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}
onChange={(event) => onChange(event.target.value)}
placeholder={placeholder}
+1 -1
View File
@@ -11,7 +11,7 @@ export function TextareaInput({
}) {
return (
<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)}
placeholder={placeholder}
rows={rows}
+2 -2
View File
@@ -10,8 +10,8 @@ export function ToggleSwitch({ enabled, size = 'md' }: ToggleSwitchProps) {
return (
<span
className={`relative inline-flex ${trackSize} shrink-0 items-center rounded-full transition-colors ${
enabled ? 'bg-indigo-500' : 'bg-zinc-700'
className={`relative inline-flex ${trackSize} shrink-0 items-center rounded-full transition-all duration-200 ${
enabled ? 'brand-gradient-bg shadow-[0_0_8px_rgba(36,92,249,0.3)]' : 'bg-[var(--color-surface-3)]'
}`}
>
<span
+1 -1
View File
@@ -8,7 +8,7 @@
/>
<title>aryx</title>
</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>
<script
type="module"
+4
View File
@@ -1,6 +1,10 @@
import { StrictMode } from 'react';
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 '@renderer/styles.css';
+279 -129
View File
@@ -1,79 +1,133 @@
@import "tailwindcss";
/* ── Design tokens: "Luminous Depth" ────────────────────────── */
@theme {
--color-surface-0: #09090b;
--color-surface-1: #18181b;
--color-surface-2: #27272a;
--color-surface-3: #3f3f46;
--color-border: #27272a;
--color-border-subtle: #1e1e22;
--color-accent: #6366f1;
--color-accent-hover: #818cf8;
--color-accent-muted: rgba(99, 102, 241, 0.15);
/* Surface palette — blue-tinted darks */
--color-surface-0: #07080e;
--color-surface-1: #0e1019;
--color-surface-2: #161924;
--color-surface-3: #1e2233;
--color-surface-4: #272b3d;
--color-border: #1a1e2e;
--color-border-subtle: #121520;
/* 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--line-height: 1.2;
}
:root {
color-scheme: dark;
font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
font-family: "DM Sans Variable", "DM Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
-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"] {
color-scheme: light;
/* Remap semantic surface tokens */
--color-surface-0: #ffffff;
--color-surface-1: #f4f4f5;
--color-surface-2: #e4e4e7;
--color-surface-3: #d4d4d8;
--color-border: #e4e4e7;
--color-border-subtle: #f0f0f2;
--color-accent-muted: rgba(99, 102, 241, 0.10);
/* Surfaces — cool-tinted whites */
--color-surface-0: #f8f9fc;
--color-surface-1: #f0f1f6;
--color-surface-2: #e6e8f0;
--color-surface-3: #d8dbe6;
--color-surface-4: #c8cdd9;
--color-border: #dfe1ea;
--color-border-subtle: #eeeff4;
/* Swap zinc scale: dark values become light equivalents.
Tailwind v4 exposes these as --color-zinc-{step}. */
--color-zinc-50: #18181b;
--color-zinc-100: #27272a;
--color-zinc-200: #3f3f46;
--color-zinc-300: #52525b;
--color-zinc-400: #71717a;
--color-zinc-500: #71717a;
--color-zinc-600: #a1a1aa;
--color-zinc-700: #d4d4d8;
--color-zinc-800: #e4e4e7;
--color-zinc-900: #f4f4f5;
--color-zinc-950: #ffffff;
/* Text — deep blue-blacks */
--color-text-primary: #0f1729;
--color-text-secondary: #5a6078;
--color-text-muted: #9298ad;
--color-text-accent: #245CF9;
/* Glass — light translucency */
--color-glass: rgba(240, 241, 246, 0.75);
--color-glass-hover: rgba(230, 232, 240, 0.8);
--color-glass-border: rgba(223, 225, 234, 0.6);
--color-border-glow: rgba(36, 92, 249, 0.2);
--color-accent-muted: rgba(36, 92, 249, 0.08);
/* 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-white: #ffffff;
}
/* ── Global base styles ──────────────────────────────────────── */
body {
margin: 0;
min-height: 100vh;
background: var(--color-surface-0);
color: var(--color-text-primary);
}
#root {
min-height: 100vh;
}
/* Scrollbar styling */
/* ── Scrollbar ───────────────────────────────────────────────── */
::-webkit-scrollbar {
width: 6px;
width: 5px;
}
::-webkit-scrollbar-track {
@@ -86,14 +140,11 @@ body {
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-hover, #52525b);
background: var(--color-surface-4);
}
[data-theme="light"] {
--scrollbar-hover: #a1a1aa;
}
/* ── Window drag regions ─────────────────────────────────────── */
/* Window drag regions for custom title bar */
.drag-region {
-webkit-app-region: drag;
}
@@ -102,22 +153,58 @@ body {
-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 {
0%,
60%,
100% {
opacity: 0.3;
0%, 80%, 100% {
opacity: 0.25;
transform: scale(0.8);
}
30% {
40% {
opacity: 1;
transform: scale(1);
transform: scale(1.1);
}
}
.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) {
@@ -128,14 +215,38 @@ body {
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 {
field-sizing: content;
min-height: 44px;
max-height: 200px;
}
/* ── Markdown composer (Lexical) ────────────────────────── */
/* ── Markdown composer (Lexical) ─────────────────────────────── */
.markdown-composer-content {
position: relative;
@@ -146,9 +257,9 @@ body {
max-height: 200px;
overflow-y: auto;
padding: 10px 16px 4px 16px;
font-size: 14px;
font-size: 13.5px;
line-height: 1.6;
color: #f4f4f5;
color: var(--color-text-primary);
outline: none;
cursor: text;
}
@@ -162,9 +273,9 @@ body {
position: absolute;
top: 10px;
left: 16px;
font-size: 14px;
font-size: 13.5px;
line-height: 1.6;
color: #52525b;
color: var(--color-text-muted);
pointer-events: none;
user-select: none;
}
@@ -176,6 +287,7 @@ body {
/* Headings */
.mc-h1, .mc-h2, .mc-h3 {
font-family: var(--font-display);
font-weight: 600;
margin: 0;
line-height: 1.4;
@@ -190,11 +302,11 @@ body {
.mc-strikethrough { text-decoration: line-through; }
.mc-underline { text-decoration: underline; }
.mc-inline-code {
background: rgba(63, 63, 70, 0.6);
border-radius: 3px;
padding: 1px 4px;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
font-size: 0.9em;
background: rgba(30, 34, 51, 0.7);
border-radius: 4px;
padding: 1px 5px;
font-family: var(--font-mono);
font-size: 0.88em;
}
/* Lists */
@@ -214,28 +326,28 @@ body {
/* Blockquote */
.mc-blockquote {
border-left: 3px solid #3f3f46;
border-left: 3px solid var(--color-surface-4);
padding-left: 0.75em;
color: #a1a1aa;
color: var(--color-text-secondary);
margin: 2px 0;
}
/* Code block */
.mc-code-block {
display: block;
background: rgba(24, 24, 27, 0.8);
border: 1px solid #27272a;
border-radius: 6px;
padding: 26px 10px 6px 10px;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
font-size: 0.9em;
background: rgba(7, 8, 14, 0.8);
border: 1px solid var(--color-border);
border-radius: 8px;
padding: 26px 12px 8px 12px;
font-family: var(--font-mono);
font-size: 0.88em;
white-space: pre-wrap;
tab-size: 2;
margin: 4px 0;
overflow-x: auto;
}
/* Language selector overlays positioned outside Lexical-managed DOM */
/* Language selector overlays */
.mc-code-overlays-container {
position: absolute;
top: 0;
@@ -256,74 +368,76 @@ body {
.mc-code-lang-select {
appearance: none;
-webkit-appearance: none;
background: rgba(39, 39, 42, 0.9);
border: 1px solid #3f3f46;
background: var(--color-surface-2);
border: 1px solid var(--color-border);
border-radius: 4px;
color: #a1a1aa;
color: var(--color-text-secondary);
font-family: inherit;
font-size: 10px;
padding: 2px 18px 2px 6px;
outline: none;
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-position: right 3px center;
background-size: 10px;
}
.mc-code-lang-select:hover {
border-color: #52525b;
color: #d4d4d8;
border-color: var(--color-surface-4);
color: var(--color-text-primary);
}
.mc-code-lang-select:focus {
border-color: rgba(99, 102, 241, 0.5);
border-color: var(--color-border-glow);
}
/* Link */
.mc-link {
color: #818cf8;
color: var(--color-text-accent);
text-decoration: underline;
text-underline-offset: 2px;
}
/* Syntax highlighting tokens (dark theme) */
.mc-tok-comment { color: #6b7280; font-style: italic; }
.mc-tok-keyword { color: #c084fc; }
.mc-tok-string { color: #86efac; }
.mc-tok-number { color: #fbbf24; }
.mc-tok-boolean { color: #fbbf24; }
.mc-tok-constant { color: #fbbf24; }
.mc-tok-function { color: #93c5fd; }
.mc-tok-builtin { color: #67e8f9; }
.mc-tok-operator { color: #f9a8d4; }
.mc-tok-punctuation { color: #a1a1aa; }
.mc-tok-property { color: #7dd3fc; }
.mc-tok-variable { color: #fca5a5; }
.mc-tok-tag { color: #f87171; }
.mc-tok-attr { color: #fdba74; }
.mc-tok-atrule { color: #c084fc; }
.mc-tok-selector { color: #86efac; }
.mc-tok-regex { color: #fcd34d; }
.mc-tok-symbol { color: #fbbf24; }
.mc-tok-namespace { color: #f87171; opacity: 0.8; }
.mc-tok-deleted { color: #fca5a5; text-decoration: line-through; }
.mc-tok-inserted { color: #86efac; }
/* ── Syntax highlighting tokens ──────────────────────────────── */
.mc-tok-comment { color: #5a6078; font-style: italic; }
.mc-tok-keyword { color: #b07ef5; }
.mc-tok-string { color: #6ee7b7; }
.mc-tok-number { color: #f9c846; }
.mc-tok-boolean { color: #f9c846; }
.mc-tok-constant { color: #f9c846; }
.mc-tok-function { color: #7ba1fc; }
.mc-tok-builtin { color: #5ccbee; }
.mc-tok-operator { color: #e8a0c8; }
.mc-tok-punctuation { color: #8b90a5; }
.mc-tok-property { color: #65b8f7; }
.mc-tok-variable { color: #f5a0a0; }
.mc-tok-tag { color: #f07070; }
.mc-tok-attr { color: #f0b070; }
.mc-tok-atrule { color: #b07ef5; }
.mc-tok-selector { color: #6ee7b7; }
.mc-tok-regex { color: #f0d860; }
.mc-tok-symbol { color: #f9c846; }
.mc-tok-namespace { color: #f07070; opacity: 0.8; }
.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 {
font-family: inherit;
}
.react-flow__edge-path {
stroke: #52525b;
stroke: var(--color-surface-4);
stroke-width: 1.5;
}
.react-flow__edge.selected .react-flow__edge-path {
stroke: #818cf8;
stroke: #245CF9;
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 {
@@ -338,32 +452,33 @@ body {
}
.react-flow__controls {
border: 1px solid #27272a;
background: #18181b;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid var(--color-border);
background: var(--color-surface-1);
border-radius: 10px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.react-flow__controls button {
background: #18181b;
border-color: #27272a;
color: #a1a1aa;
background: var(--color-surface-1);
border-color: var(--color-border);
color: var(--color-text-secondary);
width: 28px;
height: 28px;
}
.react-flow__controls button:hover {
background: #27272a;
color: #e4e4e7;
background: var(--color-surface-2);
color: var(--color-text-primary);
}
.react-flow__minimap {
background: #09090b;
border: 1px solid #27272a;
border-radius: 8px;
background: var(--color-surface-0);
border: 1px solid var(--color-border);
border-radius: 10px;
}
/* Sidebar running-session pulse animation */
/* ── Sidebar running-session pulse ───────────────────────────── */
@keyframes sidebar-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
@@ -373,7 +488,41 @@ body {
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 {
line-height: 1.7;
word-break: break-word;
@@ -393,11 +542,12 @@ body {
.markdown-content h4,
.markdown-content h5,
.markdown-content h6 {
font-family: var(--font-display);
font-weight: 600;
color: var(--color-zinc-200);
color: var(--color-text-primary);
margin-top: 1.25em;
margin-bottom: 0.5em;
line-height: 1.4;
line-height: 1.35;
}
.markdown-content h1 { font-size: 1.125rem; }
@@ -430,9 +580,9 @@ body {
}
.markdown-content blockquote {
border-left: 3px solid var(--color-surface-3);
border-left: 3px solid var(--color-surface-4);
padding-left: 1em;
color: var(--color-zinc-400);
color: var(--color-text-secondary);
margin-top: 0.75em;
margin-bottom: 0.75em;
}
@@ -455,7 +605,7 @@ body {
.markdown-content th {
background: var(--color-surface-1);
font-weight: 600;
color: var(--color-zinc-300);
color: var(--color-text-primary);
}
.markdown-content hr {
@@ -466,7 +616,7 @@ body {
.markdown-content strong {
font-weight: 600;
color: var(--color-zinc-200);
color: var(--color-text-primary);
}
.markdown-content em {