mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-23 21:18:40 +02:00
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:
@@ -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=="],
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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)}%` }}
|
||||
/>
|
||||
|
||||
@@ -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 & 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'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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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">"implement the plan"</span>,{' '}
|
||||
<span className="text-zinc-300">"adjust step 3"</span>, or ask for a different approach.
|
||||
<span className="text-[var(--color-text-primary)]">"implement the plan"</span>,{' '}
|
||||
<span className="text-[var(--color-text-primary)]">"adjust step 3"</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user