diff --git a/packages/ui/src/components/instance/instance-shell2.tsx b/packages/ui/src/components/instance/instance-shell2.tsx index 7675b586..68f76bc8 100644 --- a/packages/ui/src/components/instance/instance-shell2.tsx +++ b/packages/ui/src/components/instance/instance-shell2.tsx @@ -15,7 +15,6 @@ import { Accordion } from "@kobalte/core" import { ChevronDown, TerminalSquare, Trash2, XOctagon } from "lucide-solid" import AppBar from "@suid/material/AppBar" import Box from "@suid/material/Box" -import Divider from "@suid/material/Divider" import Drawer from "@suid/material/Drawer" import IconButton from "@suid/material/IconButton" import Toolbar from "@suid/material/Toolbar" @@ -916,7 +915,7 @@ const InstanceShell2: Component = (props) => { showFooter={false} /> - +
{(activeSession) => ( <> diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx index dbd22428..39638e1b 100644 --- a/packages/ui/src/components/markdown.tsx +++ b/packages/ui/src/components/markdown.tsx @@ -1,5 +1,5 @@ import { createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js" -import { renderMarkdown, onLanguagesLoaded, decodeHtmlEntities } from "../lib/markdown" +import { renderMarkdown, onLanguagesLoaded, decodeHtmlEntities, setMarkdownTheme } from "../lib/markdown" import { useGlobalCache } from "../lib/hooks/use-global-cache" import type { TextPart, RenderCache } from "../types/message" import { getLogger } from "../lib/logger" @@ -72,6 +72,9 @@ export function Markdown(props: MarkdownProps) { createEffect(async () => { const { part, text, themeKey, highlightEnabled, version } = resolved() + // Ensure the markdown highlighter theme matches the active UI theme. + setMarkdownTheme(themeKey === "dark") + latestRequestedText = text const cacheMatches = (cache: RenderCache | undefined) => { @@ -171,6 +174,8 @@ export function Markdown(props: MarkdownProps) { const { part, text, themeKey, version } = resolved() + setMarkdownTheme(themeKey === "dark") + if (latestRequestedText !== text) { return } diff --git a/packages/ui/src/lib/markdown.ts b/packages/ui/src/lib/markdown.ts index a8287d15..400d7476 100644 --- a/packages/ui/src/lib/markdown.ts +++ b/packages/ui/src/lib/markdown.ts @@ -329,6 +329,10 @@ export async function initMarkdown(isDark: boolean) { isInitialized = true } +export function setMarkdownTheme(isDark: boolean) { + currentTheme = isDark ? "dark" : "light" +} + export function isMarkdownReady(): boolean { return isInitialized && highlighter !== null } diff --git a/packages/ui/src/styles/messaging/message-base.css b/packages/ui/src/styles/messaging/message-base.css index eccab659..5f329d19 100644 --- a/packages/ui/src/styles/messaging/message-base.css +++ b/packages/ui/src/styles/messaging/message-base.css @@ -175,7 +175,8 @@ .message-reasoning { @apply my-2 border rounded; - border-color: var(--border-base); + --reasoning-border-color: color-mix(in oklab, var(--border-base) 62%, var(--text-primary)); + border-color: var(--reasoning-border-color); background-color: var(--surface-secondary); color: inherit; } @@ -286,6 +287,7 @@ } .message-reasoning-card { + --reasoning-border-color: color-mix(in oklab, var(--border-base) 62%, var(--text-primary)); background-color: var(--message-assistant-bg); border-left: 4px solid var(--message-assistant-border); margin-top: 0; @@ -339,7 +341,7 @@ justify-content: center; height: 1.5rem; padding: 0 0.75rem; - border: 1px solid var(--border-base); + border: 1px solid var(--reasoning-border-color, var(--border-base)); border-radius: 0.375rem; background-color: transparent; color: var(--text-muted); @@ -381,6 +383,7 @@ @apply flex flex-col; margin: 0; padding: 0.75rem; + border: 1px solid var(--reasoning-border-color, var(--border-base)); max-height: 30rem; overflow-y: auto; scrollbar-width: thin; @@ -397,4 +400,3 @@ white-space: pre-wrap; margin: 0; } - diff --git a/packages/ui/src/styles/messaging/message-timeline.css b/packages/ui/src/styles/messaging/message-timeline.css index 9fe5bc65..391bbc2e 100644 --- a/packages/ui/src/styles/messaging/message-timeline.css +++ b/packages/ui/src/styles/messaging/message-timeline.css @@ -103,7 +103,7 @@ } .message-timeline-segment-active { - border-color: transparent; + border-color: color-mix(in oklab, var(--timeline-segment-active-bg) 92%, var(--timeline-segment-active-text)); background-color: var(--timeline-segment-active-bg); color: var(--timeline-segment-active-text); font-weight: 700; @@ -182,7 +182,7 @@ .message-timeline-segment-active { background-color: var(--timeline-segment-active-bg) !important; - border-color: transparent !important; + border-color: color-mix(in oklab, var(--timeline-segment-active-bg) 92%, var(--timeline-segment-active-text)) !important; color: var(--timeline-segment-active-text) !important; font-weight: 700; box-shadow: var(--timeline-segment-active-ring); diff --git a/packages/ui/src/styles/messaging/prompt-input.css b/packages/ui/src/styles/messaging/prompt-input.css index b5507031..82f051e3 100644 --- a/packages/ui/src/styles/messaging/prompt-input.css +++ b/packages/ui/src/styles/messaging/prompt-input.css @@ -38,7 +38,8 @@ @apply w-full pl-3 pr-10 pt-2.5 border text-sm resize-none outline-none transition-colors; font-family: inherit; background-color: var(--surface-base); - color: inherit; + color: var(--text-primary); + caret-color: var(--text-primary); border-color: var(--border-base); line-height: var(--line-height-normal); border-radius: 0; diff --git a/packages/ui/src/styles/messaging/tool-call.css b/packages/ui/src/styles/messaging/tool-call.css index f39f470a..38bfd1f4 100644 --- a/packages/ui/src/styles/messaging/tool-call.css +++ b/packages/ui/src/styles/messaging/tool-call.css @@ -21,7 +21,7 @@ .tool-call-header-button { background-color: transparent; - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color, var(--border-base)); color: var(--text-secondary); padding: 0.15rem 0.75rem; border-radius: 0.375rem; @@ -58,7 +58,7 @@ font-family: var(--font-family-mono); color: inherit; background-color: var(--surface-secondary); - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color, var(--border-base)); padding: 2px 6px; border-radius: 3px; font-size: 13px; @@ -66,7 +66,9 @@ .tool-call { @apply border overflow-hidden; - border-color: var(--border-base); + /* Tool-call output borders need more contrast in light mode. */ + --tool-call-border-color: color-mix(in oklab, var(--border-base) 62%, var(--text-primary)); + border-color: var(--tool-call-border-color); color: inherit; --tool-call-line-unit: 1.4em; --tool-call-lines-compact: 15; @@ -86,13 +88,14 @@ font-family: var(--font-family-mono); font-size: 13px; border-radius: 0; + color: var(--text-primary); } .tool-call-header::before { content: "▶"; font-size: 11px; margin-right: 0.35rem; - color: var(--text-muted); + color: var(--text-secondary); } .tool-call-header[aria-expanded="true"]::before { @@ -115,6 +118,7 @@ .tool-call-summary { @apply flex-1 text-left inline-flex items-center gap-2; + color: var(--text-primary); } .tool-call-summary::before { @@ -130,6 +134,8 @@ margin-right: 0.35rem; } +/* ToolState uses status="completed"; keep "success" as a legacy alias. */ +.tool-call-status-completed, .tool-call-status-success { border-left: 3px solid var(--status-success); } @@ -157,7 +163,7 @@ .tool-call-preview { @apply p-2 flex flex-col gap-1.5; background-color: var(--surface-code); - border-top: 1px solid var(--border-base); + border-top: 1px solid var(--tool-call-border-color); } .tool-call-preview-label { @@ -186,7 +192,8 @@ .tool-call-markdown { background-color: var(--surface-code); - border: none; + /* Keep a visible frame around the scroll viewport (not the content). */ + border: 1px solid var(--tool-call-border-color); border-radius: 0; padding: 0; font-size: var(--font-size-xs); @@ -199,6 +206,16 @@ position: relative; } +/* Inner code blocks should not own the frame border; the scroll container does. */ +.tool-call-markdown .markdown-code-block { + border: none; +} + +/* Avoid double borders when ANSI output uses .tool-call-content inside tool-call-markdown. */ +.tool-call-markdown .tool-call-content { + border: none; +} + .tool-call-markdown-large { max-height: var(--tool-call-max-height-large, calc(48 * 1.4em)); } @@ -216,7 +233,7 @@ .tool-call-diff-toolbar { @apply flex items-center justify-between gap-3 px-3 py-2; background-color: var(--surface-secondary); - border-bottom: 1px solid var(--border-base); + border-bottom: 1px solid var(--tool-call-border-color); position: sticky; top: 0; z-index: 2; @@ -242,7 +259,7 @@ .tool-call-diff-mode-button { @apply border text-xs font-semibold px-3 py-1 rounded transition-all duration-150; - border-color: var(--border-base); + border-color: var(--tool-call-border-color, var(--border-base)); background-color: transparent; color: var(--text-secondary); } @@ -303,7 +320,7 @@ font-size: 12px; padding: 2px 6px; border-radius: 0.375rem; - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color, var(--border-base)); background-color: var(--surface-code); } @@ -312,7 +329,7 @@ font-size: 13px; color: var(--text-primary); background-color: var(--surface-code); - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color, var(--border-base)); border-radius: 0.5rem; padding: 0.5rem 0.75rem; word-break: break-word; @@ -395,12 +412,6 @@ .tool-call-markdown .markdown-code-block { margin: 0; border-radius: 0; -} - - -.tool-call-markdown .markdown-code-block { - margin: 0; - border: none; background-color: transparent; } @@ -408,7 +419,25 @@ position: sticky; top: 0; z-index: auto; - box-shadow: 0 1px 0 var(--border-base); + background-color: color-mix(in oklab, var(--surface-secondary) 70%, var(--text-primary)); + border-bottom: 1px solid var(--tool-call-border-color); + box-shadow: none; +} + +/* Tool output header (language + copy) needs stronger contrast in light mode. */ +.tool-call-markdown .code-block-language { + color: var(--text-primary); +} + +.tool-call-markdown .code-block-copy { + border-color: var(--tool-call-border-color); + color: var(--text-primary); +} + +/* Plain (non-highlighted) tool output must remain readable in light mode. */ +.tool-call-markdown .markdown-code-block pre:not(.shiki), +.tool-call-markdown .markdown-code-block pre:not(.shiki) code { + color: var(--text-primary); } .tool-call-markdown .markdown-code-block pre { @@ -458,12 +487,12 @@ flex-direction: column; gap: var(--space-xs); padding: var(--space-sm) var(--space-md); - border-top: 1px solid var(--border-base); + border-top: 1px solid var(--tool-call-border-color); background-color: var(--surface-base); } .tool-call-diagnostics-wrapper { - border-top: 1px solid var(--border-base); + border-top: 1px solid var(--tool-call-border-color); background-color: var(--surface-base); margin-top: var(--space-md); } @@ -515,7 +544,7 @@ width: 1.25rem; height: 1.25rem; border-radius: var(--radius-sm); - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color, var(--border-base)); font-size: 12px; } @@ -616,7 +645,7 @@ margin: 0; padding: 8px; background-color: var(--surface-code); - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color); border-radius: 0px; overflow-x: auto; max-height: var(--tool-call-max-height-compact, calc(25 * 1.4em)); @@ -659,7 +688,7 @@ .tool-call-action-button { @apply border text-xs font-semibold px-3 py-1 rounded transition-colors h-8 flex items-center; - border-color: var(--border-base); + border-color: var(--tool-call-border-color, var(--border-base)); color: var(--text-secondary); background-color: transparent; } @@ -681,7 +710,7 @@ .tool-call-content { background-color: var(--surface-code); - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color); border-radius: 0; padding: 8px 12px; font-family: var(--font-family-mono); diff --git a/packages/ui/src/styles/messaging/tool-call/task.css b/packages/ui/src/styles/messaging/tool-call/task.css index 2756cca2..a98d6500 100644 --- a/packages/ui/src/styles/messaging/tool-call/task.css +++ b/packages/ui/src/styles/messaging/tool-call/task.css @@ -6,7 +6,7 @@ } .tool-call-task-section { - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color, var(--border-base)); overflow: hidden; background-color: transparent; border-radius: 0; @@ -19,7 +19,7 @@ gap: 0.75rem; padding: 0.5rem; background-color: var(--surface-secondary); - border-bottom: 1px solid var(--border-base); + border-bottom: 1px solid var(--tool-call-border-color, var(--border-base)); font-family: var(--font-family-mono); font-size: 13px; color: inherit; @@ -81,7 +81,7 @@ align-items: center; gap: 0.4rem; padding: 0.35rem 0.5rem 0.35rem 0.75rem; - border-left: 2px solid var(--border-base); + border-left: 2px solid var(--tool-call-border-color, var(--border-base)); font-size: var(--font-size-sm); font-family: var(--font-family-mono); line-height: 1.35; diff --git a/packages/ui/src/styles/messaging/tool-call/todo.css b/packages/ui/src/styles/messaging/tool-call/todo.css index bc25d0e0..50071667 100644 --- a/packages/ui/src/styles/messaging/tool-call/todo.css +++ b/packages/ui/src/styles/messaging/tool-call/todo.css @@ -16,7 +16,7 @@ .tool-call-todo-item { @apply flex items-start gap-3; - border: 1px solid var(--border-base); + border: 1px solid var(--tool-call-border-color, var(--border-base)); border-radius: 0; padding: 10px 12px; background-color: var(--surface-secondary); @@ -40,7 +40,7 @@ width: 1.1rem; height: 1.1rem; border-radius: 9999px; - border: 2px solid var(--border-base); + border: 2px solid var(--tool-call-border-color, var(--border-base)); display: inline-flex; align-items: center; justify-content: center; diff --git a/packages/ui/src/styles/panels/tabs.css b/packages/ui/src/styles/panels/tabs.css index 98bae37e..50f3cb8a 100644 --- a/packages/ui/src/styles/panels/tabs.css +++ b/packages/ui/src/styles/panels/tabs.css @@ -87,6 +87,23 @@ ring-offset-color: inherit; } +/* Instance tabs: session-status dots should be lighter/softer than list/status pills. */ +.tab-base .status-indicator.session-status.session-working { + --session-status-dot: color-mix(in oklab, var(--session-status-working-fg) 55%, var(--surface-base)); +} + +.tab-base .status-indicator.session-status.session-compacting { + --session-status-dot: color-mix(in oklab, var(--session-status-compacting-fg) 55%, var(--surface-base)); +} + +.tab-base .status-indicator.session-status.session-idle { + --session-status-dot: color-mix(in oklab, var(--session-status-idle-fg) 55%, var(--surface-base)); +} + +.tab-base .status-indicator.session-status.session-permission { + --session-status-dot: color-mix(in oklab, var(--session-status-permission-fg) 55%, var(--surface-base)); +} + .new-tab-button { @apply inline-flex items-center justify-center w-8 h-8 rounded-md transition-colors; background-color: var(--new-tab-bg); diff --git a/packages/ui/src/styles/tokens.css b/packages/ui/src/styles/tokens.css index 4f8763df..19edbf6a 100644 --- a/packages/ui/src/styles/tokens.css +++ b/packages/ui/src/styles/tokens.css @@ -57,6 +57,7 @@ --attachment-chip-ring: rgba(0, 102, 255, 0.1); --badge-neutral-bg: rgba(0, 102, 255, 0.05); --badge-neutral-text: #0066ff; + --badge-success-bg: rgba(76, 175, 80, 0.12); --status-ready-fg: #16a34a; --status-ready-bg: rgba(34, 197, 94, 0.1); --status-starting-fg: #ca8a04; @@ -94,9 +95,10 @@ --status-success-ring: color-mix(in oklab, var(--status-success) 45%, transparent); --border-critical: var(--status-error); - --timeline-segment-active-bg: #0f5b44; - --timeline-segment-active-text: #ffffff; - --timeline-segment-active-ring: inset 0 0 0 1px rgba(0, 0, 0, 0.22); + /* Message timeline active segment (light theme should be a light tint). */ + --timeline-segment-active-bg: #b7e6d6; + --timeline-segment-active-text: #032f23; + --timeline-segment-active-ring: inset 0 0 0 1px rgba(3, 47, 35, 0.28); --button-danger-bg: color-mix(in oklab, var(--status-error) 85%, var(--surface-base)); --button-danger-hover-bg: color-mix(in oklab, var(--status-error) 90%, var(--surface-base)); @@ -223,6 +225,7 @@ --attachment-chip-ring: rgba(0, 128, 255, 0.2); --badge-neutral-bg: rgba(0, 128, 255, 0.15); --badge-neutral-text: #0080ff; + --badge-success-bg: rgba(76, 175, 80, 0.22); --status-ready-fg: #22c55e; --status-ready-bg: rgba(34, 197, 94, 0.2); --status-starting-fg: #facc15; @@ -385,6 +388,7 @@ --attachment-chip-ring: rgba(0, 128, 255, 0.2); --badge-neutral-bg: rgba(0, 128, 255, 0.15); --badge-neutral-text: #0080ff; + --badge-success-bg: rgba(76, 175, 80, 0.22); --status-ready-fg: #22c55e; --status-ready-bg: rgba(34, 197, 94, 0.2); --status-starting-fg: #facc15;