mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-30 06:27:13 +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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user