feat(web): Dark mode contrast — raise token floor and flatten opacity tier system (#2734)
* feat: Raised four dark-mode tokens, converted five hardcoded oklch valu… - "web/app/globals.css" - "web/components/gsd/code-editor.tsx" GSD-Task: S01/T01 * feat: Applied border-border 2-tier sweep across 21 component files: /20… - "web/components/gsd/command-surface.tsx" - "web/components/gsd/remaining-command-panels.tsx" - "web/components/gsd/chat-mode.tsx" - "web/components/gsd/settings-panels.tsx" - "web/components/gsd/diagnostics-panels.tsx" - "web/components/gsd/onboarding/step-authenticate.tsx" - "web/components/gsd/knowledge-captures-panel.tsx" - "web/components/gsd/projects-view.tsx" GSD-Task: S02/T01 * feat: Swept text-foreground/muted-foreground/sidebar-foreground opacity… - "web/components/gsd/command-surface.tsx" - "web/components/gsd/remaining-command-panels.tsx" - "web/components/gsd/chat-mode.tsx" - "web/components/gsd/settings-panels.tsx" - "web/components/gsd/diagnostics-panels.tsx" - "web/components/gsd/knowledge-captures-panel.tsx" - "web/components/gsd/projects-view.tsx" - "web/components/gsd/visualizer-view.tsx" GSD-Task: S02/T02 * feat: Applied background opacity mapping tables across all component fi… - "web/components/gsd/remaining-command-panels.tsx" - "web/components/gsd/command-surface.tsx" - "web/components/gsd/visualizer-view.tsx" - "web/components/gsd/chat-mode.tsx" - "web/components/gsd/settings-panels.tsx" - "web/components/gsd/diagnostics-panels.tsx" - "web/components/gsd/onboarding/step-authenticate.tsx" - "web/components/gsd/knowledge-captures-panel.tsx" GSD-Task: S02/T03
This commit is contained in:
parent
74c1736372
commit
07d804588e
34 changed files with 392 additions and 392 deletions
|
|
@ -60,12 +60,12 @@
|
|||
--secondary: oklch(0.18 0 0);
|
||||
--secondary-foreground: oklch(0.85 0 0);
|
||||
--muted: oklch(0.15 0 0);
|
||||
--muted-foreground: oklch(0.55 0 0);
|
||||
--muted-foreground: oklch(0.60 0 0);
|
||||
--accent: oklch(0.2 0 0);
|
||||
--accent-foreground: oklch(0.9 0 0);
|
||||
--destructive: oklch(0.5 0.15 25);
|
||||
--destructive-foreground: oklch(0.95 0 0);
|
||||
--border: oklch(0.22 0 0);
|
||||
--border: oklch(0.28 0 0);
|
||||
--input: oklch(0.15 0 0);
|
||||
--ring: oklch(0.4 0 0);
|
||||
--chart-1: oklch(0.7 0 0);
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
--sidebar-primary-foreground: oklch(0.09 0 0);
|
||||
--sidebar-accent: oklch(0.15 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.9 0 0);
|
||||
--sidebar-border: oklch(0.18 0 0);
|
||||
--sidebar-border: oklch(0.24 0 0);
|
||||
--sidebar-ring: oklch(0.35 0 0);
|
||||
|
||||
/* Custom tokens */
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
--info: oklch(0.6 0.1 250);
|
||||
--terminal: oklch(0.06 0 0);
|
||||
--terminal-foreground: oklch(0.75 0 0);
|
||||
--code-line-number: oklch(0.35 0 0);
|
||||
--code-line-number: oklch(0.42 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
width: 3.5ch;
|
||||
margin-right: 1.5ch;
|
||||
text-align: right;
|
||||
color: oklch(0.35 0 0);
|
||||
color: var(--code-line-number);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid oklch(0.22 0 0);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
margin-top: 1.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.35rem;
|
||||
border-bottom: 1px solid oklch(0.22 0 0);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
|
|
@ -289,14 +289,14 @@
|
|||
.markdown-body blockquote {
|
||||
margin: 0.75rem 0;
|
||||
padding: 0.25rem 1rem;
|
||||
border-left: 3px solid oklch(0.3 0 0);
|
||||
border-left: 3px solid oklch(0.38 0 0);
|
||||
color: oklch(0.6 0 0);
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
margin: 1.5rem 0;
|
||||
border: none;
|
||||
border-top: 1px solid oklch(0.22 0 0);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
|
|
@ -310,7 +310,7 @@
|
|||
|
||||
.markdown-body del {
|
||||
text-decoration: line-through;
|
||||
color: oklch(0.5 0 0);
|
||||
color: oklch(0.55 0 0);
|
||||
}
|
||||
|
||||
/* Task list checkboxes */
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ function WorkspaceChrome() {
|
|||
beta
|
||||
</Badge>
|
||||
</div>
|
||||
<span className="hidden sm:inline text-2xl font-thin text-muted-foreground/50 leading-none select-none">/</span>
|
||||
<span className="hidden sm:inline text-2xl font-thin text-muted-foreground leading-none select-none">/</span>
|
||||
<span className="hidden sm:inline text-sm text-muted-foreground truncate" data-testid="workspace-project-cwd" title={projectPath ?? undefined}>
|
||||
{isConnecting ? (
|
||||
<Skeleton className="inline-block h-4 w-28 align-middle" />
|
||||
|
|
@ -427,7 +427,7 @@ function WorkspaceChrome() {
|
|||
>
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<span className="font-medium text-foreground">Terminal</span>
|
||||
<span className="text-[10px] text-muted-foreground/50">
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{isTerminalExpanded ? "▼" : "▲"}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ function MarkdownContent({ content }: { content: string }) {
|
|||
})
|
||||
return (
|
||||
<div
|
||||
className="chat-code-block my-3 rounded-xl overflow-x-auto text-sm shadow-sm border border-border/40"
|
||||
className="chat-code-block my-3 rounded-xl overflow-x-auto text-sm shadow-sm border border-border/50"
|
||||
dangerouslySetInnerHTML={{ __html: highlighted }}
|
||||
/>
|
||||
)
|
||||
|
|
@ -348,7 +348,7 @@ function MarkdownContent({ content }: { content: string }) {
|
|||
if (isInline) {
|
||||
return (
|
||||
<code
|
||||
className="rounded-md bg-muted/80 px-1.5 py-0.5 text-[0.85em] font-mono text-foreground"
|
||||
className="rounded-md bg-muted px-1.5 py-0.5 text-[0.85em] font-mono text-foreground"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
|
@ -357,7 +357,7 @@ function MarkdownContent({ content }: { content: string }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<pre className={cn("my-3 overflow-x-auto rounded-xl p-4 text-sm border border-border/40", isDark ? "bg-[#0d1117]" : "bg-[#f6f8fa]")}>
|
||||
<pre className={cn("my-3 overflow-x-auto rounded-xl p-4 text-sm border border-border/50", isDark ? "bg-[#0d1117]" : "bg-[#f6f8fa]")}>
|
||||
<code className="font-mono">{children}</code>
|
||||
</pre>
|
||||
)
|
||||
|
|
@ -374,7 +374,7 @@ function MarkdownContent({ content }: { content: string }) {
|
|||
},
|
||||
th({ children }: { children?: React.ReactNode }) {
|
||||
return (
|
||||
<th className="border-b border-border bg-muted/40 px-3 py-2 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wide">
|
||||
<th className="border-b border-border bg-muted/50 px-3 py-2 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wide">
|
||||
{children}
|
||||
</th>
|
||||
)
|
||||
|
|
@ -424,7 +424,7 @@ function MarkdownContent({ content }: { content: string }) {
|
|||
},
|
||||
img({ alt, src }: { alt?: string; src?: string }) {
|
||||
return (
|
||||
<span className="my-2 block rounded-lg border border-border bg-muted/20 px-3 py-2 text-xs text-muted-foreground italic">
|
||||
<span className="my-2 block rounded-lg border border-border bg-muted/50 px-3 py-2 text-xs text-muted-foreground italic">
|
||||
🖼 {alt || src || "image"}
|
||||
</span>
|
||||
)
|
||||
|
|
@ -559,7 +559,7 @@ function TuiSelectPrompt({
|
|||
data-testid="tui-select-prompt"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="mt-2 rounded-xl border border-border/60 bg-background/60 p-1.5 shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-border"
|
||||
className="mt-2 rounded-xl border border-border bg-background p-1.5 shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-border"
|
||||
aria-label={`Select: ${prompt.label}`}
|
||||
role="listbox"
|
||||
aria-activedescendant={`tui-select-option-${localIndex}`}
|
||||
|
|
@ -584,7 +584,7 @@ function TuiSelectPrompt({
|
|||
"flex w-full items-start gap-2 rounded-lg px-3 py-1.5 text-left text-sm transition-colors",
|
||||
isSelected
|
||||
? "bg-primary/15 text-primary font-medium"
|
||||
: "text-foreground hover:bg-muted/60",
|
||||
: "text-foreground hover:bg-muted",
|
||||
)}
|
||||
>
|
||||
<span className="mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center">
|
||||
|
|
@ -671,7 +671,7 @@ function TuiTextPrompt({
|
|||
return (
|
||||
<div
|
||||
data-testid="tui-text-prompt"
|
||||
className="mt-2 rounded-xl border border-border/60 bg-background/60 p-3 shadow-sm"
|
||||
className="mt-2 rounded-xl border border-border bg-background p-3 shadow-sm"
|
||||
>
|
||||
{prompt.label && (
|
||||
<p className="mb-2 text-[11px] font-medium text-muted-foreground uppercase tracking-wide">
|
||||
|
|
@ -695,7 +695,7 @@ function TuiTextPrompt({
|
|||
"flex h-8 items-center justify-center rounded-lg px-3 text-xs font-medium transition-all",
|
||||
value.trim()
|
||||
? "bg-primary text-primary-foreground hover:bg-primary/90 active:scale-95 shadow-sm"
|
||||
: "bg-muted text-muted-foreground/40 cursor-not-allowed",
|
||||
: "bg-muted text-muted-foreground cursor-not-allowed",
|
||||
)}
|
||||
>
|
||||
Submit
|
||||
|
|
@ -771,7 +771,7 @@ function TuiPasswordPrompt({
|
|||
return (
|
||||
<div
|
||||
data-testid="tui-password-prompt"
|
||||
className="mt-2 rounded-xl border border-border/60 bg-background/60 p-3 shadow-sm"
|
||||
className="mt-2 rounded-xl border border-border bg-background p-3 shadow-sm"
|
||||
>
|
||||
{prompt.label && (
|
||||
<p className="mb-2 text-[11px] font-medium text-muted-foreground uppercase tracking-wide">
|
||||
|
|
@ -796,7 +796,7 @@ function TuiPasswordPrompt({
|
|||
onClick={() => setShowPassword((s) => !s)}
|
||||
tabIndex={-1}
|
||||
aria-label={showPassword ? "Hide input" : "Show input"}
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/50 hover:text-muted-foreground transition-colors"
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-muted-foreground transition-colors"
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="h-3.5 w-3.5" />
|
||||
|
|
@ -812,13 +812,13 @@ function TuiPasswordPrompt({
|
|||
"flex h-8 items-center justify-center rounded-lg px-3 text-xs font-medium transition-all",
|
||||
value
|
||||
? "bg-primary text-primary-foreground hover:bg-primary/90 active:scale-95 shadow-sm"
|
||||
: "bg-muted text-muted-foreground/40 cursor-not-allowed",
|
||||
: "bg-muted text-muted-foreground cursor-not-allowed",
|
||||
)}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1.5 text-[10px] text-muted-foreground/50">
|
||||
<p className="mt-1.5 text-[10px] text-muted-foreground">
|
||||
Value is transmitted securely and not stored in chat history.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -910,7 +910,7 @@ function InlineThinking({ content, isStreaming }: { content: string; isStreaming
|
|||
onClick={() => setExpanded((e) => !e)}
|
||||
className={cn(
|
||||
"group w-full rounded-xl border px-3.5 py-2.5 text-left transition-all",
|
||||
"border-border/40 bg-muted/20 hover:bg-muted/30",
|
||||
"border-border/50 bg-muted/50 hover:bg-muted/50",
|
||||
)}
|
||||
>
|
||||
{/* Header row */}
|
||||
|
|
@ -922,21 +922,21 @@ function InlineThinking({ content, isStreaming }: { content: string; isStreaming
|
|||
</span>
|
||||
) : (
|
||||
<span className="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded bg-muted-foreground/10">
|
||||
<span className="text-[9px] text-muted-foreground/50">💭</span>
|
||||
<span className="text-[9px] text-muted-foreground">💭</span>
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/50">
|
||||
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{isStreaming ? "Thinking…" : "Thought process"}
|
||||
</span>
|
||||
{hasMore && !expanded && (
|
||||
<span className="ml-1 rounded-full bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground/40">
|
||||
<span className="ml-1 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
||||
{lines.length} lines
|
||||
</span>
|
||||
)}
|
||||
<span className="ml-auto flex-shrink-0">
|
||||
{expanded
|
||||
? <ChevronDown className="h-3 w-3 text-muted-foreground/40 transition-transform" />
|
||||
: <ChevronRight className="h-3 w-3 text-muted-foreground/40 transition-transform group-hover:text-muted-foreground/60" />
|
||||
? <ChevronDown className="h-3 w-3 text-muted-foreground transition-transform" />
|
||||
: <ChevronRight className="h-3 w-3 text-muted-foreground transition-transform group-hover:text-muted-foreground" />
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -945,7 +945,7 @@ function InlineThinking({ content, isStreaming }: { content: string; isStreaming
|
|||
{!expanded && (
|
||||
<div className="mt-2 space-y-0.5 border-l-2 border-muted-foreground/10 pl-3">
|
||||
{previewLines.map((line, i) => (
|
||||
<p key={i} className="text-[12px] leading-relaxed text-muted-foreground/50 line-clamp-1">
|
||||
<p key={i} className="text-[12px] leading-relaxed text-muted-foreground line-clamp-1">
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
|
|
@ -957,7 +957,7 @@ function InlineThinking({ content, isStreaming }: { content: string; isStreaming
|
|||
{expanded && (
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="mt-2 max-h-[400px] overflow-y-auto overscroll-contain rounded-lg border border-border/30 bg-background/40 p-3 text-[12px] leading-[1.7] text-muted-foreground/60 whitespace-pre-wrap scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent"
|
||||
className="mt-2 max-h-[400px] overflow-y-auto overscroll-contain rounded-lg border border-border/50 bg-background/50 p-3 text-[12px] leading-[1.7] text-muted-foreground whitespace-pre-wrap scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent"
|
||||
>
|
||||
{content}
|
||||
{isStreaming && <StreamingCursor />}
|
||||
|
|
@ -991,7 +991,7 @@ function ChatBubble({
|
|||
if (message.role === "system") {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-1">
|
||||
<span className="text-[11px] text-muted-foreground/60 italic px-3">
|
||||
<span className="text-[11px] text-muted-foreground italic px-3">
|
||||
{message.content}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -1047,7 +1047,7 @@ function ChatBubble({
|
|||
<div className="mt-1 flex-shrink-0 flex h-7 w-7 items-center justify-center rounded-full bg-card border border-border">
|
||||
<PlatformLogoIcon className="h-3.5 w-auto" />
|
||||
</div>
|
||||
<div className="max-w-[82%] min-w-0 rounded-2xl rounded-tl-md border border-border/60 bg-card px-4 py-3 shadow-sm">
|
||||
<div className="max-w-[82%] min-w-0 rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm">
|
||||
{/* Minimal waiting indicator — shown when streaming starts but no content yet */}
|
||||
{isThinking && !message.content && (
|
||||
<div className="flex items-center gap-2 py-1">
|
||||
|
|
@ -1055,7 +1055,7 @@ function ChatBubble({
|
|||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-muted-foreground/30" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-muted-foreground/50" />
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider">
|
||||
<span className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
|
||||
Thinking…
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -1326,7 +1326,7 @@ function ChatInputBar({
|
|||
const overflowGroups = useMemo(() => groupByCategory(OVERFLOW_ACTIONS), [])
|
||||
|
||||
return (
|
||||
<div className="flex-shrink-0 border-t border-border bg-card/80 px-4 py-3 backdrop-blur-sm">
|
||||
<div className="flex-shrink-0 border-t border-border bg-card px-4 py-3 backdrop-blur-sm">
|
||||
<div
|
||||
className="flex items-end gap-2"
|
||||
onDrop={handleDrop}
|
||||
|
|
@ -1339,8 +1339,8 @@ function ChatInputBar({
|
|||
className={cn(
|
||||
"flex flex-1 flex-col rounded-xl border bg-background transition-colors",
|
||||
connected
|
||||
? "border-border focus-within:border-border/80 focus-within:ring-1 focus-within:ring-border/30"
|
||||
: "border-border/40 opacity-60",
|
||||
? "border-border focus-within:ring-1 focus-within:ring-border/30"
|
||||
: "border-border/50 opacity-80",
|
||||
isDragging && connected && "border-primary/60 ring-2 ring-primary/20 bg-primary/5",
|
||||
)}
|
||||
>
|
||||
|
|
@ -1367,7 +1367,7 @@ function ChatInputBar({
|
|||
</div>
|
||||
))}
|
||||
{imageNotice && (
|
||||
<span className="text-[10px] text-muted-foreground/70 italic">{imageNotice}</span>
|
||||
<span className="text-[10px] text-muted-foreground italic">{imageNotice}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1386,12 +1386,12 @@ function ChatInputBar({
|
|||
? "Message…"
|
||||
: "Connecting…"
|
||||
}
|
||||
className="min-h-[40px] flex-1 resize-none bg-transparent px-3 py-2.5 text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none disabled:cursor-not-allowed disabled:text-muted-foreground"
|
||||
className="min-h-[40px] flex-1 resize-none bg-transparent px-3 py-2.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:text-muted-foreground"
|
||||
style={{ height: "40px", maxHeight: "160px", overflowY: "auto" }}
|
||||
/>
|
||||
<div className="flex flex-shrink-0 items-end pb-1.5 pr-1.5 gap-1">
|
||||
{!connected && (
|
||||
<span className="px-2 py-1 text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wide">
|
||||
<span className="px-2 py-1 text-[10px] font-medium text-muted-foreground uppercase tracking-wide">
|
||||
Disconnected
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -1403,7 +1403,7 @@ function ChatInputBar({
|
|||
"flex h-7 w-7 items-center justify-center rounded-lg transition-all",
|
||||
hasContent && connected
|
||||
? "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 active:scale-95"
|
||||
: "bg-muted text-muted-foreground/40 cursor-not-allowed",
|
||||
: "bg-muted text-muted-foreground cursor-not-allowed",
|
||||
)}
|
||||
>
|
||||
<SendHorizonal className="h-3.5 w-3.5" />
|
||||
|
|
@ -1476,7 +1476,7 @@ function ChatInputBar({
|
|||
{overflowGroups.map((group, gi) => (
|
||||
<div key={group.category}>
|
||||
{gi > 0 && <div className="my-1.5 border-t border-border/50" />}
|
||||
<p className="px-2 py-1 text-[10px] font-semibold text-muted-foreground/60 uppercase tracking-wider">
|
||||
<p className="px-2 py-1 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
{group.label}
|
||||
</p>
|
||||
{group.items.map((action) => {
|
||||
|
|
@ -1542,9 +1542,9 @@ function PlaceholderState({
|
|||
<div className="flex flex-1 flex-col items-center justify-center text-center py-16">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full border border-border bg-card">
|
||||
{showSpinner ? (
|
||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground/70" />
|
||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
) : (
|
||||
<MessagesSquare className="h-6 w-6 text-muted-foreground/50" />
|
||||
<MessagesSquare className="h-6 w-6 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-3 space-y-1">
|
||||
|
|
@ -1608,7 +1608,7 @@ function InlineUiRequest({ request }: { request: PendingUiRequest }) {
|
|||
<div className="mt-1 flex-shrink-0 flex h-7 w-7 items-center justify-center rounded-full bg-card border border-border">
|
||||
<PlatformLogoIcon className="h-3.5 w-auto" />
|
||||
</div>
|
||||
<div className="max-w-[82%] min-w-0 rounded-2xl rounded-tl-md border border-border/60 bg-card px-4 py-3 shadow-sm">
|
||||
<div className="max-w-[82%] min-w-0 rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm">
|
||||
{request.title && (
|
||||
<p className="mb-2.5 text-sm font-medium text-foreground">{request.title}</p>
|
||||
)}
|
||||
|
|
@ -1675,7 +1675,7 @@ function InlineSelect({
|
|||
disabled={disabled}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-sm transition-colors",
|
||||
checked ? "bg-primary/15 text-primary font-medium" : "text-foreground hover:bg-muted/60",
|
||||
checked ? "bg-primary/15 text-primary font-medium" : "text-foreground hover:bg-muted",
|
||||
)}
|
||||
>
|
||||
<span className="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-border">
|
||||
|
|
@ -1693,7 +1693,7 @@ function InlineSelect({
|
|||
disabled={disabled}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-sm transition-colors",
|
||||
selected ? "bg-primary/15 text-primary font-medium" : "text-foreground hover:bg-muted/60",
|
||||
selected ? "bg-primary/15 text-primary font-medium" : "text-foreground hover:bg-muted",
|
||||
)}
|
||||
>
|
||||
<span className="flex h-4 w-4 flex-shrink-0 items-center justify-center">
|
||||
|
|
@ -1714,7 +1714,7 @@ function InlineSelect({
|
|||
"mt-2 flex w-full items-center justify-center rounded-lg px-3 py-2 text-xs font-medium transition-all",
|
||||
canSubmit && !disabled
|
||||
? "bg-primary text-primary-foreground hover:bg-primary/90 active:scale-[0.98] shadow-sm"
|
||||
: "bg-muted text-muted-foreground/40 cursor-not-allowed",
|
||||
: "bg-muted text-muted-foreground cursor-not-allowed",
|
||||
)}
|
||||
>
|
||||
{isMulti ? `Submit (${multiValues.size})` : "Submit"}
|
||||
|
|
@ -1816,7 +1816,7 @@ function InlineInput({
|
|||
"flex h-8 items-center justify-center rounded-lg px-3 text-xs font-medium transition-all",
|
||||
value.trim() && !disabled
|
||||
? "bg-primary text-primary-foreground hover:bg-primary/90 active:scale-95 shadow-sm"
|
||||
: "bg-muted text-muted-foreground/40 cursor-not-allowed",
|
||||
: "bg-muted text-muted-foreground cursor-not-allowed",
|
||||
)}
|
||||
>
|
||||
Submit
|
||||
|
|
@ -1927,12 +1927,12 @@ function ToolExecutionBlock({ tool }: { tool: CompletedToolExecution }) {
|
|||
"w-full rounded-lg border px-3 py-2 text-left text-xs transition-colors",
|
||||
isError
|
||||
? "border-destructive/30 bg-destructive/5 hover:bg-destructive/10"
|
||||
: "border-border/40 bg-muted/20 hover:bg-muted/30",
|
||||
: "border-border/50 bg-muted/50 hover:bg-muted/50",
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn("flex-shrink-0", isError ? "text-destructive" : "text-muted-foreground/60")}>
|
||||
<span className={cn("flex-shrink-0", isError ? "text-destructive" : "text-muted-foreground")}>
|
||||
{icon}
|
||||
</span>
|
||||
<span className={cn("font-mono font-medium", isError ? "text-destructive" : "text-muted-foreground")}>
|
||||
|
|
@ -1942,16 +1942,16 @@ function ToolExecutionBlock({ tool }: { tool: CompletedToolExecution }) {
|
|||
<span className="truncate font-mono text-info/80">{shortPath}</span>
|
||||
)}
|
||||
{bashCommand && !shortPath && (
|
||||
<span className="truncate font-mono text-muted-foreground/70">{bashCommand.length > 60 ? bashCommand.slice(0, 60) + "…" : bashCommand}</span>
|
||||
<span className="truncate font-mono text-muted-foreground">{bashCommand.length > 60 ? bashCommand.slice(0, 60) + "…" : bashCommand}</span>
|
||||
)}
|
||||
<span className="ml-auto flex-shrink-0 text-muted-foreground/40">
|
||||
<span className="ml-auto flex-shrink-0 text-muted-foreground">
|
||||
{expanded ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Expanded content */}
|
||||
{expanded && diff && (
|
||||
<div className="mt-2 overflow-x-auto rounded-md border border-border/30 bg-background/80 p-2 font-mono text-[11px] leading-relaxed">
|
||||
<div className="mt-2 overflow-x-auto rounded-md border border-border/50 bg-background p-2 font-mono text-[11px] leading-relaxed">
|
||||
{diff.split("\n").map((line, i) => {
|
||||
const isAdd = line.startsWith("+")
|
||||
const isRemove = line.startsWith("-")
|
||||
|
|
@ -1963,8 +1963,8 @@ function ToolExecutionBlock({ tool }: { tool: CompletedToolExecution }) {
|
|||
"whitespace-pre",
|
||||
isAdd && "bg-success/10 text-success",
|
||||
isRemove && "bg-destructive/10 text-destructive",
|
||||
isContext && "text-muted-foreground/60",
|
||||
!isAdd && !isRemove && !isContext && "text-muted-foreground/40",
|
||||
isContext && "text-muted-foreground",
|
||||
!isAdd && !isRemove && !isContext && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{line}
|
||||
|
|
@ -1976,7 +1976,7 @@ function ToolExecutionBlock({ tool }: { tool: CompletedToolExecution }) {
|
|||
|
||||
{/* Expanded: bash output or other result */}
|
||||
{expanded && !diff && resultText && (
|
||||
<div className="mt-2 max-h-[200px] overflow-y-auto rounded-md border border-border/30 bg-background/80 p-2 font-mono text-[11px] leading-relaxed text-muted-foreground/70 whitespace-pre-wrap">
|
||||
<div className="mt-2 max-h-[200px] overflow-y-auto rounded-md border border-border/50 bg-background p-2 font-mono text-[11px] leading-relaxed text-muted-foreground whitespace-pre-wrap">
|
||||
{resultText.length > 2000 ? resultText.slice(0, 2000) + "\n…" : resultText}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -2291,8 +2291,8 @@ export function ChatPane({ className, onOpenAction }: ChatPaneProps) {
|
|||
<div key={`active-${item.tool.id}`} className="flex justify-start gap-3">
|
||||
<div className="w-7 flex-shrink-0" />
|
||||
<div className="max-w-[82%] min-w-0">
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border/40 bg-muted/20 px-3.5 py-2">
|
||||
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground/60" />
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border/50 bg-muted/50 px-3.5 py-2">
|
||||
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
|
||||
<span className="font-mono text-xs text-muted-foreground">
|
||||
{item.tool.name}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ const darkTheme = createTheme({
|
|||
selection: "oklch(0.2 0 0)",
|
||||
lineHighlight: "oklch(0.12 0 0)",
|
||||
gutterBackground: "oklch(0.09 0 0)",
|
||||
gutterForeground: "oklch(0.35 0 0)",
|
||||
gutterForeground: "oklch(0.42 0 0)",
|
||||
gutterBorder: "transparent",
|
||||
},
|
||||
styles: darkStyles,
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ function SectionHeader({
|
|||
return (
|
||||
<div className="flex items-center justify-between gap-3 pb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-foreground/70">{title}</h3>
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{title}</h3>
|
||||
{status}
|
||||
</div>
|
||||
{action}
|
||||
|
|
@ -290,7 +290,7 @@ function SegmentedControl<T extends string>({
|
|||
disabled?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className="inline-flex rounded-lg border border-border/60 bg-card/30 p-0.5">
|
||||
<div className="inline-flex rounded-lg border border-border bg-card/50 p-0.5">
|
||||
{options.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
|
|
@ -673,7 +673,7 @@ export function CommandSurface() {
|
|||
<div className="space-y-4">
|
||||
{Array.from(groupedModels.entries()).map(([provider, models]) => (
|
||||
<div key={provider}>
|
||||
<div className="mb-1.5 px-1 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/60">
|
||||
<div className="mb-1.5 px-1 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{provider}
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
|
|
@ -720,10 +720,10 @@ export function CommandSurface() {
|
|||
{/* Badges */}
|
||||
<div className="flex shrink-0 items-center gap-1.5">
|
||||
{model.isCurrent && (
|
||||
<span className="rounded bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium text-foreground/70">Active</span>
|
||||
<span className="rounded bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">Active</span>
|
||||
)}
|
||||
{model.reasoning && (
|
||||
<span className="rounded bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium text-foreground/70">Thinking</span>
|
||||
<span className="rounded bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">Thinking</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
|
@ -738,7 +738,7 @@ export function CommandSurface() {
|
|||
)}
|
||||
|
||||
{/* Apply */}
|
||||
<div className="flex justify-end border-t border-border/40 pt-3">
|
||||
<div className="flex justify-end border-t border-border/50 pt-3">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
|
|
@ -806,7 +806,7 @@ export function CommandSurface() {
|
|||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end border-t border-border/40 pt-3">
|
||||
<div className="flex justify-end border-t border-border/50 pt-3">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
|
|
@ -853,7 +853,7 @@ export function CommandSurface() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/30" />
|
||||
<div className="border-t border-border/50" />
|
||||
|
||||
{/* Follow-up mode */}
|
||||
<div className="space-y-3">
|
||||
|
|
@ -1029,7 +1029,7 @@ export function CommandSurface() {
|
|||
<LoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
||||
Loading diagnostics…
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 border-t border-border/30 pt-3" data-testid="command-surface-recovery-actions">
|
||||
<div className="flex flex-wrap gap-2 border-t border-border/50 pt-3" data-testid="command-surface-recovery-actions">
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
|
|
@ -1050,7 +1050,7 @@ export function CommandSurface() {
|
|||
<div className="text-sm font-medium text-foreground">{diag.summary.label}</div>
|
||||
<p className="text-xs text-muted-foreground">{diag.summary.detail}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 border-t border-border/30 pt-3" data-testid="command-surface-recovery-actions">
|
||||
<div className="flex flex-wrap gap-2 border-t border-border/50 pt-3" data-testid="command-surface-recovery-actions">
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
|
|
@ -1117,7 +1117,7 @@ export function CommandSurface() {
|
|||
<Badge variant={issue.severity === "error" ? "destructive" : "outline"} className="text-[10px]">{issue.code}</Badge>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">{issue.message}</p>
|
||||
{issue.suggestion && <p className="mt-0.5 text-[11px] text-muted-foreground/70">→ {issue.suggestion}</p>}
|
||||
{issue.suggestion && <p className="mt-0.5 text-[11px] text-muted-foreground">→ {issue.suggestion}</p>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -1156,7 +1156,7 @@ export function CommandSurface() {
|
|||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex flex-wrap gap-2 border-t border-border/30 pt-3" data-testid="command-surface-recovery-actions">
|
||||
<div className="flex flex-wrap gap-2 border-t border-border/50 pt-3" data-testid="command-surface-recovery-actions">
|
||||
{diag.actions.browser.length > 0 ? (
|
||||
diag.actions.browser.map((action) => (
|
||||
<Button
|
||||
|
|
@ -1179,7 +1179,7 @@ export function CommandSurface() {
|
|||
</div>
|
||||
|
||||
{diag.actions.commands.length > 0 && (
|
||||
<div className="space-y-2 border-t border-border/30 pt-3" data-testid="command-surface-recovery-commands">
|
||||
<div className="space-y-2 border-t border-border/50 pt-3" data-testid="command-surface-recovery-commands">
|
||||
<div className="text-xs font-medium text-muted-foreground">Suggested commands</div>
|
||||
{diag.actions.commands.map((command) => (
|
||||
<div key={command.command} className="rounded-lg border border-border/50 bg-card/50 px-3 py-2 text-xs">
|
||||
|
|
@ -1259,7 +1259,7 @@ export function CommandSurface() {
|
|||
<span className="font-mono">{shortenPath(result.project.repoRoot, 3)}</span>
|
||||
{result.project.repoRelativePath && (
|
||||
<>
|
||||
<ChevronRight className="h-3 w-3 text-foreground/20" />
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="font-mono">{result.project.repoRelativePath}</span>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -1275,15 +1275,15 @@ export function CommandSurface() {
|
|||
].map(({ label, count, active, color }) => (
|
||||
<div key={label} className={cn(
|
||||
"rounded-md border px-2 py-2 text-center transition-colors",
|
||||
active ? "border-border/60 bg-card/80" : "border-border/30 bg-card/30",
|
||||
active ? "border-border bg-card" : "border-border/50 bg-card/50",
|
||||
)}>
|
||||
<div className={cn(
|
||||
"text-base font-semibold tabular-nums leading-none",
|
||||
active ? color : "text-foreground/25",
|
||||
active ? color : "text-muted-foreground",
|
||||
)}>{count}</div>
|
||||
<div className={cn(
|
||||
"mt-1.5 text-[10px] leading-none",
|
||||
active ? "text-muted-foreground" : "text-muted-foreground/50",
|
||||
active ? "text-muted-foreground" : "text-muted-foreground",
|
||||
)}>{label}</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -1293,14 +1293,14 @@ export function CommandSurface() {
|
|||
{result.changedFiles.length > 0 && (
|
||||
<div data-testid="command-surface-git-files">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<span className="text-[11px] font-medium uppercase tracking-[0.06em] text-muted-foreground/70">
|
||||
<span className="text-[11px] font-medium uppercase tracking-[0.06em] text-muted-foreground">
|
||||
Changes
|
||||
</span>
|
||||
<span className="text-[11px] tabular-nums text-muted-foreground/50">
|
||||
<span className="text-[11px] tabular-nums text-muted-foreground">
|
||||
{result.changedFiles.length}{result.truncatedFileCount > 0 ? `+${result.truncatedFileCount}` : ""} files
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-px rounded-lg border border-border/40 bg-card/30 overflow-hidden">
|
||||
<div className="space-y-px rounded-lg border border-border/50 bg-card/50 overflow-hidden">
|
||||
{result.changedFiles.map((file) => (
|
||||
<div
|
||||
key={`${file.status}:${file.repoPath}`}
|
||||
|
|
@ -1324,7 +1324,7 @@ export function CommandSurface() {
|
|||
))}
|
||||
</div>
|
||||
{result.truncatedFileCount > 0 && (
|
||||
<p className="mt-1.5 text-center text-[11px] text-muted-foreground/50">
|
||||
<p className="mt-1.5 text-center text-[11px] text-muted-foreground">
|
||||
+{result.truncatedFileCount} more files not shown
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -1392,7 +1392,7 @@ export function CommandSurface() {
|
|||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"rounded-md border border-border/60 px-2.5 py-1.5 text-[11px] font-medium transition-colors",
|
||||
"rounded-md border border-border px-2.5 py-1.5 text-[11px] font-medium transition-colors",
|
||||
sessionBrowser.nameFilter === "named" ? "bg-foreground/10 text-foreground" : "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
onClick={() => {
|
||||
|
|
@ -1452,7 +1452,7 @@ export function CommandSurface() {
|
|||
{session.name && session.firstMessage && (
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">{session.firstMessage}</p>
|
||||
)}
|
||||
<div className="mt-0.5 flex gap-3 text-[11px] text-muted-foreground/70">
|
||||
<div className="mt-0.5 flex gap-3 text-[11px] text-muted-foreground">
|
||||
<span>{session.messageCount} msgs</span>
|
||||
<span>{formatRelativeTime(session.modifiedAt)}</span>
|
||||
</div>
|
||||
|
|
@ -1473,7 +1473,7 @@ export function CommandSurface() {
|
|||
|
||||
{/* Rename controls */}
|
||||
{renameMode && (
|
||||
<div className="space-y-3 border-t border-border/30 pt-3">
|
||||
<div className="space-y-3 border-t border-border/50 pt-3">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={selectedNameTarget?.name ?? ""}
|
||||
|
|
@ -1504,7 +1504,7 @@ export function CommandSurface() {
|
|||
|
||||
{/* Resume controls */}
|
||||
{!renameMode && (
|
||||
<div className="flex items-center justify-between border-t border-border/30 pt-3">
|
||||
<div className="flex items-center justify-between border-t border-border/50 pt-3">
|
||||
<span className="text-xs text-muted-foreground" data-testid="command-surface-resume-state">
|
||||
{resumeBusy ? "Switching…" : commandSurface.resumeRequest.error ?? commandSurface.resumeRequest.result ?? "Select a session"}
|
||||
</span>
|
||||
|
|
@ -1563,7 +1563,7 @@ export function CommandSurface() {
|
|||
{selected && <Check className="h-2.5 w-2.5 text-background" />}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="font-mono text-[10px] text-muted-foreground/60">{message.entryId}</div>
|
||||
<div className="font-mono text-[10px] text-muted-foreground">{message.entryId}</div>
|
||||
<p className="mt-0.5 text-sm text-foreground">{message.text}</p>
|
||||
</div>
|
||||
</button>
|
||||
|
|
@ -1574,7 +1574,7 @@ export function CommandSurface() {
|
|||
<p className="py-4 text-center text-xs text-muted-foreground">No fork points available yet.</p>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end border-t border-border/40 pt-3">
|
||||
<div className="flex justify-end border-t border-border/50 pt-3">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
|
|
@ -1647,7 +1647,7 @@ export function CommandSurface() {
|
|||
)}
|
||||
|
||||
{/* Export */}
|
||||
<div className="space-y-3 border-t border-border/30 pt-3">
|
||||
<div className="space-y-3 border-t border-border/50 pt-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">Export</div>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
|
|
@ -1779,7 +1779,7 @@ export function CommandSurface() {
|
|||
</span>
|
||||
</div>
|
||||
{provider.recommended && (
|
||||
<span className="rounded bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium text-foreground/70">Recommended</span>
|
||||
<span className="rounded bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">Recommended</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
|
@ -1788,7 +1788,7 @@ export function CommandSurface() {
|
|||
|
||||
{/* Selected provider details */}
|
||||
{selectedAuthProvider && (
|
||||
<div className="space-y-4 border-t border-border/30 pt-3">
|
||||
<div className="space-y-4 border-t border-border/50 pt-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-foreground">{selectedAuthProvider.label}</div>
|
||||
|
|
@ -1899,7 +1899,7 @@ export function CommandSurface() {
|
|||
{activeFlow.progress.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
{activeFlow.progress.map((message, index) => (
|
||||
<div key={`${activeFlow.flowId}-${index}`} className="rounded-md border border-border/40 bg-card/30 px-2.5 py-1.5 text-xs text-muted-foreground">
|
||||
<div key={`${activeFlow.flowId}-${index}`} className="rounded-md border border-border/50 bg-card/50 px-2.5 py-1.5 text-xs text-muted-foreground">
|
||||
{message}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -1987,7 +1987,7 @@ export function CommandSurface() {
|
|||
|
||||
{/* Individual overrides — only visible when master is on */}
|
||||
{devOverrides.enabled && (
|
||||
<div className="space-y-2 rounded-lg border border-border/50 bg-card/30 p-3">
|
||||
<div className="space-y-2 rounded-lg border border-border/50 bg-card/50 p-3">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Override shortcuts
|
||||
</div>
|
||||
|
|
@ -1999,7 +1999,7 @@ export function CommandSurface() {
|
|||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground">{entry.label}</span>
|
||||
<Badge variant="outline" className="border-border/60 font-mono text-[10px] text-muted-foreground">
|
||||
<Badge variant="outline" className="border-border font-mono text-[10px] text-muted-foreground">
|
||||
{entry.shortcutLabel}
|
||||
</Badge>
|
||||
</div>
|
||||
|
|
@ -2016,7 +2016,7 @@ export function CommandSurface() {
|
|||
)}
|
||||
|
||||
{/* Onboarding — one-click launch */}
|
||||
<div className="rounded-lg border border-border/50 bg-card/30 p-3 space-y-3">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 p-3 space-y-3">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Onboarding
|
||||
</div>
|
||||
|
|
@ -2046,7 +2046,7 @@ export function CommandSurface() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-border/40 bg-card/30 px-3 py-2.5 text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 text-xs text-muted-foreground">
|
||||
This tab is only visible when running via{" "}
|
||||
<code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">npm run gsd:web</code>.
|
||||
Overrides reset on page refresh.
|
||||
|
|
@ -2061,7 +2061,7 @@ export function CommandSurface() {
|
|||
case "model": return (
|
||||
<div className="space-y-8">
|
||||
{renderModelSection()}
|
||||
<div className="border-t border-border/30 pt-6">
|
||||
<div className="border-t border-border/50 pt-6">
|
||||
{renderThinkingSection()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2069,7 +2069,7 @@ export function CommandSurface() {
|
|||
case "thinking": return (
|
||||
<div className="space-y-8">
|
||||
{renderModelSection()}
|
||||
<div className="border-t border-border/30 pt-6">
|
||||
<div className="border-t border-border/50 pt-6">
|
||||
{renderThinkingSection()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2077,10 +2077,10 @@ export function CommandSurface() {
|
|||
case "session-behavior": return (
|
||||
<div className="space-y-6">
|
||||
{renderQueueSection()}
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderCompactionSection()}
|
||||
</div>
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderRetrySection()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2089,10 +2089,10 @@ export function CommandSurface() {
|
|||
case "queue": return (
|
||||
<div className="space-y-6">
|
||||
{renderQueueSection()}
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderCompactionSection()}
|
||||
</div>
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderRetrySection()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2100,10 +2100,10 @@ export function CommandSurface() {
|
|||
case "compaction": return (
|
||||
<div className="space-y-6">
|
||||
{renderQueueSection()}
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderCompactionSection()}
|
||||
</div>
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderRetrySection()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2111,10 +2111,10 @@ export function CommandSurface() {
|
|||
case "retry": return (
|
||||
<div className="space-y-6">
|
||||
{renderQueueSection()}
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderCompactionSection()}
|
||||
</div>
|
||||
<div className="border-t border-border/30 pt-4">
|
||||
<div className="border-t border-border/50 pt-4">
|
||||
{renderRetrySection()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2188,7 +2188,7 @@ export function CommandSurface() {
|
|||
const isClean = gitResult?.kind === "repo" && !hasChanges
|
||||
|
||||
return (
|
||||
<div className="border-b border-border/40 px-5 py-4">
|
||||
<div className="border-b border-border/50 px-5 py-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={cn(
|
||||
|
|
@ -2206,7 +2206,7 @@ export function CommandSurface() {
|
|||
{branchName ?? "Git"}
|
||||
</h2>
|
||||
{branchName && mainBranch && branchName !== mainBranch && (
|
||||
<span className="text-[11px] text-muted-foreground/50">from {mainBranch}</span>
|
||||
<span className="text-[11px] text-muted-foreground">from {mainBranch}</span>
|
||||
)}
|
||||
</div>
|
||||
{gitResult?.kind === "repo" && (
|
||||
|
|
@ -2248,7 +2248,7 @@ export function CommandSurface() {
|
|||
}
|
||||
|
||||
const renderDefaultHeader = () => (
|
||||
<div className="flex items-center justify-between gap-3 border-b border-border/40 px-5 py-4">
|
||||
<div className="flex items-center justify-between gap-3 border-b border-border/50 px-5 py-4">
|
||||
<div>
|
||||
<div className="text-xs uppercase tracking-wider text-muted-foreground">Command surface</div>
|
||||
<div className="text-lg font-semibold text-foreground" data-testid="command-surface-title">
|
||||
|
|
@ -2285,7 +2285,7 @@ export function CommandSurface() {
|
|||
<div className="flex h-full min-h-0">
|
||||
{/* ─── Left nav rail (hidden for single-section surfaces) ─── */}
|
||||
{!isSingleSection && (
|
||||
<nav className="flex w-12 shrink-0 flex-col items-center gap-0.5 border-r border-border/40 bg-card/30 py-3" data-testid="command-surface-sections">
|
||||
<nav className="flex w-12 shrink-0 flex-col items-center gap-0.5 border-r border-border/50 bg-card/50 py-3" data-testid="command-surface-sections">
|
||||
{surfaceSections.map((section) => {
|
||||
const active = commandSurface.section === section
|
||||
return (
|
||||
|
|
@ -2320,7 +2320,7 @@ export function CommandSurface() {
|
|||
{(commandSurface.lastResult || commandSurface.lastError) && (
|
||||
<div
|
||||
className={cn(
|
||||
"border-b border-border/30 px-5 py-3 text-xs",
|
||||
"border-b border-border/50 px-5 py-3 text-xs",
|
||||
commandSurface.lastError ? "bg-destructive/5 text-destructive" : "bg-success/5 text-success",
|
||||
)}
|
||||
data-testid="command-surface-result"
|
||||
|
|
|
|||
|
|
@ -82,11 +82,11 @@ function MetricCard({ label, value, subtext, icon }: MetricCardProps) {
|
|||
function taskStatusIcon(status: ItemStatus) {
|
||||
switch (status) {
|
||||
case "done":
|
||||
return <CheckCircle2 className="h-4 w-4 text-foreground/70" />
|
||||
return <CheckCircle2 className="h-4 w-4 text-muted-foreground" />
|
||||
case "in-progress":
|
||||
return <Play className="h-4 w-4 text-foreground" />
|
||||
case "pending":
|
||||
return <Circle className="h-4 w-4 text-muted-foreground/50" />
|
||||
return <Circle className="h-4 w-4 text-muted-foreground" />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ export function Dashboard({ onSwitchView, onExpandTerminal }: DashboardProps = {
|
|||
<h1 className="text-base md:text-lg font-semibold shrink-0">Dashboard</h1>
|
||||
{!isConnecting && scopeLabel && (
|
||||
<>
|
||||
<span className="hidden sm:inline text-lg font-thin text-muted-foreground/40 select-none">/</span>
|
||||
<span className="hidden sm:inline text-lg font-thin text-muted-foreground select-none">/</span>
|
||||
<span className="hidden sm:inline"><ScopeBadge label={scopeLabel} size="sm" /></span>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -351,7 +351,7 @@ export function Dashboard({ onSwitchView, onExpandTerminal }: DashboardProps = {
|
|||
{task.title}
|
||||
</span>
|
||||
{status === "in-progress" && (
|
||||
<span className="shrink-0 rounded-sm bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-foreground/70">
|
||||
<span className="shrink-0 rounded-sm bg-foreground/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||
active
|
||||
</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function DiagHeader({
|
|||
return (
|
||||
<div className="flex items-center justify-between gap-3 pb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-foreground/70">{title}</h3>
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{title}</h3>
|
||||
{status}
|
||||
{subtitle && <span className="text-[11px] text-muted-foreground">{subtitle}</span>}
|
||||
</div>
|
||||
|
|
@ -89,7 +89,7 @@ function DiagLoading({ label }: { label: string }) {
|
|||
|
||||
function DiagEmpty({ message }: { message: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
{message}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -102,7 +102,7 @@ function StatPill({ label, value, variant }: { label: string; value: number | st
|
|||
variant === "error" && "border-destructive/20 bg-destructive/5 text-destructive",
|
||||
variant === "warning" && "border-warning/20 bg-warning/5 text-warning",
|
||||
variant === "info" && "border-info/20 bg-info/5 text-info",
|
||||
(!variant || variant === "default") && "border-border/40 bg-card/50 text-foreground/80",
|
||||
(!variant || variant === "default") && "border-border/50 bg-card/50 text-foreground/80",
|
||||
)}>
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<span className="font-medium tabular-nums">{value}</span>
|
||||
|
|
@ -116,7 +116,7 @@ function StatPill({ label, value, variant }: { label: string; value: number | st
|
|||
|
||||
function AnomalyRow({ anomaly }: { anomaly: ForensicAnomaly }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 space-y-1">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<SeverityIcon severity={anomaly.severity} />
|
||||
<Badge variant={severityBadgeVariant(anomaly.severity)} className="text-[10px] px-1.5 py-0">{anomaly.severity}</Badge>
|
||||
|
|
@ -125,7 +125,7 @@ function AnomalyRow({ anomaly }: { anomaly: ForensicAnomaly }) {
|
|||
<span className="text-[10px] text-muted-foreground font-mono truncate">{anomaly.unitType}/{anomaly.unitId}</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-foreground/90">{anomaly.summary}</p>
|
||||
<p className="text-xs text-foreground">{anomaly.summary}</p>
|
||||
{anomaly.details && anomaly.details !== anomaly.summary && (
|
||||
<p className="text-[11px] text-muted-foreground leading-relaxed">{anomaly.details}</p>
|
||||
)}
|
||||
|
|
@ -187,7 +187,7 @@ export function ForensicsPanel() {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border/30 bg-card/30 px-3 py-2 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border/50 bg-card/50 px-3 py-2 text-xs text-muted-foreground">
|
||||
<CheckCircle2 className="h-3.5 w-3.5 text-success" />
|
||||
No crash lock
|
||||
</div>
|
||||
|
|
@ -196,7 +196,7 @@ export function ForensicsPanel() {
|
|||
{/* Anomalies */}
|
||||
{data.anomalies.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Anomalies ({data.anomalies.length})</h4>
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Anomalies ({data.anomalies.length})</h4>
|
||||
{data.anomalies.map((a, i) => <AnomalyRow key={i} anomaly={a} />)}
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -206,11 +206,11 @@ export function ForensicsPanel() {
|
|||
{/* Recent units */}
|
||||
{data.recentUnits.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Recent Units ({data.recentUnits.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Recent Units ({data.recentUnits.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Type</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">ID</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Model</th>
|
||||
|
|
@ -220,7 +220,7 @@ export function ForensicsPanel() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{data.recentUnits.map((u, i) => (
|
||||
<tr key={i} className="border-b border-border/20 last:border-0">
|
||||
<tr key={i} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80">{u.type}</td>
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80 truncate max-w-[120px]">{u.id}</td>
|
||||
<td className="px-2.5 py-1.5 text-muted-foreground">{u.model}</td>
|
||||
|
|
@ -249,7 +249,7 @@ function humanizeCode(code: string): string {
|
|||
|
||||
function IssueRow({ issue }: { issue: DoctorIssue }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 space-y-1">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 space-y-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<SeverityIcon severity={issue.severity} />
|
||||
<Badge variant={severityBadgeVariant(issue.severity)} className="text-[10px] px-1.5 py-0">{issue.severity}</Badge>
|
||||
|
|
@ -261,7 +261,7 @@ function IssueRow({ issue }: { issue: DoctorIssue }) {
|
|||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-foreground/90">{issue.message}</p>
|
||||
<p className="text-xs text-foreground">{issue.message}</p>
|
||||
{issue.file && <p className="text-[10px] font-mono text-muted-foreground truncate">{issue.file}</p>}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -349,7 +349,7 @@ export function DoctorPanel() {
|
|||
{/* Issue list */}
|
||||
{data.issues.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Issues ({data.issues.length})</h4>
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Issues ({data.issues.length})</h4>
|
||||
{data.issues.map((issue, i) => <IssueRow key={i} issue={issue} />)}
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -379,14 +379,14 @@ function trendColor(trend: "stable" | "rising" | "declining"): string {
|
|||
|
||||
function SuggestionRow({ suggestion }: { suggestion: SkillHealSuggestion }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 space-y-1">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 space-y-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<SeverityIcon severity={suggestion.severity} />
|
||||
<Badge variant={severityBadgeVariant(suggestion.severity)} className="text-[10px] px-1.5 py-0">{suggestion.severity}</Badge>
|
||||
<span className="text-[11px] font-medium text-foreground/80">{suggestion.skillName}</span>
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0 font-mono">{suggestion.trigger.replace(/_/g, " ")}</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-foreground/90">{suggestion.message}</p>
|
||||
<p className="text-xs text-foreground">{suggestion.message}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -429,11 +429,11 @@ export function SkillHealthPanel() {
|
|||
{/* Skill table */}
|
||||
{data.skills.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Skills ({data.skills.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Skills ({data.skills.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Skill</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Uses</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Success</th>
|
||||
|
|
@ -446,7 +446,7 @@ export function SkillHealthPanel() {
|
|||
<tbody>
|
||||
{data.skills.map((skill) => (
|
||||
<tr key={skill.name} className={cn(
|
||||
"border-b border-border/20 last:border-0",
|
||||
"border-b border-border/50 last:border-0",
|
||||
skill.flagged && "bg-destructive/3",
|
||||
)}>
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80">
|
||||
|
|
@ -484,7 +484,7 @@ export function SkillHealthPanel() {
|
|||
{/* Stale skills */}
|
||||
{data.staleSkills.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Stale Skills</h4>
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Stale Skills</h4>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{data.staleSkills.map((name) => (
|
||||
<Badge key={name} variant="secondary" className="text-[10px] font-mono">{name}</Badge>
|
||||
|
|
@ -496,7 +496,7 @@ export function SkillHealthPanel() {
|
|||
{/* Declining skills */}
|
||||
{data.decliningSkills.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Declining Skills</h4>
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Declining Skills</h4>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{data.decliningSkills.map((name) => (
|
||||
<Badge key={name} variant="destructive" className="text-[10px] font-mono">{name}</Badge>
|
||||
|
|
@ -508,7 +508,7 @@ export function SkillHealthPanel() {
|
|||
{/* Suggestions */}
|
||||
{data.suggestions.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Suggestions ({data.suggestions.length})</h4>
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Suggestions ({data.suggestions.length})</h4>
|
||||
{data.suggestions.map((s, i) => <SuggestionRow key={i} suggestion={s} />)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ function PlainViewer({ content }: { content: string }) {
|
|||
{lines.map((line, i) => (
|
||||
<tr key={i} className="hover:bg-accent/20">
|
||||
<td
|
||||
className="select-none pr-4 text-right text-muted-foreground/40 align-top"
|
||||
className="select-none pr-4 text-right text-muted-foreground align-top"
|
||||
style={{ minWidth: `${gutterWidth + 1}ch` }}
|
||||
>
|
||||
{i + 1}
|
||||
|
|
@ -313,7 +313,7 @@ function MarkdownViewer({ content, filepath, shikiTheme = "github-dark-default"
|
|||
},
|
||||
img({ src, alt }) {
|
||||
return (
|
||||
<span className="my-2 block rounded border border-border bg-muted/20 px-3 py-2 text-xs text-muted-foreground italic">
|
||||
<span className="my-2 block rounded border border-border bg-muted/50 px-3 py-2 text-xs text-muted-foreground italic">
|
||||
🖼 {alt || (typeof src === "string" ? src : "") || "image"}
|
||||
</span>
|
||||
)
|
||||
|
|
@ -485,7 +485,7 @@ function InlineDiffViewer({ before, after, onDismiss }: { before: string; after:
|
|||
"select-none pr-3 text-right align-top min-w-[3ch]",
|
||||
line.type === "add" ? "text-emerald-400/40" :
|
||||
line.type === "remove" ? "text-red-400/40" :
|
||||
"text-muted-foreground/30",
|
||||
"text-muted-foreground/50",
|
||||
)}
|
||||
>
|
||||
{line.lineNum ?? ""}
|
||||
|
|
@ -495,8 +495,8 @@ function InlineDiffViewer({ before, after, onDismiss }: { before: string; after:
|
|||
"whitespace-pre pr-4",
|
||||
line.type === "add" && "text-emerald-300",
|
||||
line.type === "remove" && "text-red-300 line-through decoration-red-400/30",
|
||||
line.type === "context" && line.text === "···" && "text-muted-foreground/30 text-center italic",
|
||||
line.type === "context" && line.text !== "···" && "text-muted-foreground/70",
|
||||
line.type === "context" && line.text === "···" && "text-muted-foreground/50 text-center italic",
|
||||
line.type === "context" && line.text !== "···" && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{line.text || " "}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ function SelectRenderer({
|
|||
{request.options.map((option) => (
|
||||
<label
|
||||
key={option}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border border-border/70 bg-background/70 px-3 py-2.5 transition-colors hover:bg-accent/40"
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5 transition-colors hover:bg-accent/40"
|
||||
>
|
||||
<Checkbox
|
||||
checked={multiValues.has(option)}
|
||||
|
|
@ -115,7 +115,7 @@ function SelectRenderer({
|
|||
{request.options.map((option) => (
|
||||
<label
|
||||
key={option}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border border-border/70 bg-background/70 px-3 py-2.5 transition-colors hover:bg-accent/40"
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5 transition-colors hover:bg-accent/40"
|
||||
>
|
||||
<RadioGroupItem value={option} id={`select-${option}`} />
|
||||
<Label htmlFor={`select-${option}`} className="cursor-pointer text-sm font-normal">
|
||||
|
|
@ -145,7 +145,7 @@ function ConfirmRenderer({
|
|||
}) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border border-border/70 bg-background/70 px-4 py-3 text-sm leading-relaxed">
|
||||
<div className="rounded-lg border border-border bg-background px-4 py-3 text-sm leading-relaxed">
|
||||
{request.message}
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function PanelHeader({
|
|||
return (
|
||||
<div className="flex items-center justify-between gap-3 pb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-foreground/70">{title}</h3>
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{title}</h3>
|
||||
{status}
|
||||
{subtitle && <span className="text-[11px] text-muted-foreground">{subtitle}</span>}
|
||||
</div>
|
||||
|
|
@ -84,7 +84,7 @@ function PanelLoading({ label }: { label: string }) {
|
|||
|
||||
function PanelEmpty({ message }: { message: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
{message}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -97,7 +97,7 @@ function StatPill({ label, value, variant }: { label: string; value: number | st
|
|||
variant === "error" && "border-destructive/20 bg-destructive/5 text-destructive",
|
||||
variant === "warning" && "border-warning/20 bg-warning/5 text-warning",
|
||||
variant === "info" && "border-info/20 bg-info/5 text-info",
|
||||
(!variant || variant === "default") && "border-border/40 bg-card/50 text-foreground/80",
|
||||
(!variant || variant === "default") && "border-border/50 bg-card/50 text-foreground/80",
|
||||
)}>
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<span className="font-medium tabular-nums">{value}</span>
|
||||
|
|
@ -181,12 +181,12 @@ const CLASSIFICATION_OPTIONS: Classification[] = ["quick-task", "inject", "defer
|
|||
function KnowledgeEntryRow({ entry }: { entry: KnowledgeEntry }) {
|
||||
const badge = knowledgeTypeBadge(entry.type)
|
||||
return (
|
||||
<div className="group rounded-lg border border-border/30 bg-card/20 px-3 py-2.5 transition-colors hover:bg-card/40">
|
||||
<div className="group rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 transition-colors hover:bg-card/50">
|
||||
<div className="flex items-start gap-2.5">
|
||||
<KnowledgeTypeIcon type={entry.type} className="mt-0.5" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-medium text-foreground/90 truncate">{entry.title}</span>
|
||||
<span className="text-xs font-medium text-foreground truncate">{entry.title}</span>
|
||||
<Badge variant="outline" className={cn("text-[10px] px-1.5 py-0 h-4 shrink-0", badge.className)}>
|
||||
{badge.label}
|
||||
</Badge>
|
||||
|
|
@ -231,7 +231,7 @@ function KnowledgeTabContent({
|
|||
))}
|
||||
</div>
|
||||
{data.lastModified && (
|
||||
<p className="pt-2 text-[10px] text-muted-foreground/60">
|
||||
<p className="pt-2 text-[10px] text-muted-foreground">
|
||||
Last modified: {new Date(data.lastModified).toLocaleString()}
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -255,7 +255,7 @@ function CaptureEntryRow({
|
|||
const status = captureStatusStyle(entry.status)
|
||||
|
||||
return (
|
||||
<div className="group rounded-lg border border-border/30 bg-card/20 px-3 py-2.5 transition-colors hover:bg-card/40">
|
||||
<div className="group rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 transition-colors hover:bg-card/50">
|
||||
<div className="flex items-start gap-2.5">
|
||||
<div className={cn(
|
||||
"mt-1 h-2 w-2 shrink-0 rounded-full",
|
||||
|
|
@ -265,24 +265,24 @@ function CaptureEntryRow({
|
|||
)} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-xs text-foreground/90">{entry.text}</span>
|
||||
<span className="text-xs text-foreground">{entry.text}</span>
|
||||
<Badge variant="outline" className={cn("text-[10px] px-1.5 py-0 h-4 shrink-0", status.className)}>
|
||||
{status.label}
|
||||
</Badge>
|
||||
{entry.classification && (
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0 h-4 shrink-0 border-border/40 text-muted-foreground">
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0 h-4 shrink-0 border-border/50 text-muted-foreground">
|
||||
{classificationLabel(entry.classification)}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{entry.timestamp && (
|
||||
<div className="mt-1 flex items-center gap-1 text-[10px] text-muted-foreground/60">
|
||||
<div className="mt-1 flex items-center gap-1 text-[10px] text-muted-foreground">
|
||||
<Clock className="h-2.5 w-2.5" />
|
||||
{entry.timestamp}
|
||||
</div>
|
||||
)}
|
||||
{entry.resolution && (
|
||||
<p className="mt-1 text-[10px] text-muted-foreground/70 italic">{entry.resolution}</p>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground italic">{entry.resolution}</p>
|
||||
)}
|
||||
{entry.status === "pending" && (
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
|
|
@ -294,7 +294,7 @@ function CaptureEntryRow({
|
|||
size="sm"
|
||||
disabled={resolvePending}
|
||||
onClick={() => onResolve(entry.id, c)}
|
||||
className="h-6 gap-1 px-2 text-[10px] font-normal border-border/40 hover:bg-foreground/5"
|
||||
className="h-6 gap-1 px-2 text-[10px] font-normal border-border/50 hover:bg-foreground/5"
|
||||
>
|
||||
<ClassificationIcon classification={c} />
|
||||
{classificationLabel(c)}
|
||||
|
|
@ -397,7 +397,7 @@ export function KnowledgeCapturesPanel({ initialTab }: KnowledgeCapturesPanelPro
|
|||
return (
|
||||
<div className="space-y-0">
|
||||
{/* Tab bar */}
|
||||
<div className="flex items-center gap-0.5 border-b border-border/30 px-1">
|
||||
<div className="flex items-center gap-0.5 border-b border-border/50 px-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("knowledge")}
|
||||
|
|
@ -405,7 +405,7 @@ export function KnowledgeCapturesPanel({ initialTab }: KnowledgeCapturesPanelPro
|
|||
"flex items-center gap-1.5 px-3 py-2 text-xs font-medium transition-all border-b-2 -mb-px",
|
||||
activeTab === "knowledge"
|
||||
? "border-foreground/60 text-foreground"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground/70",
|
||||
: "border-transparent text-muted-foreground hover:text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
<BookOpen className="h-3.5 w-3.5" />
|
||||
|
|
@ -418,7 +418,7 @@ export function KnowledgeCapturesPanel({ initialTab }: KnowledgeCapturesPanelPro
|
|||
"flex items-center gap-1.5 px-3 py-2 text-xs font-medium transition-all border-b-2 -mb-px",
|
||||
activeTab === "captures"
|
||||
? "border-foreground/60 text-foreground"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground/70",
|
||||
: "border-transparent text-muted-foreground hover:text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
<InboxIcon className="h-3.5 w-3.5" />
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ export function MainSessionTerminal({ className, fontSize, projectCwd }: MainSes
|
|||
)}
|
||||
{/* Drop overlay */}
|
||||
{isDragOver && (
|
||||
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-background/80 backdrop-blur-sm border-2 border-dashed border-primary rounded-md pointer-events-none">
|
||||
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-background backdrop-blur-sm border-2 border-dashed border-primary rounded-md pointer-events-none">
|
||||
<ImagePlus className="h-8 w-8 text-primary" />
|
||||
<span className="text-sm font-medium text-primary">Drop image here</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export function OnboardingGate() {
|
|||
|
||||
{/* Right — step label */}
|
||||
<div className="flex w-24 justify-end">
|
||||
<span className="text-xs text-muted-foreground/60">{stepLabel}</span>
|
||||
<span className="text-xs text-muted-foreground">{stepLabel}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ export function StepAuthenticate({
|
|||
|
||||
{/* ─── API key form ─── */}
|
||||
{hasApiKey && !canProceed && (
|
||||
<div className="space-y-3 rounded-xl border border-border/40 bg-card/30 p-4">
|
||||
<div className="space-y-3 rounded-xl border border-border/50 bg-card/50 p-4">
|
||||
<div className="text-sm font-medium text-foreground">API key</div>
|
||||
<form
|
||||
className="space-y-3"
|
||||
|
|
@ -276,15 +276,15 @@ export function StepAuthenticate({
|
|||
{/* Divider between API key and OAuth */}
|
||||
{hasApiKey && (
|
||||
<div className="flex items-center gap-3 py-1">
|
||||
<div className="h-px flex-1 bg-border/40" />
|
||||
<span className="text-xs text-muted-foreground/50">or</span>
|
||||
<div className="h-px flex-1 bg-border/40" />
|
||||
<div className="h-px flex-1 bg-border/50" />
|
||||
<span className="text-xs text-muted-foreground">or</span>
|
||||
<div className="h-px flex-1 bg-border/50" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─── No active flow: show start button ─── */}
|
||||
{!flowActive && (
|
||||
<div className="rounded-xl border border-border/40 bg-card/30 p-4">
|
||||
<div className="rounded-xl border border-border/50 bg-card/50 p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-foreground">Browser sign-in</div>
|
||||
|
|
@ -316,7 +316,7 @@ export function StepAuthenticate({
|
|||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="rounded-xl border border-border/40 bg-card/30 p-4 space-y-4"
|
||||
className="rounded-xl border border-border/50 bg-card/50 p-4 space-y-4"
|
||||
data-testid="onboarding-active-flow"
|
||||
>
|
||||
{/* Device code — big and prominent */}
|
||||
|
|
@ -326,12 +326,12 @@ export function StepAuthenticate({
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => copyCode(deviceCode)}
|
||||
className="group flex items-center gap-3 rounded-lg border border-border/60 bg-background/50 px-5 py-3 transition-colors hover:border-foreground/20 active:scale-[0.98]"
|
||||
className="group flex items-center gap-3 rounded-lg border border-border bg-background/50 px-5 py-3 transition-colors hover:border-foreground/20 active:scale-[0.98]"
|
||||
>
|
||||
<span className="font-mono text-2xl font-bold tracking-[0.15em] text-foreground">
|
||||
{deviceCode}
|
||||
</span>
|
||||
<span className="text-muted-foreground/40 transition-colors group-hover:text-muted-foreground">
|
||||
<span className="text-muted-foreground transition-colors group-hover:text-muted-foreground">
|
||||
{copied ? (
|
||||
<CheckCircle2 className="h-4 w-4 text-success" />
|
||||
) : (
|
||||
|
|
@ -339,7 +339,7 @@ export function StepAuthenticate({
|
|||
)}
|
||||
</span>
|
||||
</button>
|
||||
<div className="text-[11px] text-muted-foreground/50">
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
{copied ? "Copied!" : "Click to copy"}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -402,7 +402,7 @@ export function StepAuthenticate({
|
|||
size="sm"
|
||||
onClick={() => onCancelFlow(activeFlow.flowId)}
|
||||
disabled={isBusy}
|
||||
className="h-7 text-xs text-muted-foreground/60"
|
||||
className="h-7 text-xs text-muted-foreground"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
|
@ -412,7 +412,7 @@ export function StepAuthenticate({
|
|||
{/* Generic prompt input (non-device-code) */}
|
||||
{activeFlow.prompt && !deviceCode && (
|
||||
<form
|
||||
className="space-y-2 border-t border-border/30 pt-3"
|
||||
className="space-y-2 border-t border-border/50 pt-3"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
if (!activeFlow.prompt?.allowEmpty && !flowInput.trim()) return
|
||||
|
|
@ -446,9 +446,9 @@ export function StepAuthenticate({
|
|||
|
||||
{/* Progress messages */}
|
||||
{activeFlow.progress.length > 0 && (
|
||||
<div className="space-y-1 border-t border-border/30 pt-3">
|
||||
<div className="space-y-1 border-t border-border/50 pt-3">
|
||||
{activeFlow.progress.map((message, i) => (
|
||||
<div key={`${activeFlow.flowId}-${i}`} className="text-xs text-muted-foreground/60">
|
||||
<div key={`${activeFlow.flowId}-${i}`} className="text-xs text-muted-foreground">
|
||||
{message}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -461,7 +461,7 @@ export function StepAuthenticate({
|
|||
|
||||
{/* OAuth unavailable */}
|
||||
{provider.supports.oauth && !provider.supports.oauthAvailable && !hasApiKey && (
|
||||
<div className="rounded-xl border border-border/40 bg-card/30 px-4 py-3.5 text-sm text-muted-foreground">
|
||||
<div className="rounded-xl border border-border/50 bg-card/50 px-4 py-3.5 text-sm text-muted-foreground">
|
||||
Browser sign-in is not available in this runtime. Go back and choose a provider with API-key support.
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -72,9 +72,9 @@ function InlineFolderBrowser({
|
|||
}, [browse])
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-border/40 bg-card/20 overflow-hidden">
|
||||
<div className="rounded-xl border border-border/50 bg-card/50 overflow-hidden">
|
||||
{/* Current path */}
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/30 px-4 py-2.5">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 px-4 py-2.5">
|
||||
<p className="min-w-0 truncate font-mono text-xs text-muted-foreground" title={currentPath}>
|
||||
{currentPath}
|
||||
</p>
|
||||
|
|
@ -123,7 +123,7 @@ function InlineFolderBrowser({
|
|||
>
|
||||
<Folder className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="min-w-0 flex-1 truncate text-foreground">{entry.name}</span>
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/30 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/50 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||
</button>
|
||||
))}
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ function InlineFolderBrowser({
|
|||
</ScrollArea>
|
||||
|
||||
{/* Cancel */}
|
||||
<div className="border-t border-border/30 px-4 py-2">
|
||||
<div className="border-t border-border/50 px-4 py-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
|
@ -306,7 +306,7 @@ export function StepDevRoot({ onNext, onBack }: StepDevRootProps) {
|
|||
"active:scale-[0.96]",
|
||||
path === suggestion
|
||||
? "border-foreground/25 bg-foreground/10 text-foreground"
|
||||
: "border-border/40 text-muted-foreground hover:border-foreground/15 hover:text-foreground",
|
||||
: "border-border/50 text-muted-foreground hover:border-foreground/15 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{suggestion}
|
||||
|
|
@ -337,7 +337,7 @@ export function StepDevRoot({ onNext, onBack }: StepDevRootProps) {
|
|||
<Button
|
||||
variant="ghost"
|
||||
onClick={onNext}
|
||||
className="gap-1.5 text-muted-foreground/70 transition-transform active:scale-[0.96]"
|
||||
className="gap-1.5 text-muted-foreground transition-transform active:scale-[0.96]"
|
||||
data-testid="onboarding-devroot-skip"
|
||||
>
|
||||
Skip
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export function StepMode({ selected, onSelect, onNext, onBack }: StepModeProps)
|
|||
"active:scale-[0.98]",
|
||||
isSelected
|
||||
? "border-foreground/30 bg-foreground/[0.06] shadow-[0_0_0_1px_rgba(255,255,255,0.06)]"
|
||||
: "border-border/50 bg-card/30 hover:border-foreground/15 hover:bg-card/60",
|
||||
: "border-border/50 bg-card/50 hover:border-foreground/15 hover:bg-card/50",
|
||||
)}
|
||||
data-testid={`onboarding-mode-${opt.id}`}
|
||||
>
|
||||
|
|
@ -141,7 +141,7 @@ export function StepMode({ selected, onSelect, onNext, onBack }: StepModeProps)
|
|||
<span
|
||||
className={cn(
|
||||
"ml-2 text-xs font-medium transition-colors duration-200",
|
||||
isSelected ? "text-foreground/50" : "text-muted-foreground/50",
|
||||
isSelected ? "text-muted-foreground" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{opt.tagline}
|
||||
|
|
@ -149,7 +149,7 @@ export function StepMode({ selected, onSelect, onNext, onBack }: StepModeProps)
|
|||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="mt-2 text-[13px] leading-relaxed text-muted-foreground/80">
|
||||
<p className="mt-2 text-[13px] leading-relaxed text-muted-foreground">
|
||||
{opt.description}
|
||||
</p>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export function StepOptional({ sections, onBack, onNext }: StepOptionalProps) {
|
|||
"flex items-start gap-3.5 rounded-xl border px-4 py-3.5 transition-colors",
|
||||
section.configured
|
||||
? "border-success/15 bg-success/[0.03]"
|
||||
: "border-border/40 bg-card/20",
|
||||
: "border-border/50 bg-card/50",
|
||||
)}
|
||||
data-testid={`onboarding-optional-${section.id}`}
|
||||
>
|
||||
|
|
@ -74,7 +74,7 @@ export function StepOptional({ sections, onBack, onNext }: StepOptionalProps) {
|
|||
"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
|
||||
section.configured
|
||||
? "bg-success/15 text-success"
|
||||
: "bg-foreground/[0.05] text-muted-foreground/40",
|
||||
: "bg-foreground/[0.05] text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{section.configured ? (
|
||||
|
|
@ -95,7 +95,7 @@ export function StepOptional({ sections, onBack, onNext }: StepOptionalProps) {
|
|||
"text-[10px]",
|
||||
section.configured
|
||||
? "border-success/15 text-success/70"
|
||||
: "border-border/40 text-muted-foreground/50",
|
||||
: "border-border/50 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{section.configured ? "Ready" : "Skipped"}
|
||||
|
|
@ -115,7 +115,7 @@ export function StepOptional({ sections, onBack, onNext }: StepOptionalProps) {
|
|||
<Badge
|
||||
key={item}
|
||||
variant="outline"
|
||||
className="border-border/30 text-[10px] text-muted-foreground/60"
|
||||
className="border-border/50 text-[10px] text-muted-foreground"
|
||||
>
|
||||
{item}
|
||||
</Badge>
|
||||
|
|
@ -124,7 +124,7 @@ export function StepOptional({ sections, onBack, onNext }: StepOptionalProps) {
|
|||
)}
|
||||
|
||||
{section.configuredItems.length === 0 && (
|
||||
<p className="mt-0.5 text-xs text-muted-foreground/50">
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
Not configured — add later from settings.
|
||||
</p>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
)}
|
||||
|
||||
{noDevRoot && (
|
||||
<div className="rounded-xl border border-border/40 bg-card/30 px-4 py-6 text-center text-sm text-muted-foreground">
|
||||
<div className="rounded-xl border border-border/50 bg-card/50 px-4 py-6 text-center text-sm text-muted-foreground">
|
||||
No dev root configured. Go back and set one, or finish setup to configure later.
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -278,7 +278,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
"active:scale-[0.98]",
|
||||
isSwitching
|
||||
? "border-foreground/30 bg-foreground/[0.06]"
|
||||
: "border-border/40 bg-card/20 hover:border-foreground/15 hover:bg-card/50",
|
||||
: "border-border/50 bg-card/50 hover:border-foreground/15 hover:bg-card/50",
|
||||
switchingTo && !isSwitching && "opacity-40 pointer-events-none",
|
||||
)}
|
||||
>
|
||||
|
|
@ -288,7 +288,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
project.kind === "active-gsd" ? "bg-success/10" : "bg-foreground/[0.04]",
|
||||
)}>
|
||||
{isSwitching ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin text-foreground/60" />
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
) : (
|
||||
<KindIcon className={cn("h-4 w-4", style.color)} />
|
||||
)}
|
||||
|
|
@ -310,7 +310,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
{stack.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="rounded bg-foreground/[0.04] px-1.5 py-0.5 text-[10px] text-muted-foreground/60"
|
||||
className="rounded bg-foreground/[0.04] px-1.5 py-0.5 text-[10px] text-muted-foreground"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
|
|
@ -320,7 +320,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
|
||||
{/* Row 3: progress info (for active-gsd projects) */}
|
||||
{progress && (
|
||||
<div className="mt-1.5 text-[11px] text-muted-foreground/50">
|
||||
<div className="mt-1.5 text-[11px] text-muted-foreground">
|
||||
{progress}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -336,7 +336,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] tabular-nums text-muted-foreground/40">
|
||||
<span className="text-[10px] tabular-nums text-muted-foreground">
|
||||
{milestoneCount}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -344,7 +344,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
</div>
|
||||
|
||||
{/* Arrow */}
|
||||
<ArrowRight className="mt-1 h-4 w-4 shrink-0 text-muted-foreground/20 transition-all group-hover:text-muted-foreground/60 group-hover:translate-x-0.5" />
|
||||
<ArrowRight className="mt-1 h-4 w-4 shrink-0 text-muted-foreground/50 transition-all group-hover:text-muted-foreground group-hover:translate-x-0.5" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
|
@ -352,7 +352,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
)}
|
||||
|
||||
{!loading && devRoot && projects.length === 0 && !error && (
|
||||
<div className="rounded-xl border border-border/40 bg-card/30 px-4 py-6 text-center text-sm text-muted-foreground">
|
||||
<div className="rounded-xl border border-border/50 bg-card/50 px-4 py-6 text-center text-sm text-muted-foreground">
|
||||
No projects found in {devRoot}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -367,7 +367,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
disabled={!!switchingTo}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-3.5 rounded-xl border border-dashed px-4 py-3.5 text-left transition-all duration-200",
|
||||
"border-border/40 text-muted-foreground hover:border-foreground/15 hover:text-foreground",
|
||||
"border-border/50 text-muted-foreground hover:border-foreground/15 hover:text-foreground",
|
||||
"active:scale-[0.98]",
|
||||
switchingTo && "opacity-40 pointer-events-none",
|
||||
)}
|
||||
|
|
@ -377,7 +377,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium">Create new project</span>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground/50">Initialize a new directory with Git</p>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">Initialize a new directory with Git</p>
|
||||
</div>
|
||||
</button>
|
||||
) : (
|
||||
|
|
@ -385,7 +385,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "auto" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="rounded-xl border border-border/40 bg-card/30 p-4 space-y-3"
|
||||
className="rounded-xl border border-border/50 bg-card/50 p-4 space-y-3"
|
||||
>
|
||||
<div className="text-sm font-medium text-foreground">New project</div>
|
||||
<form
|
||||
|
|
@ -411,7 +411,7 @@ export function StepProject({ onFinish, onBack, onBeforeSwitch }: StepProjectPro
|
|||
<p className="text-xs text-destructive">{createError}</p>
|
||||
)}
|
||||
{newName && nameValid && !nameConflict && (
|
||||
<p className="font-mono text-xs text-muted-foreground/40">{devRoot}/{newName}</p>
|
||||
<p className="font-mono text-xs text-muted-foreground">{devRoot}/{newName}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-2 pt-1">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export function StepProvider({ providers, selectedId, onSelect, onNext, onBack }
|
|||
>
|
||||
{groups.map((group) => (
|
||||
<div key={group.label}>
|
||||
<div className="mb-2 px-0.5 text-[11px] font-medium uppercase tracking-widest text-muted-foreground/50">
|
||||
<div className="mb-2 px-0.5 text-[11px] font-medium uppercase tracking-widest text-muted-foreground">
|
||||
{group.label}
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
|
|
@ -96,7 +96,7 @@ export function StepProvider({ providers, selectedId, onSelect, onNext, onBack }
|
|||
"active:scale-[0.98]",
|
||||
selected
|
||||
? "border-foreground/30 bg-foreground/[0.06]"
|
||||
: "border-border/40 bg-card/20 hover:border-foreground/15 hover:bg-card/50",
|
||||
: "border-border/50 bg-card/50 hover:border-foreground/15 hover:bg-card/50",
|
||||
)}
|
||||
data-testid={`onboarding-provider-${provider.id}`}
|
||||
>
|
||||
|
|
@ -116,7 +116,7 @@ export function StepProvider({ providers, selectedId, onSelect, onNext, onBack }
|
|||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-foreground">{provider.label}</span>
|
||||
{provider.recommended && (
|
||||
<Badge variant="outline" className="border-foreground/10 bg-foreground/[0.03] text-[9px] text-foreground/50">
|
||||
<Badge variant="outline" className="border-foreground/10 bg-foreground/[0.03] text-[9px] text-muted-foreground">
|
||||
Recommended
|
||||
</Badge>
|
||||
)}
|
||||
|
|
@ -129,7 +129,7 @@ export function StepProvider({ providers, selectedId, onSelect, onNext, onBack }
|
|||
<span>{configuredViaLabel(provider.configuredVia)}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-muted-foreground/50">Not configured</span>
|
||||
<span className="text-muted-foreground">Not configured</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -138,7 +138,7 @@ export function StepProvider({ providers, selectedId, onSelect, onNext, onBack }
|
|||
{capabilityBadges(provider).map((cap) => (
|
||||
<Tooltip key={cap}>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge variant="outline" className="border-border/30 text-[10px] text-muted-foreground/60">
|
||||
<Badge variant="outline" className="border-border/50 text-[10px] text-muted-foreground">
|
||||
{cap}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export function StepReady({ providerLabel, onFinish }: StepReadyProps) {
|
|||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.26, duration: 0.4 }}
|
||||
className="mt-8 flex items-center gap-4 rounded-xl border border-border/40 bg-card/30 px-5 py-3"
|
||||
className="mt-8 flex items-center gap-4 rounded-xl border border-border/50 bg-card/50 px-5 py-3"
|
||||
>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Image
|
||||
|
|
@ -70,7 +70,7 @@ export function StepReady({ providerLabel, onFinish }: StepReadyProps) {
|
|||
/>
|
||||
<span>Shell unlocked</span>
|
||||
</div>
|
||||
<div className="h-3 w-px bg-border/60" />
|
||||
<div className="h-3 w-px bg-border" />
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-success" />
|
||||
<span>{providerLabel}</span>
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ export function StepRemote({ onBack, onNext }: StepRemoteProps) {
|
|||
{/* Channel picker */}
|
||||
{!loading && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-medium text-muted-foreground/60">Channel</div>
|
||||
<div className="text-xs font-medium text-muted-foreground">Channel</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{CHANNEL_OPTIONS.map((opt) => (
|
||||
<button
|
||||
|
|
@ -225,11 +225,11 @@ export function StepRemote({ onBack, onNext }: StepRemoteProps) {
|
|||
"active:scale-[0.97]",
|
||||
channel === opt.value
|
||||
? "border-foreground/30 bg-foreground/[0.06]"
|
||||
: "border-border/40 bg-card/20 hover:border-foreground/15 hover:bg-card/50",
|
||||
: "border-border/50 bg-card/50 hover:border-foreground/15 hover:bg-card/50",
|
||||
)}
|
||||
>
|
||||
<div className="text-sm font-medium text-foreground">{opt.label}</div>
|
||||
<div className="mt-0.5 text-[11px] text-muted-foreground/60">{opt.description}</div>
|
||||
<div className="mt-0.5 text-[11px] text-muted-foreground">{opt.description}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -239,7 +239,7 @@ export function StepRemote({ onBack, onNext }: StepRemoteProps) {
|
|||
{/* Channel ID input */}
|
||||
{channel && !loading && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-medium text-muted-foreground/60">Channel ID</div>
|
||||
<div className="text-xs font-medium text-muted-foreground">Channel ID</div>
|
||||
<Input
|
||||
value={channelId}
|
||||
onChange={(e) => {
|
||||
|
|
@ -266,7 +266,7 @@ export function StepRemote({ onBack, onNext }: StepRemoteProps) {
|
|||
{/* Bot token input */}
|
||||
{channel && !loading && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-medium text-muted-foreground/60">
|
||||
<div className="text-xs font-medium text-muted-foreground">
|
||||
Bot token
|
||||
{tokenSet && (
|
||||
<span className="ml-2 text-success">✓ configured</span>
|
||||
|
|
@ -296,7 +296,7 @@ export function StepRemote({ onBack, onNext }: StepRemoteProps) {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setShowToken((v) => !v)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground/50 hover:text-muted-foreground transition-colors"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-muted-foreground transition-colors"
|
||||
>
|
||||
{showToken ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
|
|
@ -365,7 +365,7 @@ export function StepRemote({ onBack, onNext }: StepRemoteProps) {
|
|||
<Button
|
||||
variant="ghost"
|
||||
onClick={onNext}
|
||||
className="gap-1.5 text-muted-foreground/70 transition-transform active:scale-[0.96]"
|
||||
className="gap-1.5 text-muted-foreground transition-transform active:scale-[0.96]"
|
||||
>
|
||||
Skip
|
||||
<SkipForward className="h-3.5 w-3.5" />
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export function StepWelcome({ onNext }: StepWelcomeProps) {
|
|||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.24, duration: 0.4 }}
|
||||
className="mt-10 flex items-center gap-3 text-xs text-muted-foreground/60"
|
||||
className="mt-10 flex items-center gap-3 text-xs text-muted-foreground"
|
||||
>
|
||||
{["Mode", "Provider", "Auth", "Workspace"].map((label, i) => (
|
||||
<span key={label} className="flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ export function WizardStepper({ steps, currentIndex, onStepClick, className }: W
|
|||
className={cn(
|
||||
"hidden text-sm font-medium transition-colors duration-200 sm:inline",
|
||||
isCurrent && "text-foreground",
|
||||
isComplete && "text-foreground/70",
|
||||
!isComplete && !isCurrent && "text-muted-foreground/60",
|
||||
isComplete && "text-muted-foreground",
|
||||
!isComplete && !isCurrent && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{step.shortLabel ?? step.label}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ export function ProjectWelcome({
|
|||
|
||||
{/* Detail note */}
|
||||
{variant.detail && (
|
||||
<p className="mt-2 text-xs leading-relaxed text-muted-foreground/70">
|
||||
<p className="mt-2 text-xs leading-relaxed text-muted-foreground">
|
||||
{variant.detail}
|
||||
</p>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ function ProjectCard({
|
|||
"active:scale-[0.98]",
|
||||
isActive
|
||||
? "border-primary/30 bg-primary/[0.08]"
|
||||
: "border-border/40 bg-card/20 hover:border-foreground/15 hover:bg-card/50",
|
||||
: "border-border/50 bg-card/50 hover:border-foreground/15 hover:bg-card/50",
|
||||
disabled && "opacity-40 pointer-events-none",
|
||||
)}
|
||||
>
|
||||
|
|
@ -227,7 +227,7 @@ function ProjectCard({
|
|||
|
||||
{/* Row 3: progress info */}
|
||||
{progress && (
|
||||
<div className="mt-1.5 text-[11px] text-muted-foreground/70">{progress}</div>
|
||||
<div className="mt-1.5 text-[11px] text-muted-foreground">{progress}</div>
|
||||
)}
|
||||
|
||||
{/* Row 4: milestone progress bar */}
|
||||
|
|
@ -243,13 +243,13 @@ function ProjectCard({
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] tabular-nums text-muted-foreground/60">{milestoneCount}</span>
|
||||
<span className="text-[10px] tabular-nums text-muted-foreground">{milestoneCount}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Arrow */}
|
||||
<ArrowRight className="mt-1 h-4 w-4 shrink-0 text-muted-foreground/30 transition-all group-hover:text-muted-foreground/70 group-hover:translate-x-0.5" />
|
||||
<ArrowRight className="mt-1 h-4 w-4 shrink-0 text-muted-foreground/50 transition-all group-hover:text-muted-foreground group-hover:translate-x-0.5" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -443,7 +443,7 @@ export function ProjectsPanel({
|
|||
onClick={() => setNewProjectOpen(true)}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-3.5 rounded-xl border border-dashed px-4 py-3.5 text-left transition-all duration-200",
|
||||
"border-border/40 text-muted-foreground hover:border-foreground/15 hover:text-foreground",
|
||||
"border-border/50 text-muted-foreground hover:border-foreground/15 hover:text-foreground",
|
||||
"active:scale-[0.98]",
|
||||
)}
|
||||
>
|
||||
|
|
@ -452,7 +452,7 @@ export function ProjectsPanel({
|
|||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium">Create new project</span>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground/70">Initialize a new directory with Git</p>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">Initialize a new directory with Git</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
|
@ -477,7 +477,7 @@ export function ProjectsPanel({
|
|||
</SheetHeader>
|
||||
|
||||
{/* Visible header */}
|
||||
<div className="flex items-center justify-between border-b border-border/40 px-5 py-4">
|
||||
<div className="flex items-center justify-between border-b border-border/50 px-5 py-4">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-foreground">Projects</h2>
|
||||
{devRoot && !loading && (
|
||||
|
|
@ -491,7 +491,7 @@ export function ProjectsPanel({
|
|||
>
|
||||
Change
|
||||
</button>
|
||||
<span className="text-muted-foreground/50">·</span>
|
||||
<span className="text-muted-foreground">·</span>
|
||||
<span>{projects.length} project{projects.length !== 1 ? "s" : ""}</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -537,7 +537,7 @@ function ActiveProjectSummary({ workspaceState }: { workspaceState: ReturnType<t
|
|||
|
||||
if (parts.length === 0) return null
|
||||
|
||||
return <div className="mt-1.5 text-[11px] text-muted-foreground/70">{parts.join(" · ")}</div>
|
||||
return <div className="mt-1.5 text-[11px] text-muted-foreground">{parts.join(" · ")}</div>
|
||||
}
|
||||
|
||||
// ─── New Project Dialog ────────────────────────────────────────────────
|
||||
|
|
@ -726,7 +726,7 @@ function FolderPickerDialog({
|
|||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="border-y border-border/40 bg-muted/30 px-5 py-2">
|
||||
<div className="border-y border-border/50 bg-muted/50 px-5 py-2">
|
||||
<p className="font-mono text-xs text-muted-foreground truncate" title={currentPath}>
|
||||
{currentPath}
|
||||
</p>
|
||||
|
|
@ -762,7 +762,7 @@ function FolderPickerDialog({
|
|||
>
|
||||
<Folder className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-foreground truncate flex-1">{entry.name}</span>
|
||||
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground/40 opacity-0 group-hover:opacity-100 transition-opacity shrink-0" />
|
||||
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0" />
|
||||
</button>
|
||||
))}
|
||||
|
||||
|
|
@ -774,7 +774,7 @@ function FolderPickerDialog({
|
|||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter className="border-t border-border/40 px-5 py-3">
|
||||
<DialogFooter className="border-t border-border/50 px-5 py-3">
|
||||
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
|
@ -845,7 +845,7 @@ function DevRootSetup({
|
|||
return (
|
||||
<div className="space-y-3" data-testid="devroot-settings">
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 truncate rounded border border-border/40 bg-muted/30 px-3 py-2 font-mono text-xs text-foreground">
|
||||
<code className="flex-1 truncate rounded border border-border/50 bg-muted/50 px-3 py-2 font-mono text-xs text-foreground">
|
||||
{currentRoot}
|
||||
</code>
|
||||
<Button
|
||||
|
|
@ -945,7 +945,7 @@ export function DevRootSettingsSection() {
|
|||
<div className="space-y-3" data-testid="settings-devroot">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<FolderRoot className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-foreground/70">
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
Development Root
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -1164,7 +1164,7 @@ export function ProjectSelectionGate() {
|
|||
{/* Dev root + change button */}
|
||||
{devRoot && (
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<FolderRoot className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50" />
|
||||
<FolderRoot className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground truncate">{devRoot}</code>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -1179,18 +1179,18 @@ export function ProjectSelectionGate() {
|
|||
|
||||
{/* Filter + count */}
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p className="text-xs text-muted-foreground/60 tabular-nums">
|
||||
<p className="text-xs text-muted-foreground tabular-nums">
|
||||
{sortedProjects.length} project{sortedProjects.length !== 1 ? "s" : ""}
|
||||
</p>
|
||||
{showFilter && (
|
||||
<div className="relative w-48">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50" />
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter…"
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
className="h-8 w-full rounded-md border border-border bg-background pl-8 pr-3 text-xs text-foreground placeholder:text-muted-foreground/40 focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
className="h-8 w-full rounded-md border border-border bg-background pl-8 pr-3 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1232,7 +1232,7 @@ export function ProjectSelectionGate() {
|
|||
<span>{stack.join(" · ")}</span>
|
||||
)}
|
||||
{stack.length > 0 && progress && (
|
||||
<span className="text-muted-foreground/30">—</span>
|
||||
<span className="text-muted-foreground/50">—</span>
|
||||
)}
|
||||
{progress && (
|
||||
<span className="truncate">{progress}</span>
|
||||
|
|
@ -1249,7 +1249,7 @@ export function ProjectSelectionGate() {
|
|||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] tabular-nums text-muted-foreground/50 w-6 text-right">
|
||||
<span className="text-[10px] tabular-nums text-muted-foreground w-6 text-right">
|
||||
{project.progress!.milestonesCompleted}/{project.progress!.milestonesTotal}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -1257,13 +1257,13 @@ export function ProjectSelectionGate() {
|
|||
|
||||
{/* Modified time */}
|
||||
{project.lastModified > 0 && (
|
||||
<span className="hidden lg:inline text-[10px] text-muted-foreground/40 shrink-0 w-16 text-right tabular-nums">
|
||||
<span className="hidden lg:inline text-[10px] text-muted-foreground shrink-0 w-16 text-right tabular-nums">
|
||||
{relativeTime(project.lastModified)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Arrow */}
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground/20 transition-colors group-hover:text-muted-foreground/60" />
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground/50 transition-colors group-hover:text-muted-foreground" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ function PanelHeader({
|
|||
<div className="flex items-center justify-between gap-3 pb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="text-muted-foreground">{icon}</span>
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-foreground/70">{title}</h3>
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{title}</h3>
|
||||
{status}
|
||||
{subtitle && <span className="text-[11px] text-muted-foreground">{subtitle}</span>}
|
||||
</div>
|
||||
|
|
@ -109,7 +109,7 @@ function PanelLoading({ label }: { label: string }) {
|
|||
|
||||
function PanelEmpty({ message }: { message: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
{message}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -123,7 +123,7 @@ function InfoPill({ label, value, variant }: { label: string; value: string | nu
|
|||
variant === "warning" && "border-warning/20 bg-warning/5 text-warning",
|
||||
variant === "success" && "border-success/20 bg-success/5 text-success",
|
||||
variant === "error" && "border-destructive/20 bg-destructive/5 text-destructive",
|
||||
(!variant || variant === "default") && "border-border/40 bg-card/50 text-foreground/80",
|
||||
(!variant || variant === "default") && "border-border/50 bg-card/50 text-foreground/80",
|
||||
)}>
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<span className="font-medium tabular-nums">{value}</span>
|
||||
|
|
@ -155,21 +155,21 @@ export function QuickPanel() {
|
|||
icon={<Zap className="h-3.5 w-3.5" />}
|
||||
/>
|
||||
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-4 py-4 space-y-3">
|
||||
<p className="text-xs text-foreground/90">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-4 py-4 space-y-3">
|
||||
<p className="text-xs text-foreground">
|
||||
Create a quick one-off task outside the current plan. Useful for small fixes, experiments, or ad-hoc work that
|
||||
doesn't fit into the milestone structure.
|
||||
</p>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Usage</h4>
|
||||
<div className="rounded-md border border-border/20 bg-background/50 px-3 py-2 font-mono text-[11px] text-foreground/80">
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Usage</h4>
|
||||
<div className="rounded-md border border-border/50 bg-background/50 px-3 py-2 font-mono text-[11px] text-foreground/80">
|
||||
/gsd quick <description>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Examples</h4>
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Examples</h4>
|
||||
<div className="space-y-1.5">
|
||||
{[
|
||||
"Fix the typo in README.md header",
|
||||
|
|
@ -178,8 +178,8 @@ export function QuickPanel() {
|
|||
"Run prettier on the whole project",
|
||||
].map((example) => (
|
||||
<div key={example} className="flex items-center gap-2 text-[11px]">
|
||||
<span className="text-muted-foreground/50">$</span>
|
||||
<code className="font-mono text-foreground/70">/gsd quick {example}</code>
|
||||
<span className="text-muted-foreground">$</span>
|
||||
<code className="font-mono text-muted-foreground">/gsd quick {example}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -231,7 +231,7 @@ export function HistoryPanel() {
|
|||
</div>
|
||||
|
||||
{/* Tab switcher */}
|
||||
<div className="flex gap-1 rounded-lg border border-border/30 bg-card/20 p-0.5">
|
||||
<div className="flex gap-1 rounded-lg border border-border/50 bg-card/50 p-0.5">
|
||||
{(["phase", "slice", "model", "units"] as const).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
|
|
@ -240,8 +240,8 @@ export function HistoryPanel() {
|
|||
className={cn(
|
||||
"flex-1 rounded-md px-2.5 py-1 text-[11px] font-medium capitalize transition-colors",
|
||||
activeTab === tab
|
||||
? "bg-card/80 text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground/70",
|
||||
? "bg-card text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{tab === "units" ? "Recent" : `By ${tab}`}
|
||||
|
|
@ -251,10 +251,10 @@ export function HistoryPanel() {
|
|||
|
||||
{/* By Phase */}
|
||||
{activeTab === "phase" && data.byPhase.length > 0 && (
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Phase</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Units</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Cost</th>
|
||||
|
|
@ -263,7 +263,7 @@ export function HistoryPanel() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{data.byPhase.map((row: HistoryPhaseAggregate) => (
|
||||
<tr key={row.phase} className="border-b border-border/20 last:border-0">
|
||||
<tr key={row.phase} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80 capitalize">{row.phase}</td>
|
||||
<td className="px-2.5 py-1.5 text-right tabular-nums text-foreground/80">{row.units}</td>
|
||||
<td className="px-2.5 py-1.5 text-right tabular-nums text-foreground/80">{formatCost(row.cost)}</td>
|
||||
|
|
@ -277,10 +277,10 @@ export function HistoryPanel() {
|
|||
|
||||
{/* By Slice */}
|
||||
{activeTab === "slice" && data.bySlice.length > 0 && (
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Slice</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Units</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Cost</th>
|
||||
|
|
@ -289,7 +289,7 @@ export function HistoryPanel() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{data.bySlice.map((row: HistorySliceAggregate) => (
|
||||
<tr key={row.sliceId} className="border-b border-border/20 last:border-0">
|
||||
<tr key={row.sliceId} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80">{row.sliceId}</td>
|
||||
<td className="px-2.5 py-1.5 text-right tabular-nums text-foreground/80">{row.units}</td>
|
||||
<td className="px-2.5 py-1.5 text-right tabular-nums text-foreground/80">{formatCost(row.cost)}</td>
|
||||
|
|
@ -303,10 +303,10 @@ export function HistoryPanel() {
|
|||
|
||||
{/* By Model */}
|
||||
{activeTab === "model" && data.byModel.length > 0 && (
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Model</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Units</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Cost</th>
|
||||
|
|
@ -314,7 +314,7 @@ export function HistoryPanel() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{data.byModel.map((row: HistoryModelAggregate) => (
|
||||
<tr key={row.model} className="border-b border-border/20 last:border-0">
|
||||
<tr key={row.model} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80 truncate max-w-[180px]">{row.model}</td>
|
||||
<td className="px-2.5 py-1.5 text-right tabular-nums text-foreground/80">{row.units}</td>
|
||||
<td className="px-2.5 py-1.5 text-right tabular-nums text-foreground/80">{formatCost(row.cost)}</td>
|
||||
|
|
@ -329,10 +329,10 @@ export function HistoryPanel() {
|
|||
{activeTab === "units" && (
|
||||
<>
|
||||
{data.units.length > 0 ? (
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Type</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">ID</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Model</th>
|
||||
|
|
@ -342,7 +342,7 @@ export function HistoryPanel() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{data.units.slice(0, 20).map((u, i) => (
|
||||
<tr key={i} className="border-b border-border/20 last:border-0">
|
||||
<tr key={i} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80">{u.type}</td>
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80 truncate max-w-[120px]">{u.id}</td>
|
||||
<td className="px-2.5 py-1.5 text-muted-foreground truncate max-w-[120px]">{u.model}</td>
|
||||
|
|
@ -418,7 +418,7 @@ export function UndoPanel() {
|
|||
{result.success ? <CheckCircle2 className="h-3.5 w-3.5" /> : <XCircle className="h-3.5 w-3.5" />}
|
||||
<span className="font-medium">{result.success ? "Undo Successful" : "Undo Failed"}</span>
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-foreground/70">{result.message}</p>
|
||||
<p className="mt-1 text-[11px] text-muted-foreground">{result.message}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -427,8 +427,8 @@ export function UndoPanel() {
|
|||
{data.lastUnitType ? (
|
||||
<>
|
||||
{/* Last unit info */}
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Last Completed Unit</h4>
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Last Completed Unit</h4>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-0.5 text-[11px]">
|
||||
<span className="text-muted-foreground">Type</span>
|
||||
<span className="font-mono text-foreground/80">{data.lastUnitType}</span>
|
||||
|
|
@ -449,7 +449,7 @@ export function UndoPanel() {
|
|||
{/* Commit SHAs */}
|
||||
{data.commits.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70">Associated Commits</h4>
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground">Associated Commits</h4>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{data.commits.map((sha) => (
|
||||
<Badge key={sha} variant="outline" className="text-[10px] px-1.5 py-0 font-mono">
|
||||
|
|
@ -565,13 +565,13 @@ export function SteerPanel() {
|
|||
|
||||
{/* Current overrides */}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Current Overrides</h4>
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Current Overrides</h4>
|
||||
{data?.overridesContent ? (
|
||||
<div className="rounded-lg border border-border/30 bg-background/50 px-3 py-2.5 text-[11px] font-mono text-foreground/80 whitespace-pre-wrap max-h-[200px] overflow-y-auto leading-relaxed">
|
||||
<div className="rounded-lg border border-border/50 bg-background/50 px-3 py-2.5 text-[11px] font-mono text-foreground/80 whitespace-pre-wrap max-h-[200px] overflow-y-auto leading-relaxed">
|
||||
{data.overridesContent}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 text-[11px] text-muted-foreground italic">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 text-[11px] text-muted-foreground italic">
|
||||
No active overrides
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -579,7 +579,7 @@ export function SteerPanel() {
|
|||
|
||||
{/* Steer message form */}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Send Steering Message</h4>
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Send Steering Message</h4>
|
||||
<Textarea
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
|
|
@ -633,10 +633,10 @@ export function HooksPanel() {
|
|||
{data && (
|
||||
<>
|
||||
{data.entries.length > 0 ? (
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Name</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Type</th>
|
||||
<th className="px-2.5 py-1.5 text-center font-medium text-muted-foreground">Status</th>
|
||||
|
|
@ -648,7 +648,7 @@ export function HooksPanel() {
|
|||
{data.entries.map((entry: HookStatusEntry) => {
|
||||
const totalCycles = Object.values(entry.activeCycles).reduce((sum, n) => sum + n, 0)
|
||||
return (
|
||||
<tr key={entry.name} className="border-b border-border/20 last:border-0">
|
||||
<tr key={entry.name} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80">{entry.name}</td>
|
||||
<td className="px-2.5 py-1.5">
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
|
||||
|
|
@ -684,7 +684,7 @@ export function HooksPanel() {
|
|||
|
||||
{/* Formatted status */}
|
||||
{data.formattedStatus && (
|
||||
<div className="rounded-lg border border-border/30 bg-background/50 px-3 py-2.5 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap leading-relaxed">
|
||||
<div className="rounded-lg border border-border/50 bg-background/50 px-3 py-2.5 text-[11px] font-mono text-muted-foreground whitespace-pre-wrap leading-relaxed">
|
||||
{data.formattedStatus}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -730,11 +730,11 @@ export function InspectPanel() {
|
|||
{/* Recent decisions */}
|
||||
{data.recentDecisions.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Recent Decisions ({data.recentDecisions.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Recent Decisions ({data.recentDecisions.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">ID</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Decision</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Choice</th>
|
||||
|
|
@ -742,7 +742,7 @@ export function InspectPanel() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{data.recentDecisions.map((d) => (
|
||||
<tr key={d.id} className="border-b border-border/20 last:border-0">
|
||||
<tr key={d.id} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80">{d.id}</td>
|
||||
<td className="px-2.5 py-1.5 text-foreground/80 max-w-[200px] truncate">{d.decision}</td>
|
||||
<td className="px-2.5 py-1.5 text-muted-foreground max-w-[150px] truncate">{d.choice}</td>
|
||||
|
|
@ -757,11 +757,11 @@ export function InspectPanel() {
|
|||
{/* Recent requirements */}
|
||||
{data.recentRequirements.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Recent Requirements ({data.recentRequirements.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Recent Requirements ({data.recentRequirements.length})</h4>
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">ID</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Status</th>
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Description</th>
|
||||
|
|
@ -769,7 +769,7 @@ export function InspectPanel() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{data.recentRequirements.map((r) => (
|
||||
<tr key={r.id} className="border-b border-border/20 last:border-0">
|
||||
<tr key={r.id} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80">{r.id}</td>
|
||||
<td className="px-2.5 py-1.5">
|
||||
<Badge
|
||||
|
|
@ -843,8 +843,8 @@ export function ExportPanel() {
|
|||
|
||||
{/* Format selector */}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Format</h4>
|
||||
<div className="flex gap-1 rounded-lg border border-border/30 bg-card/20 p-0.5">
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Format</h4>
|
||||
<div className="flex gap-1 rounded-lg border border-border/50 bg-card/50 p-0.5">
|
||||
{(["markdown", "json"] as const).map((f) => (
|
||||
<button
|
||||
key={f}
|
||||
|
|
@ -853,8 +853,8 @@ export function ExportPanel() {
|
|||
className={cn(
|
||||
"flex-1 rounded-md px-3 py-1.5 text-[11px] font-medium capitalize transition-colors",
|
||||
format === f
|
||||
? "bg-card/80 text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground/70",
|
||||
? "bg-card text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{f === "markdown" ? "Markdown" : "JSON"}
|
||||
|
|
@ -884,7 +884,7 @@ export function ExportPanel() {
|
|||
<span className="font-medium">Export Ready</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[11px] font-mono text-foreground/70">{data.filename}</span>
|
||||
<span className="text-[11px] font-mono text-muted-foreground">{data.filename}</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
|
@ -952,7 +952,7 @@ export function CleanupPanel() {
|
|||
<CheckCircle2 className="h-3.5 w-3.5" />
|
||||
<span className="font-medium">Cleanup Complete</span>
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-foreground/70">{result.message}</p>
|
||||
<p className="mt-1 text-[11px] text-muted-foreground">{result.message}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -961,7 +961,7 @@ export function CleanupPanel() {
|
|||
{/* Branches table */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Branches ({data.branches.length})</h4>
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Branches ({data.branches.length})</h4>
|
||||
{mergedBranches.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -977,17 +977,17 @@ export function CleanupPanel() {
|
|||
)}
|
||||
</div>
|
||||
{data.branches.length > 0 ? (
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Branch</th>
|
||||
<th className="px-2.5 py-1.5 text-center font-medium text-muted-foreground">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.branches.map((b: CleanupBranch) => (
|
||||
<tr key={b.name} className="border-b border-border/20 last:border-0">
|
||||
<tr key={b.name} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80 truncate max-w-[250px]">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<GitBranch className="h-3 w-3 text-muted-foreground shrink-0" />
|
||||
|
|
@ -1018,7 +1018,7 @@ export function CleanupPanel() {
|
|||
{/* Snapshots table */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-xs font-medium text-foreground/70">Snapshots ({data.snapshots.length})</h4>
|
||||
<h4 className="text-xs font-medium text-muted-foreground">Snapshots ({data.snapshots.length})</h4>
|
||||
{oldSnapshots.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -1034,17 +1034,17 @@ export function CleanupPanel() {
|
|||
)}
|
||||
</div>
|
||||
{data.snapshots.length > 0 ? (
|
||||
<div className="overflow-x-auto rounded-lg border border-border/30">
|
||||
<div className="overflow-x-auto rounded-lg border border-border/50">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30 bg-card/40">
|
||||
<tr className="border-b border-border/50 bg-card/50">
|
||||
<th className="px-2.5 py-1.5 text-left font-medium text-muted-foreground">Ref</th>
|
||||
<th className="px-2.5 py-1.5 text-right font-medium text-muted-foreground">Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.snapshots.map((s: CleanupSnapshot) => (
|
||||
<tr key={s.ref} className="border-b border-border/20 last:border-0">
|
||||
<tr key={s.ref} className="border-b border-border/50 last:border-0">
|
||||
<td className="px-2.5 py-1.5 font-mono text-foreground/80 truncate max-w-[200px]">{s.ref}</td>
|
||||
<td className="px-2.5 py-1.5 text-right text-muted-foreground">{s.date}</td>
|
||||
</tr>
|
||||
|
|
@ -1101,13 +1101,13 @@ export function QueuePanel() {
|
|||
"rounded-lg border px-3 py-2.5 space-y-1.5",
|
||||
isActive
|
||||
? "border-info/25 bg-info/5"
|
||||
: "border-border/30 bg-card/30",
|
||||
: "border-border/50 bg-card/50",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-mono font-medium text-foreground/80">{m.id}</span>
|
||||
<span className="text-xs text-foreground/90 truncate">{m.title}</span>
|
||||
<span className="text-xs text-foreground truncate">{m.title}</span>
|
||||
{isActive && (
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 border-info/30 text-info">
|
||||
active
|
||||
|
|
@ -1121,7 +1121,7 @@ export function QueuePanel() {
|
|||
|
||||
{/* Progress bar */}
|
||||
{progress.total > 0 && (
|
||||
<div className="h-1 rounded-full bg-border/30 overflow-hidden">
|
||||
<div className="h-1 rounded-full bg-border/50 overflow-hidden">
|
||||
<div
|
||||
className={cn(
|
||||
"h-full rounded-full transition-all",
|
||||
|
|
@ -1194,8 +1194,8 @@ export function StatusPanel() {
|
|||
/>
|
||||
|
||||
{/* Active context card */}
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-3 space-y-2">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Active Context</h4>
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-3 space-y-2">
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Active Context</h4>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-[11px]">
|
||||
<span className="text-muted-foreground">Phase</span>
|
||||
<span className="font-mono text-foreground/80">
|
||||
|
|
@ -1244,7 +1244,7 @@ export function StatusPanel() {
|
|||
<span>Overall Progress</span>
|
||||
<span className="tabular-nums">{Math.round((doneSlices / totalSlices) * 100)}%</span>
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-border/30 overflow-hidden">
|
||||
<div className="h-1.5 rounded-full bg-border/50 overflow-hidden">
|
||||
<div
|
||||
className={cn(
|
||||
"h-full rounded-full transition-all",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const StatusIcon = ({
|
|||
if (status === "in-progress") {
|
||||
return <Play className={cn(sizeClass, "text-warning")} />
|
||||
}
|
||||
return <Circle className={cn(sizeClass, "text-muted-foreground/40")} />
|
||||
return <Circle className={cn(sizeClass, "text-muted-foreground")} />
|
||||
}
|
||||
|
||||
const RiskBadge = ({ risk }: { risk: RiskLevel }) => {
|
||||
|
|
@ -113,7 +113,7 @@ export function Roadmap() {
|
|||
className={cn(
|
||||
"flex items-center gap-3 px-4 py-2.5",
|
||||
sliceStatus === "in-progress" && "bg-accent/20",
|
||||
sliceStatus === "pending" && "opacity-60",
|
||||
sliceStatus === "pending" && "opacity-70",
|
||||
)}
|
||||
>
|
||||
<div className="w-4" />
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function SettingsHeader({
|
|||
<div className="flex items-center justify-between gap-3 pb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="text-muted-foreground">{icon}</span>
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-foreground/70">{title}</h3>
|
||||
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{title}</h3>
|
||||
{subtitle && <span className="text-[11px] text-muted-foreground">{subtitle}</span>}
|
||||
</div>
|
||||
<Button type="button" variant="ghost" size="sm" onClick={onRefresh} disabled={refreshing} className="h-7 gap-1.5 text-xs">
|
||||
|
|
@ -88,7 +88,7 @@ function SettingsLoading({ label }: { label: string }) {
|
|||
|
||||
function SettingsEmpty({ message }: { message: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-4 py-5 text-center text-xs text-muted-foreground">
|
||||
{message}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -101,7 +101,7 @@ function Pill({ label, value, variant }: { label: string; value: string | number
|
|||
variant === "info" && "border-info/20 bg-info/5 text-info",
|
||||
variant === "warning" && "border-warning/20 bg-warning/5 text-warning",
|
||||
variant === "success" && "border-success/20 bg-success/5 text-success",
|
||||
(!variant || variant === "default") && "border-border/40 bg-card/50 text-foreground/80",
|
||||
(!variant || variant === "default") && "border-border/50 bg-card/50 text-foreground/80",
|
||||
)}>
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<span className="font-medium tabular-nums">{value}</span>
|
||||
|
|
@ -215,7 +215,7 @@ export function PrefsPanel() {
|
|||
</div>
|
||||
|
||||
{/* Toggles */}
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-1.5 rounded-lg border border-border/30 bg-card/30 px-3 py-2.5">
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-1.5 rounded-lg border border-border/50 bg-card/50 px-3 py-2.5">
|
||||
<KvRow label="Auto-Supervisor">
|
||||
{prefs.autoSupervisor?.enabled ? (
|
||||
<span className="text-success">
|
||||
|
|
@ -343,8 +343,8 @@ export function ModelRoutingPanel() {
|
|||
|
||||
{/* Tier assignments */}
|
||||
{routingConfig?.tier_models && (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Tier Assignments</h4>
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Tier Assignments</h4>
|
||||
<TierModelRow tier="light" modelId={routingConfig.tier_models.light} />
|
||||
<TierModelRow tier="standard" modelId={routingConfig.tier_models.standard} />
|
||||
<TierModelRow tier="heavy" modelId={routingConfig.tier_models.heavy} />
|
||||
|
|
@ -370,10 +370,10 @@ export function ModelRoutingPanel() {
|
|||
{/* Top patterns table */}
|
||||
{Object.keys(routingHistory.patterns).length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70">Top Patterns</h4>
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground">Top Patterns</h4>
|
||||
<div className="space-y-2">
|
||||
{topPatterns(routingHistory).map(({ name, total, pattern }) => (
|
||||
<div key={name} className="rounded-lg border border-border/30 bg-card/30 px-3 py-2 space-y-1">
|
||||
<div key={name} className="rounded-lg border border-border/50 bg-card/50 px-3 py-2 space-y-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-mono text-foreground/80 truncate">{name}</span>
|
||||
<span className="text-[10px] text-muted-foreground tabular-nums shrink-0">{total} attempts</span>
|
||||
|
|
@ -455,8 +455,8 @@ export function BudgetPanel() {
|
|||
|
||||
{/* Context budget allocations */}
|
||||
{budget && (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Context Budget Allocations</h4>
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Context Budget Allocations</h4>
|
||||
<KvRow label="Summary Budget">{formatChars(budget.summaryBudgetChars)} chars</KvRow>
|
||||
<KvRow label="Inline Context">{formatChars(budget.inlineContextBudgetChars)} chars</KvRow>
|
||||
<KvRow label="Verification">{formatChars(budget.verificationBudgetChars)} chars</KvRow>
|
||||
|
|
@ -468,7 +468,7 @@ export function BudgetPanel() {
|
|||
{/* Project cost totals */}
|
||||
{totals ? (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Project Cost Totals</h4>
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Project Cost Totals</h4>
|
||||
|
||||
{/* Summary pills */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
|
|
@ -478,8 +478,8 @@ export function BudgetPanel() {
|
|||
</div>
|
||||
|
||||
{/* Token breakdown */}
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-foreground/70 uppercase tracking-wide">Token Breakdown</h4>
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-2.5 space-y-1.5">
|
||||
<h4 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide">Token Breakdown</h4>
|
||||
<KvRow label="Input">{formatTokens(totals.tokens.input)}</KvRow>
|
||||
<KvRow label="Output">{formatTokens(totals.tokens.output)}</KvRow>
|
||||
<KvRow label="Cache Read">{formatTokens(totals.tokens.cacheRead)}</KvRow>
|
||||
|
|
@ -764,7 +764,7 @@ export function RemoteQuestionsPanel() {
|
|||
|
||||
{/* ── Channel picker (card-based) ──────────────────────────── */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-medium text-muted-foreground/60">
|
||||
<div className="text-xs font-medium text-muted-foreground">
|
||||
{isConfigured ? "Switch channel" : "Choose a channel"}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
|
|
@ -783,11 +783,11 @@ export function RemoteQuestionsPanel() {
|
|||
"active:scale-[0.97]",
|
||||
channel === opt.value
|
||||
? "border-foreground/30 bg-foreground/[0.06]"
|
||||
: "border-border/40 bg-card/20 hover:border-foreground/15 hover:bg-card/50",
|
||||
: "border-border/50 bg-card/50 hover:border-foreground/15 hover:bg-card/50",
|
||||
)}
|
||||
>
|
||||
<div className="text-sm font-medium text-foreground">{opt.label}</div>
|
||||
<div className="mt-0.5 text-[11px] text-muted-foreground/60">{opt.description}</div>
|
||||
<div className="mt-0.5 text-[11px] text-muted-foreground">{opt.description}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -795,7 +795,7 @@ export function RemoteQuestionsPanel() {
|
|||
|
||||
{/* ── Channel ID input ─────────────────────────────────────── */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-medium text-muted-foreground/60">Channel ID</div>
|
||||
<div className="text-xs font-medium text-muted-foreground">Channel ID</div>
|
||||
<input
|
||||
type="text"
|
||||
value={channelId}
|
||||
|
|
@ -803,13 +803,13 @@ export function RemoteQuestionsPanel() {
|
|||
placeholder={selectedChannelOption.idPlaceholder}
|
||||
disabled={saving}
|
||||
className={cn(
|
||||
"w-full rounded-xl border bg-card/20 px-4 py-2.5 font-mono text-sm text-foreground",
|
||||
"placeholder:text-muted-foreground/40",
|
||||
"w-full rounded-xl border bg-card/50 px-4 py-2.5 font-mono text-sm text-foreground",
|
||||
"placeholder:text-muted-foreground",
|
||||
"focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent",
|
||||
"transition-colors",
|
||||
channelId.trim().length > 0 && !CHANNEL_ID_PATTERNS[channel].test(channelId.trim())
|
||||
? "border-destructive/40"
|
||||
: "border-border/40",
|
||||
: "border-border/50",
|
||||
)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter" && canSave) void handleSave() }}
|
||||
/>
|
||||
|
|
@ -824,7 +824,7 @@ export function RemoteQuestionsPanel() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAdvanced((v) => !v)}
|
||||
className="flex items-center gap-1.5 text-[11px] text-muted-foreground/60 hover:text-muted-foreground transition-colors"
|
||||
className="flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-muted-foreground transition-colors"
|
||||
>
|
||||
<svg
|
||||
className={cn("h-3 w-3 transition-transform", showAdvanced && "rotate-90")}
|
||||
|
|
@ -839,7 +839,7 @@ export function RemoteQuestionsPanel() {
|
|||
{showAdvanced && (
|
||||
<div className="grid grid-cols-2 gap-3 pl-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[11px] text-muted-foreground/60" htmlFor="rq-timeout">
|
||||
<label className="text-[11px] text-muted-foreground" htmlFor="rq-timeout">
|
||||
Timeout (min)
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -849,11 +849,11 @@ export function RemoteQuestionsPanel() {
|
|||
max={30}
|
||||
value={timeoutMinutes}
|
||||
onChange={(e) => setTimeoutMinutes(Math.max(1, Math.min(30, Number(e.target.value) || 1)))}
|
||||
className="w-full rounded-lg border border-border/40 bg-card/20 px-3 py-2 text-xs text-foreground tabular-nums focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border/50 bg-card/50 px-3 py-2 text-xs text-foreground tabular-nums focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[11px] text-muted-foreground/60" htmlFor="rq-poll">
|
||||
<label className="text-[11px] text-muted-foreground" htmlFor="rq-poll">
|
||||
Poll interval (sec)
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -863,7 +863,7 @@ export function RemoteQuestionsPanel() {
|
|||
max={30}
|
||||
value={pollIntervalSeconds}
|
||||
onChange={(e) => setPollIntervalSeconds(Math.max(2, Math.min(30, Number(e.target.value) || 2)))}
|
||||
className="w-full rounded-lg border border-border/40 bg-card/20 px-3 py-2 text-xs text-foreground tabular-nums focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border/50 bg-card/50 px-3 py-2 text-xs text-foreground tabular-nums focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -888,7 +888,7 @@ export function RemoteQuestionsPanel() {
|
|||
|
||||
{/* ── Bot token ─────────────────────────────────────────── */}
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground/60">Bot token</div>
|
||||
<div className="text-xs font-medium text-muted-foreground">Bot token</div>
|
||||
|
||||
{tokenSuccess && (
|
||||
<div className="flex items-center gap-2.5 rounded-xl border border-success/15 bg-success/[0.04] px-4 py-2.5 text-xs text-muted-foreground">
|
||||
|
|
@ -920,8 +920,8 @@ export function RemoteQuestionsPanel() {
|
|||
placeholder={`Paste your ${selectedChannelOption.label} bot token`}
|
||||
disabled={savingToken}
|
||||
className={cn(
|
||||
"w-full rounded-xl border border-border/40 bg-card/20 pl-4 pr-10 py-2.5 font-mono text-sm text-foreground",
|
||||
"placeholder:text-muted-foreground/40",
|
||||
"w-full rounded-xl border border-border/50 bg-card/50 pl-4 pr-10 py-2.5 font-mono text-sm text-foreground",
|
||||
"placeholder:text-muted-foreground",
|
||||
"focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent",
|
||||
"transition-colors",
|
||||
)}
|
||||
|
|
@ -930,7 +930,7 @@ export function RemoteQuestionsPanel() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setShowToken((v) => !v)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground/50 hover:text-muted-foreground transition-colors"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-muted-foreground transition-colors"
|
||||
>
|
||||
{showToken ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
|
|
@ -976,7 +976,7 @@ function FontSizeControl({
|
|||
previewFont: "mono" | "sans"
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border/30 bg-card/30 px-3 py-3 space-y-3">
|
||||
<div className="rounded-lg border border-border/50 bg-card/50 px-3 py-3 space-y-3">
|
||||
<div>
|
||||
<div className="text-xs font-medium text-foreground">{label}</div>
|
||||
<div className="text-[11px] text-muted-foreground mt-0.5">{description}</div>
|
||||
|
|
@ -992,12 +992,12 @@ function FontSizeControl({
|
|||
"rounded-md border px-3 py-1.5 text-xs font-medium tabular-nums transition-colors",
|
||||
currentSize === size
|
||||
? "border-foreground/30 bg-foreground/10 text-foreground shadow-sm"
|
||||
: "border-border/40 bg-card/50 text-muted-foreground hover:border-foreground/20 hover:text-foreground",
|
||||
: "border-border/50 bg-card/50 text-muted-foreground hover:border-foreground/20 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{size}px
|
||||
{size === defaultSize && (
|
||||
<span className="ml-1 text-[10px] text-muted-foreground/60">(default)</span>
|
||||
<span className="ml-1 text-[10px] text-muted-foreground">(default)</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
|
@ -1005,7 +1005,7 @@ function FontSizeControl({
|
|||
|
||||
<div
|
||||
className={cn(
|
||||
"mt-2 rounded-md border border-border/20 bg-terminal px-3 py-2 text-foreground/80",
|
||||
"mt-2 rounded-md border border-border/50 bg-terminal px-3 py-2 text-foreground/80",
|
||||
previewFont === "mono" ? "font-mono" : "font-sans",
|
||||
)}
|
||||
style={{ fontSize: `${currentSize}px`, lineHeight: 1.35 }}
|
||||
|
|
@ -1141,7 +1141,7 @@ export function ExperimentalPanel() {
|
|||
return (
|
||||
<div
|
||||
key={flag.key}
|
||||
className="rounded-lg border border-border/40 bg-card/30 px-3 py-3 space-y-2"
|
||||
className="rounded-lg border border-border/50 bg-card/50 px-3 py-3 space-y-2"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
|
|
|
|||
|
|
@ -711,7 +711,7 @@ export function ShellTerminal({
|
|||
|
||||
{/* Drop overlay */}
|
||||
{isDragOver && (
|
||||
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-background/80 backdrop-blur-sm border-2 border-dashed border-primary rounded-md pointer-events-none">
|
||||
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-background backdrop-blur-sm border-2 border-dashed border-primary rounded-md pointer-events-none">
|
||||
<ImagePlus className="h-8 w-8 text-primary" />
|
||||
<span className="text-sm font-medium text-primary">Drop image here</span>
|
||||
</div>
|
||||
|
|
@ -719,7 +719,7 @@ export function ShellTerminal({
|
|||
</div>
|
||||
|
||||
{!hideSidebar && (
|
||||
<div className="flex w-[34px] flex-shrink-0 flex-col border-l border-border/40 bg-terminal">
|
||||
<div className="flex w-[34px] flex-shrink-0 flex-col border-l border-border/50 bg-terminal">
|
||||
{/* New terminal button */}
|
||||
<button
|
||||
onClick={createTab}
|
||||
|
|
@ -729,7 +729,7 @@ export function ShellTerminal({
|
|||
<Plus className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
<div className="h-px bg-border/40" />
|
||||
<div className="h-px bg-border/50" />
|
||||
|
||||
{/* Tab list */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const StatusIcon = ({ status }: { status: ItemStatus }) => {
|
|||
if (status === "in-progress") {
|
||||
return <Play className="h-4 w-4 shrink-0 text-warning" />
|
||||
}
|
||||
return <Circle className="h-4 w-4 shrink-0 text-muted-foreground/50" />
|
||||
return <Circle className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
}
|
||||
|
||||
/* ─── Nav Rail (left icon bar) ─── */
|
||||
|
|
@ -110,7 +110,7 @@ export function NavRail({ activeView, onViewChange, isConnecting = false }: NavR
|
|||
className={cn(
|
||||
"flex h-10 w-10 items-center justify-center rounded-md transition-colors",
|
||||
isConnecting
|
||||
? "cursor-not-allowed text-muted-foreground/30"
|
||||
? "cursor-not-allowed text-muted-foreground/50"
|
||||
: activeView === item.id
|
||||
? "bg-accent text-foreground"
|
||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||||
|
|
@ -127,7 +127,7 @@ export function NavRail({ activeView, onViewChange, isConnecting = false }: NavR
|
|||
className={cn(
|
||||
"flex h-10 w-10 items-center justify-center rounded-md transition-colors",
|
||||
isConnecting
|
||||
? "cursor-not-allowed text-muted-foreground/30"
|
||||
? "cursor-not-allowed text-muted-foreground/50"
|
||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||||
)}
|
||||
title={isConnecting ? "Connecting…" : "Projects"}
|
||||
|
|
@ -748,7 +748,7 @@ function MobileNavPanel({ activeView, onViewChange, isConnecting = false }: NavR
|
|||
className={cn(
|
||||
"flex w-full items-center gap-3 rounded-md px-3 py-3 text-sm font-medium transition-colors min-h-[44px]",
|
||||
isConnecting
|
||||
? "cursor-not-allowed text-muted-foreground/30"
|
||||
? "cursor-not-allowed text-muted-foreground/50"
|
||||
: activeView === item.id
|
||||
? "bg-accent text-foreground"
|
||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||||
|
|
|
|||
|
|
@ -89,24 +89,24 @@ function TerminalWidgetBand({
|
|||
|
||||
return (
|
||||
<div
|
||||
className="border-t border-border/50 bg-card/20 px-4 py-2"
|
||||
className="border-t border-border/50 bg-card/50 px-4 py-2"
|
||||
data-testid={placement === "aboveEditor" ? "terminal-widgets-above-editor" : "terminal-widgets-below-editor"}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
{widgets.map((widget) => (
|
||||
<div
|
||||
key={`${widget.placement}:${widget.key}`}
|
||||
className="rounded-md border border-border/60 bg-background/40 px-3 py-2"
|
||||
className="rounded-md border border-border bg-background/50 px-3 py-2"
|
||||
data-testid="terminal-widget"
|
||||
data-widget-key={widget.key}
|
||||
data-widget-placement={widget.placement}
|
||||
title={widget.fullText}
|
||||
>
|
||||
<div className="mb-1 flex items-center justify-between gap-2 text-[10px] uppercase tracking-[0.2em] text-muted-foreground/80">
|
||||
<div className="mb-1 flex items-center justify-between gap-2 text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
||||
<span className="truncate">{widget.key}</span>
|
||||
<span>{widget.placement === "aboveEditor" ? "Above editor" : "Below editor"}</span>
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-foreground/90">
|
||||
<div className="space-y-1 text-xs text-foreground">
|
||||
{widget.visibleLines.map((line, index) => (
|
||||
<div key={`${widget.key}:${index}`} className="whitespace-pre-wrap break-words">
|
||||
{line}
|
||||
|
|
@ -238,7 +238,7 @@ export function Terminal({ className }: TerminalProps) {
|
|||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{workspace.terminalLines.map((line) => (
|
||||
<div key={line.id} className="flex" data-testid="terminal-line">
|
||||
<span className="mr-2 select-none text-muted-foreground/50">{line.timestamp}</span>
|
||||
<span className="mr-2 select-none text-muted-foreground">{line.timestamp}</span>
|
||||
<span
|
||||
className={cn(
|
||||
"whitespace-pre-wrap",
|
||||
|
|
@ -260,7 +260,7 @@ export function Terminal({ className }: TerminalProps) {
|
|||
{workspace.liveTranscript.map((block, i) => (
|
||||
<div
|
||||
key={`transcript-${i}`}
|
||||
className="whitespace-pre-wrap rounded border border-border/30 bg-accent/20 px-3 py-2 text-foreground/90"
|
||||
className="whitespace-pre-wrap rounded border border-border/50 bg-accent/20 px-3 py-2 text-foreground"
|
||||
>
|
||||
{block}
|
||||
</div>
|
||||
|
|
@ -271,7 +271,7 @@ export function Terminal({ className }: TerminalProps) {
|
|||
{/* Live streaming assistant text */}
|
||||
{workspace.streamingAssistantText && (
|
||||
<div className="mt-2" data-testid="terminal-streaming-text">
|
||||
<div className="whitespace-pre-wrap rounded border border-foreground/10 bg-foreground/[0.03] px-3 py-2 text-foreground/90">
|
||||
<div className="whitespace-pre-wrap rounded border border-foreground/10 bg-foreground/[0.03] px-3 py-2 text-foreground">
|
||||
{workspace.streamingAssistantText}
|
||||
<span className="ml-0.5 inline-block h-4 w-1.5 animate-pulse bg-foreground/60" />
|
||||
</div>
|
||||
|
|
@ -328,7 +328,7 @@ export function Terminal({ className }: TerminalProps) {
|
|||
type="text"
|
||||
value={input}
|
||||
onChange={(event) => setInput(event.target.value)}
|
||||
className="flex-1 bg-transparent text-foreground outline-none placeholder:text-muted-foreground/50 disabled:cursor-not-allowed disabled:text-muted-foreground"
|
||||
className="flex-1 bg-transparent text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:text-muted-foreground"
|
||||
placeholder={inputModePlaceholder(inputMode, workspace)}
|
||||
disabled={isInputDisabled}
|
||||
data-testid="terminal-command-input"
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ function statusIcon(status: "complete" | "active" | "pending" | "done") {
|
|||
case "active":
|
||||
return <Play className="h-4 w-4 shrink-0 text-info" />
|
||||
case "pending":
|
||||
return <Circle className="h-4 w-4 shrink-0 text-muted-foreground/30" />
|
||||
return <Circle className="h-4 w-4 shrink-0 text-muted-foreground/50" />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,9 +121,9 @@ function SectionLabel({ children }: { children: React.ReactNode }) {
|
|||
/** Large empty state with icon */
|
||||
function EmptyState({ message, icon: Icon = AlertCircle }: { message: string; icon?: React.ComponentType<{ className?: string }> }) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-dashed border-border/60 py-16 text-center">
|
||||
<div className="rounded-full border border-border/60 bg-muted/40 p-4">
|
||||
<Icon className="h-6 w-6 text-muted-foreground/50" />
|
||||
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-dashed border-border py-16 text-center">
|
||||
<div className="rounded-full border border-border bg-muted/50 p-4">
|
||||
<Icon className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-muted-foreground">{message}</p>
|
||||
</div>
|
||||
|
|
@ -182,7 +182,7 @@ function ProgressBar({
|
|||
const pct = max > 0 ? Math.max(1, (value / max) * 100) : 0
|
||||
const barColor = { sky: "bg-info", emerald: "bg-success", amber: "bg-warning" }[color]
|
||||
return (
|
||||
<div className="h-2 w-full overflow-hidden rounded-full bg-muted/60">
|
||||
<div className="h-2 w-full overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
className={cn("h-full rounded-full transition-all duration-700", barColor, animated && "animate-pulse")}
|
||||
style={{ width: `${pct}%` }}
|
||||
|
|
@ -261,7 +261,7 @@ function ProgressTab({ data }: { data: VisualizerData }) {
|
|||
{data.milestones.map((ms) => (
|
||||
<div key={ms.id} className="overflow-hidden rounded-xl border border-border bg-card">
|
||||
{/* Milestone header */}
|
||||
<div className="flex items-center justify-between border-b border-border bg-muted/20 px-5 py-4">
|
||||
<div className="flex items-center justify-between border-b border-border bg-muted/50 px-5 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
{statusIcon(ms.status)}
|
||||
<span className="font-mono text-xs font-semibold text-muted-foreground">{ms.id}</span>
|
||||
|
|
@ -324,7 +324,7 @@ function ProgressTab({ data }: { data: VisualizerData }) {
|
|||
"flex items-center gap-2.5 rounded-lg px-3 py-2 transition-colors",
|
||||
task.active
|
||||
? "bg-info/8 border border-info/20"
|
||||
: "hover:bg-muted/40",
|
||||
: "hover:bg-muted/50",
|
||||
)}
|
||||
>
|
||||
{taskStatusIcon(task)}
|
||||
|
|
@ -332,7 +332,7 @@ function ProgressTab({ data }: { data: VisualizerData }) {
|
|||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
task.done && "text-muted-foreground/50 line-through",
|
||||
task.done && "text-muted-foreground line-through",
|
||||
task.active && "font-semibold text-info",
|
||||
!task.done && !task.active && "text-muted-foreground",
|
||||
)}
|
||||
|
|
@ -383,8 +383,8 @@ function DepsTab({ data }: { data: VisualizerData }) {
|
|||
<span className="rounded-lg border border-info/25 bg-info/10 px-3 py-1.5 font-mono text-sm font-semibold text-info">
|
||||
{dep}
|
||||
</span>
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground/50" />
|
||||
<span className="rounded-lg border border-border bg-muted/40 px-3 py-1.5 font-mono text-sm font-medium">
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="rounded-lg border border-border bg-muted/50 px-3 py-1.5 font-mono text-sm font-medium">
|
||||
{ms.id}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">{ms.title}</span>
|
||||
|
|
@ -415,8 +415,8 @@ function DepsTab({ data }: { data: VisualizerData }) {
|
|||
<span className="rounded-lg border border-info/25 bg-info/10 px-3 py-1.5 font-mono text-sm font-semibold text-info">
|
||||
{dep}
|
||||
</span>
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground/50" />
|
||||
<span className="rounded-lg border border-border bg-muted/40 px-3 py-1.5 font-mono text-sm font-medium">
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="rounded-lg border border-border bg-muted/50 px-3 py-1.5 font-mono text-sm font-medium">
|
||||
{sl.id}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">{sl.title}</span>
|
||||
|
|
@ -450,7 +450,7 @@ function DepsTab({ data }: { data: VisualizerData }) {
|
|||
{id}
|
||||
</span>
|
||||
{i < cp.milestonePath.length - 1 && (
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground/50" />
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
|
|
@ -467,7 +467,7 @@ function DepsTab({ data }: { data: VisualizerData }) {
|
|||
{data.milestones
|
||||
.filter((m) => !cp.milestonePath.includes(m.id))
|
||||
.map((m) => (
|
||||
<div key={m.id} className="flex items-center gap-4 rounded-lg bg-muted/30 px-4 py-2.5">
|
||||
<div key={m.id} className="flex items-center gap-4 rounded-lg bg-muted/50 px-4 py-2.5">
|
||||
<span className="w-16 font-mono text-sm font-semibold">{m.id}</span>
|
||||
<span className="text-sm text-muted-foreground">{m.title}</span>
|
||||
<span className="ml-auto font-mono text-xs text-muted-foreground">
|
||||
|
|
@ -492,7 +492,7 @@ function DepsTab({ data }: { data: VisualizerData }) {
|
|||
{id}
|
||||
</span>
|
||||
{i < cp.slicePath.length - 1 && (
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground/50" />
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
|
|
@ -530,7 +530,7 @@ function DepsTab({ data }: { data: VisualizerData }) {
|
|||
{Object.entries(cp.sliceSlack).map(([id, slack]) => (
|
||||
<span
|
||||
key={id}
|
||||
className="rounded-lg border border-border bg-muted/40 px-3 py-1.5 font-mono text-xs text-muted-foreground"
|
||||
className="rounded-lg border border-border bg-muted/50 px-3 py-1.5 font-mono text-xs text-muted-foreground"
|
||||
>
|
||||
{id}: {slack}
|
||||
</span>
|
||||
|
|
@ -639,7 +639,7 @@ function MetricsTab({ data }: { data: VisualizerData }) {
|
|||
</thead>
|
||||
<tbody className="divide-y divide-border/50">
|
||||
{data.bySlice.map((sl) => (
|
||||
<tr key={sl.sliceId} className="transition-colors hover:bg-muted/30">
|
||||
<tr key={sl.sliceId} className="transition-colors hover:bg-muted/50">
|
||||
<td className="py-3 pr-5 font-mono text-xs font-semibold">{sl.sliceId}</td>
|
||||
<td className="py-3 pr-5 text-right tabular-nums text-muted-foreground">{sl.units}</td>
|
||||
<td className="py-3 pr-5 text-right tabular-nums font-medium">{formatCost(sl.cost)}</td>
|
||||
|
|
@ -732,7 +732,7 @@ function TimelineTab({ data }: { data: VisualizerData }) {
|
|||
<div className="space-y-4">
|
||||
<div className="overflow-hidden rounded-xl border border-border bg-card">
|
||||
{/* Header */}
|
||||
<div className="border-b border-border bg-muted/20 px-6 py-4">
|
||||
<div className="border-b border-border bg-muted/50 px-6 py-4">
|
||||
<SectionLabel>Execution Timeline</SectionLabel>
|
||||
<p className="mt-1.5 text-xs text-muted-foreground">
|
||||
Showing {recent.length} of {data.units.length} units — most recent first
|
||||
|
|
@ -758,7 +758,7 @@ function TimelineTab({ data }: { data: VisualizerData }) {
|
|||
return (
|
||||
<div
|
||||
key={`${unit.id}-${unit.startedAt}-${i}`}
|
||||
className="grid grid-cols-[3.5rem_1.5rem_5rem_8rem_1fr_4.5rem_5rem] items-center gap-3 px-6 py-3.5 transition-colors hover:bg-muted/30"
|
||||
className="grid grid-cols-[3.5rem_1.5rem_5rem_8rem_1fr_4.5rem_5rem] items-center gap-3 px-6 py-3.5 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<span className="font-mono text-xs text-muted-foreground">
|
||||
{formatTime(unit.startedAt)}
|
||||
|
|
@ -816,7 +816,7 @@ function AgentTab({ data }: { data: VisualizerData }) {
|
|||
"relative flex h-10 w-10 items-center justify-center rounded-full",
|
||||
activity.active
|
||||
? "bg-success/15"
|
||||
: "bg-muted/60",
|
||||
: "bg-muted",
|
||||
)}>
|
||||
{activity.active && (
|
||||
<div className="absolute inset-0 animate-ping rounded-full bg-success/20" />
|
||||
|
|
@ -886,7 +886,7 @@ function AgentTab({ data }: { data: VisualizerData }) {
|
|||
{/* Recent units */}
|
||||
{data.units.filter((u) => u.finishedAt > 0).length > 0 && (
|
||||
<div className="overflow-hidden rounded-xl border border-border bg-card">
|
||||
<div className="border-b border-border bg-muted/20 px-6 py-4">
|
||||
<div className="border-b border-border bg-muted/50 px-6 py-4">
|
||||
<SectionLabel>Recent Completed Units</SectionLabel>
|
||||
</div>
|
||||
<div className="divide-y divide-border/40">
|
||||
|
|
@ -895,7 +895,7 @@ function AgentTab({ data }: { data: VisualizerData }) {
|
|||
.slice(-5)
|
||||
.reverse()
|
||||
.map((u, i) => (
|
||||
<div key={`${u.id}-${i}`} className="flex items-center gap-4 px-6 py-4 transition-colors hover:bg-muted/30">
|
||||
<div key={`${u.id}-${i}`} className="flex items-center gap-4 px-6 py-4 transition-colors hover:bg-muted/50">
|
||||
<span className="w-12 font-mono text-xs text-muted-foreground">{formatTime(u.startedAt)}</span>
|
||||
<CheckCircle2 className="h-4 w-4 shrink-0 text-success" />
|
||||
<span className="flex-1 truncate text-sm font-medium">{u.type}</span>
|
||||
|
|
@ -927,7 +927,7 @@ function ChangesTab({ data }: { data: VisualizerData }) {
|
|||
{sorted.map((entry, i) => (
|
||||
<div key={`${entry.milestoneId}-${entry.sliceId}-${i}`} className="overflow-hidden rounded-xl border border-border bg-card">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-border bg-muted/20 px-6 py-4">
|
||||
<div className="flex items-center justify-between border-b border-border bg-muted/50 px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle2 className="h-4 w-4 shrink-0 text-success" />
|
||||
<span className="font-mono text-xs font-bold text-success">
|
||||
|
|
@ -956,11 +956,11 @@ function ChangesTab({ data }: { data: VisualizerData }) {
|
|||
</p>
|
||||
<div className="space-y-2">
|
||||
{entry.filesModified.map((f, fi) => (
|
||||
<div key={fi} className="flex items-start gap-3 rounded-lg bg-muted/30 px-4 py-2.5">
|
||||
<div key={fi} className="flex items-start gap-3 rounded-lg bg-muted/50 px-4 py-2.5">
|
||||
<CheckCircle2 className="mt-0.5 h-3.5 w-3.5 shrink-0 text-success/70" />
|
||||
<span className="font-mono text-xs font-medium text-muted-foreground">{f.path}</span>
|
||||
{f.description && (
|
||||
<span className="ml-1 text-xs text-muted-foreground/60">— {f.description}</span>
|
||||
<span className="ml-1 text-xs text-muted-foreground">— {f.description}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -1069,7 +1069,7 @@ function ExportTab({ data }: { data: VisualizerData }) {
|
|||
<div className="mt-7 grid gap-4 sm:grid-cols-2">
|
||||
<button
|
||||
onClick={handleMarkdown}
|
||||
className="group flex items-center gap-5 rounded-xl border border-border bg-muted/20 p-5 text-left transition-all hover:border-info/40 hover:bg-info/5"
|
||||
className="group flex items-center gap-5 rounded-xl border border-border bg-muted/50 p-5 text-left transition-all hover:border-info/40 hover:bg-info/5"
|
||||
>
|
||||
<div className="rounded-xl border border-info/20 bg-info/10 p-4 transition-colors group-hover:bg-info/15">
|
||||
<FileText className="h-6 w-6 text-info" />
|
||||
|
|
@ -1083,7 +1083,7 @@ function ExportTab({ data }: { data: VisualizerData }) {
|
|||
|
||||
<button
|
||||
onClick={handleJSON}
|
||||
className="group flex items-center gap-5 rounded-xl border border-border bg-muted/20 p-5 text-left transition-all hover:border-success/40 hover:bg-success/5"
|
||||
className="group flex items-center gap-5 rounded-xl border border-border bg-muted/50 p-5 text-left transition-all hover:border-success/40 hover:bg-success/5"
|
||||
>
|
||||
<div className="rounded-xl border border-success/20 bg-success/10 p-4 transition-colors group-hover:bg-success/15">
|
||||
<FileJson className="h-6 w-6 text-success" />
|
||||
|
|
@ -1147,10 +1147,10 @@ function VisualizerTabList() {
|
|||
/>
|
||||
|
||||
{/* Hover background */}
|
||||
<span className="absolute inset-x-0 inset-y-1.5 rounded-lg bg-muted/0 transition-colors duration-150 group-hover:bg-muted/60 group-data-[state=active]:bg-transparent" />
|
||||
<span className="absolute inset-x-0 inset-y-1.5 rounded-lg bg-muted/0 transition-colors duration-150 group-hover:bg-muted group-data-[state=active]:bg-transparent" />
|
||||
|
||||
{/* Icon */}
|
||||
<Icon className="relative h-4 w-4 shrink-0 transition-colors duration-150 text-muted-foreground/70 group-hover:text-foreground/70 group-data-[state=active]:text-foreground" />
|
||||
<Icon className="relative h-4 w-4 shrink-0 transition-colors duration-150 text-muted-foreground group-hover:text-muted-foreground group-data-[state=active]:text-foreground" />
|
||||
|
||||
{/* Label */}
|
||||
<span className="relative">{label}</span>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {
|
|||
className={cn(
|
||||
'bg-muted w-fit text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none',
|
||||
"[&_svg:not([class*='size-'])]:size-3",
|
||||
'[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10',
|
||||
'[[data-slot=tooltip-content]_&]:bg-background/50 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ function SidebarGroupLabel({
|
|||
data-slot="sidebar-group-label"
|
||||
data-sidebar="group-label"
|
||||
className={cn(
|
||||
'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
'text-sidebar-foreground ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
||||
className,
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ const ToastClose = React.forwardRef<
|
|||
<ToastPrimitives.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-destructive group-[.destructive]:hover:text-destructive group-[.destructive]:focus:ring-destructive group-[.destructive]:focus:ring-offset-destructive',
|
||||
'absolute right-2 top-2 rounded-md p-1 text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-destructive group-[.destructive]:hover:text-destructive group-[.destructive]:focus:ring-destructive group-[.destructive]:focus:ring-offset-destructive',
|
||||
className,
|
||||
)}
|
||||
toast-close=""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue