From 568fc5ca04d3e457d0b773e09928a4fdbbb03659 Mon Sep 17 00:00:00 2001 From: Sina Matian <89218912+time-attack@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:48:16 -0400 Subject: [PATCH 01/51] web-ui: adopt the Beautiful UI design tokens, Inter and JetBrains Mono Defines the Beautiful UI palette (page/canvas/surface/ink/line/field, blue/green/ orange/red with tints, tooltip colours), radii, easing and shared keyframes in shell.css and maps the existing shadcn tokens onto it so every surface recolours consistently in light and dark. Self-hosts the Inter and JetBrains Mono latin subsets. Splits net-new component CSS into src/styles/*.css, each imported from shell.css and covered by the css-vars guardrail. Imported custom themes now emit the same tokens so nothing bleeds through. --- plugins/web-ui/index.html | 4 +- plugins/web-ui/public/fonts/inter-latin.woff2 | Bin 0 -> 48432 bytes .../public/fonts/jetbrains-mono-latin.woff2 | Bin 0 -> 40480 bytes plugins/web-ui/src/shell.css | 217 +++++++++++++++--- plugins/web-ui/src/styles/approval.css | 0 plugins/web-ui/src/styles/chat.css | 0 plugins/web-ui/src/styles/code.css | 0 plugins/web-ui/src/styles/composer.css | 0 plugins/web-ui/src/styles/filter.css | 0 plugins/web-ui/src/styles/loading.css | 0 plugins/web-ui/src/styles/recommendation.css | 0 plugins/web-ui/src/styles/search.css | 0 plugins/web-ui/src/styles/selection.css | 0 plugins/web-ui/src/styles/settings.css | 0 plugins/web-ui/src/styles/tables.css | 0 plugins/web-ui/src/styles/thinking.css | 0 plugins/web-ui/src/styles/tool-chips.css | 0 plugins/web-ui/src/styles/topbar.css | 0 plugins/web-ui/src/theme-import.ts | 28 +++ plugins/web-ui/test/css-vars.test.ts | 9 +- 20 files changed, 228 insertions(+), 30 deletions(-) create mode 100644 plugins/web-ui/public/fonts/inter-latin.woff2 create mode 100644 plugins/web-ui/public/fonts/jetbrains-mono-latin.woff2 create mode 100644 plugins/web-ui/src/styles/approval.css create mode 100644 plugins/web-ui/src/styles/chat.css create mode 100644 plugins/web-ui/src/styles/code.css create mode 100644 plugins/web-ui/src/styles/composer.css create mode 100644 plugins/web-ui/src/styles/filter.css create mode 100644 plugins/web-ui/src/styles/loading.css create mode 100644 plugins/web-ui/src/styles/recommendation.css create mode 100644 plugins/web-ui/src/styles/search.css create mode 100644 plugins/web-ui/src/styles/selection.css create mode 100644 plugins/web-ui/src/styles/settings.css create mode 100644 plugins/web-ui/src/styles/tables.css create mode 100644 plugins/web-ui/src/styles/thinking.css create mode 100644 plugins/web-ui/src/styles/tool-chips.css create mode 100644 plugins/web-ui/src/styles/topbar.css diff --git a/plugins/web-ui/index.html b/plugins/web-ui/index.html index a73eb6f5b..0bfc356ac 100644 --- a/plugins/web-ui/index.html +++ b/plugins/web-ui/index.html @@ -10,8 +10,8 @@ - - + + diff --git a/plugins/web-ui/public/fonts/inter-latin.woff2 b/plugins/web-ui/public/fonts/inter-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..91dc3e8529921cabc74fb1dacf5253260b9c8cca GIT binary patch literal 48432 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 Tp~gD%=1~4;zp%;UV+#NP+I>lo literal 0 HcmV?d00001 diff --git a/plugins/web-ui/public/fonts/jetbrains-mono-latin.woff2 b/plugins/web-ui/public/fonts/jetbrains-mono-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..c3f0666ea8d891e46b1710f50c998eaa9b701fd5 GIT binary patch literal 40480 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 literal 0 HcmV?d00001 diff --git a/plugins/web-ui/src/shell.css b/plugins/web-ui/src/shell.css index 33c5f77e9..9aad6458f 100644 --- a/plugins/web-ui/src/shell.css +++ b/plugins/web-ui/src/shell.css @@ -1,50 +1,149 @@ @import "@earendil-works/pi-web-ui/app.css"; +@import "./styles/loading.css"; +@import "./styles/thinking.css"; +@import "./styles/chat.css"; +@import "./styles/approval.css"; +@import "./styles/tool-chips.css"; +@import "./styles/composer.css"; +@import "./styles/recommendation.css"; +@import "./styles/tables.css"; +@import "./styles/filter.css"; +@import "./styles/search.css"; +@import "./styles/code.css"; +@import "./styles/settings.css"; +@import "./styles/selection.css"; +@import "./styles/topbar.css"; + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url("/fonts/inter-latin.woff2") format("woff2"); +} +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-weight: 100 800; + font-display: swap; + src: url("/fonts/jetbrains-mono-latin.woff2") format("woff2"); +} :root { - --app-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --page: oklch(98.5% 0.001 286.376); + --canvas: oklch(96.1% 0.002 247.84); + --surface: oklch(100% 0 0); + --inset: oklch(97.9% 0.002 247.839); + --hover: oklch(97% 0.002 247.839); + --hover-2: oklch(93.3% 0.003 247.86); + --ink: oklch(24.7% 0.006 258.361); + --ink-2: oklch(50.6% 0.01 264.477); + --ink-3: oklch(69.5% 0.009 264.505); + --line: oklch(94.6% 0.003 264.542); + --line-strong: oklch(91.2% 0.005 258.326); + --line-soft: oklch(96.6% 0.002 264.542); + --field: oklch(96.1% 0.001 286.375); + --stripe: oklch(40.5% 0 0 / 0.075); + --stripe-bg: oklch(97% 0 0); + --blue: oklch(62.6% 0.205 254.947); + --blue-ink: oklch(55.6% 0.187 255.617); + --blue-tint: oklch(96% 0.019 252.878); + --green: oklch(60.3% 0.155 150.883); + --green-tint: oklch(95.8% 0.017 159.118); + --orange: oklch(68.9% 0.179 49.902); + --orange-tint: oklch(96.4% 0.021 67.581); + --red: oklch(62.1% 0.192 23.042); + --red-tint: oklch(95.6% 0.017 17.462); + --tooltip-bg: oklch(27.2% 0.008 264.435); + --tooltip-fg: oklch(97.6% 0.002 247.839); + --tooltip-muted: oklch(73.1% 0.008 260.731); + --tooltip-border: oklch(35.6% 0.007 264.474); + + --background: var(--page); + --foreground: var(--ink); + --card: var(--surface); + --card-foreground: var(--ink); + --popover: var(--surface); + --popover-foreground: var(--ink); + --primary: var(--ink); + --primary-foreground: var(--page); + --secondary: var(--field); + --secondary-foreground: var(--ink); + --muted: var(--inset); + --muted-foreground: var(--ink-2); + --accent: var(--hover-2); + --accent-foreground: var(--ink); + --destructive: var(--red); + --destructive-foreground: oklch(100% 0 0); + --border: var(--line); + --input: var(--line-strong); + --ring: var(--blue); + --sidebar: var(--canvas); + --sidebar-foreground: var(--ink); + --sidebar-primary: var(--ink); + --sidebar-primary-foreground: var(--page); + --sidebar-accent: var(--hover-2); + --sidebar-accent-foreground: var(--ink); + --sidebar-border: var(--line); + + --app-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace; + --default-font-family: var(--app-font); + --default-mono-font-family: var(--font-mono); --app-serif: ui-serif, Georgia, "Times New Roman", Times, serif; --brand-font: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif; --sidebar-w: 280px; --content-w: 820px; + --radius-chip: 6px; --radius-sm: 8px; --radius-md: 10px; - --radius-lg: 16px; + --radius-lg: 14px; + --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1); --chat-pad: 16px; --composer-inset: 13px; - --chat-surface-shadow: 0 2px 5px rgb(0 0 0 / 0.08), 0 8px 24px rgb(0 0 0 / 0.1); + --chat-surface-shadow: 0 0 0 1px var(--line-strong), 0 1px 2px rgb(0 0 0 / 0.06); /* Height reserved at the bottom of every chat message for its own hover footer; it doubles as the rhythm between message blocks. */ --meta-lane: 18px; --brand-accent: #4f46e5; --dev-accent: #8a5a00; --brand-mark: none; - --cta: oklch(0.27 0.062 250); - --cta-hover: oklch(0.33 0.07 250); - --cta-foreground: oklch(0.99 0 0); - --success: #1a7f37; - --warning: #a66700; + --cta: var(--blue); + --cta-hover: var(--blue-ink); + --cta-foreground: oklch(100% 0 0); + --success: var(--green); + --warning: var(--orange); } .dark { - --cta: oklch(0.42 0.085 250); - --cta-hover: oklch(0.48 0.09 250); - --cta-foreground: oklch(0.99 0 0); - --success: #3fb950; - --warning: #d29922; - - --background: oklch(0.165 0.022 250); - --foreground: oklch(0.975 0.005 250); - --card: oklch(0.205 0.024 250); - --popover: oklch(0.255 0.026 250); - --secondary: oklch(0.245 0.024 250); - --muted: oklch(0.245 0.024 250); - --muted-foreground: oklch(0.72 0.021 250); - --accent: oklch(0.36 0.03 250); - --border: oklch(0.275 0.024 250); - --input: oklch(0.325 0.026 250); - --sidebar: oklch(0.205 0.024 250); - --sidebar-accent: oklch(0.245 0.024 250); - --sidebar-border: oklch(0.275 0.024 250); + --page: oklch(20.9% 0.004 264.477); + --canvas: oklch(23.1% 0.004 264.487); + --surface: oklch(26% 0.006 271.191); + --inset: oklch(24.3% 0.004 264.492); + --hover: oklch(28.9% 0.006 271.22); + --hover-2: oklch(31.8% 0.007 274.747); + --ink: oklch(96.4% 0.002 247.839); + --ink-2: oklch(73.1% 0.008 260.731); + --ink-3: oklch(54.1% 0.01 264.484); + --line: oklch(30.8% 0.006 258.354); + --line-strong: oklch(35.6% 0.007 264.474); + --line-soft: oklch(27.8% 0.006 258.354); + --field: oklch(29.3% 0.006 271.223); + --stripe: oklch(100% 0 0 / 0.055); + --stripe-bg: oklch(22.6% 0.004 264.485); + --blue: oklch(68% 0.173 253.301); + --blue-ink: oklch(78.8% 0.113 248.33); + --blue-tint: oklch(68% 0.173 253.301 / 0.16); + --green: oklch(70.5% 0.154 153.814); + --green-tint: oklch(70.5% 0.154 153.814 / 0.14); + --orange: oklch(74.6% 0.156 55.642); + --orange-tint: oklch(74.6% 0.156 55.642 / 0.14); + --red: oklch(66.6% 0.18 21.433); + --red-tint: oklch(66.6% 0.18 21.433 / 0.14); + --tooltip-bg: oklch(18.2% 0.004 264.459); + --tooltip-fg: oklch(96.4% 0.002 247.839); + --tooltip-muted: oklch(73.1% 0.008 260.731); + --tooltip-border: oklch(30.8% 0.006 258.354); } html, @@ -63,6 +162,70 @@ body { text-rendering: optimizeLegibility; } +@keyframes pixel-on { + 0%, + 100% { + opacity: 0.15; + } + 18%, + 42% { + opacity: 1; + } + 62% { + opacity: 0.15; + } +} +@keyframes shimmer-text { + 0% { + background-position: 150%; + } + 100% { + background-position: -50%; + } +} +@keyframes fade-up { + 0% { + opacity: 0; + transform: translateY(8px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} +@keyframes fade-in { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +@keyframes pop-in { + 0% { + opacity: 0; + transform: scale(0.95); + } + 100% { + opacity: 1; + transform: scale(1); + } +} +@keyframes caret-blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} +@keyframes spin { + to { + transform: rotate(1turn); + } +} + .layout { --surface-safe-top: env(safe-area-inset-top); --rail-w: 50px; diff --git a/plugins/web-ui/src/styles/approval.css b/plugins/web-ui/src/styles/approval.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/chat.css b/plugins/web-ui/src/styles/chat.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/code.css b/plugins/web-ui/src/styles/code.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/composer.css b/plugins/web-ui/src/styles/composer.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/filter.css b/plugins/web-ui/src/styles/filter.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/loading.css b/plugins/web-ui/src/styles/loading.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/recommendation.css b/plugins/web-ui/src/styles/recommendation.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/search.css b/plugins/web-ui/src/styles/search.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/selection.css b/plugins/web-ui/src/styles/selection.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/settings.css b/plugins/web-ui/src/styles/settings.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/tables.css b/plugins/web-ui/src/styles/tables.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/thinking.css b/plugins/web-ui/src/styles/thinking.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/tool-chips.css b/plugins/web-ui/src/styles/tool-chips.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/styles/topbar.css b/plugins/web-ui/src/styles/topbar.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/web-ui/src/theme-import.ts b/plugins/web-ui/src/theme-import.ts index 1851c0c23..c1a609d56 100644 --- a/plugins/web-ui/src/theme-import.ts +++ b/plugins/web-ui/src/theme-import.ts @@ -364,6 +364,34 @@ export function themeTokens(palette: Palette): ThemeTokens { "--cta-foreground": textOn(action), "--success": readable(green ?? action), "--warning": readable(ansiOrBright(ANSI.yellow) ?? action), + "--page": bg, + "--canvas": step(0.03, 0.02), + "--surface": step(0.05, 0.025), + "--inset": step(0.03, 0.015), + "--hover": step(0.07, 0.03), + "--hover-2": step(0.11, 0.06), + "--ink": fg, + "--ink-2": mutedForeground, + "--ink-3": mix(fg, bg, 0.55), + "--line": border, + "--line-strong": step(0.17, 0.14), + "--line-soft": step(0.08, 0.06), + "--field": step(0.09, 0.04), + "--stripe": mix(bg, fg, 0.075), + "--stripe-bg": step(0.02, 0.015), + "--blue": action, + "--blue-ink": link, + "--blue-tint": mix(bg, action, 0.15), + "--green": readable(green ?? action), + "--green-tint": mix(bg, green ?? action, 0.14), + "--orange": readable(ansiOrBright(ANSI.yellow) ?? action), + "--orange-tint": mix(bg, ansiOrBright(ANSI.yellow) ?? action, 0.14), + "--red": destructive, + "--red-tint": mix(bg, destructive, 0.14), + "--tooltip-bg": dark ? mix(bg, { r: 0, g: 0, b: 0 }, 0.25) : mix(fg, bg, 0.1), + "--tooltip-fg": dark ? fg : bg, + "--tooltip-muted": dark ? mix(fg, bg, 0.3) : mix(bg, fg, 0.3), + "--tooltip-border": dark ? border : mix(fg, bg, 0.25), "--selection": palette.selection ?? mix(bg, action, 0.35), "--working-dot": readable(ansiOrBright(ANSI.blue) ?? action), "--awaiting-dot": readable(ansiOrBright(ANSI.yellow) ?? action), diff --git a/plugins/web-ui/test/css-vars.test.ts b/plugins/web-ui/test/css-vars.test.ts index 72e47b6ce..8395d4bbd 100644 --- a/plugins/web-ui/test/css-vars.test.ts +++ b/plugins/web-ui/test/css-vars.test.ts @@ -2,7 +2,14 @@ import assert from "node:assert/strict"; import { readFileSync, readdirSync } from "node:fs"; import test from "node:test"; -const shellCss = readFileSync(new URL("../src/shell.css", import.meta.url), "utf8"); +const stylesDir = new URL("../src/styles/", import.meta.url); +const shellCss = [ + readFileSync(new URL("../src/shell.css", import.meta.url), "utf8"), + ...readdirSync(stylesDir) + .filter((f) => f.endsWith(".css")) + .sort() + .map((f) => readFileSync(new URL(f, stylesDir), "utf8")), +].join("\n"); const themeCss = readFileSync( new URL("../node_modules/@earendil-works/pi-web-ui/dist/app.css", import.meta.url), "utf8", From 619dcbb572f3c8d68ee5f4c8b1444ed0386b2883 Mon Sep 17 00:00:00 2001 From: Sina Matian <89218912+time-attack@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:58:47 -0400 Subject: [PATCH 02/51] =?UTF-8?q?web-ui:=20chat=20=E2=80=94=20Beautiful=20?= =?UTF-8?q?UI=20message=20rows,=20action=20lane,=20and=20word=20fade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web-ui/src/chat.ts | 8 +- plugins/web-ui/src/shell.css | 131 +++++++++++------------------ plugins/web-ui/src/styles/chat.css | 19 +++++ 3 files changed, 73 insertions(+), 85 deletions(-) diff --git a/plugins/web-ui/src/chat.ts b/plugins/web-ui/src/chat.ts index c21e6a185..7add72e8e 100644 --- a/plugins/web-ui/src/chat.ts +++ b/plugins/web-ui/src/chat.ts @@ -1563,7 +1563,7 @@ export function createChatSurface( aria-label="Copy message" @click=${(e: Event) => void copyMessage(text, e.currentTarget as HTMLButtonElement)} > - ${icon(Copy, 13)} + ${icon(Copy, 15)} ` : nothing } @@ -1576,7 +1576,7 @@ export function createChatSurface( aria-label="Fork conversation from here" @click=${() => void forkFromMessage(index)} > - ${icon(GitFork, 13)} + ${icon(GitFork, 15)} ` : nothing } @@ -1638,11 +1638,11 @@ export function createChatSurface( return; } btn.classList.add("copied"); - btn.replaceChildren(icon(Check, 13)); + btn.replaceChildren(icon(Check, 15)); setTimeout(() => { if (!btn.isConnected) return; btn.classList.remove("copied"); - btn.replaceChildren(icon(Copy, 13)); + btn.replaceChildren(icon(Copy, 15)); }, 1200); } diff --git a/plugins/web-ui/src/shell.css b/plugins/web-ui/src/shell.css index 9aad6458f..cbe3a3fea 100644 --- a/plugins/web-ui/src/shell.css +++ b/plugins/web-ui/src/shell.css @@ -102,9 +102,7 @@ --chat-pad: 16px; --composer-inset: 13px; --chat-surface-shadow: 0 0 0 1px var(--line-strong), 0 1px 2px rgb(0 0 0 / 0.06); - /* Height reserved at the bottom of every chat message for its own hover footer; it doubles - as the rhythm between message blocks. */ - --meta-lane: 18px; + --meta-lane: 24px; --brand-accent: #4f46e5; --dev-accent: #8a5a00; --brand-mark: none; @@ -1639,9 +1637,6 @@ a.chat-row-open { margin: 0 auto; display: flex; flex-direction: column; - - /* The space between blocks lives in each row's reserved meta lane (see .message-row), so the - stack adds none of its own — a hover footer can never overhang the block below. */ gap: 0; } .empty-stack { @@ -1682,10 +1677,6 @@ a.chat-row-open { display: flex; min-width: 0; position: relative; - /* A lane at the bottom of every row that belongs to that row's own hover footer. It doubles - as the rhythm between blocks, so reserving it costs almost no height — and because the - footer paints inside its own row, it can never overlay the block below (most visibly a - collapsed 'Worked for Ns' header). Revealing the footer still changes no layout. */ padding-bottom: var(--meta-lane); } .user-row { @@ -1723,33 +1714,26 @@ a.chat-row-open { } } -.assistant-row + .user-row { - margin-top: 10px; -} - -.steer-label { - font-size: 11px; - color: var(--muted-foreground); - margin: 2px 4px 3px 0; +.steer-label, +.speaker-label, +.revision-badge { + font-size: 12px; + line-height: 1.3; + color: var(--ink-3); user-select: none; } -.steered-row .user-bubble { - border: 1px dashed var(--border); +.steer-label, +.speaker-label { + margin: 0 4px 4px 0; } - .speaker-label { - font-size: 11px; - font-weight: 600; - color: var(--muted-foreground); - margin: 2px 4px 3px 0; - user-select: none; + font-weight: 500; +} +.steered-row .user-bubble { + border: 1px dashed var(--line-strong); } - .revision-badge { - font-size: 11px; - color: var(--muted-foreground); margin-left: 6px; - user-select: none; } .deleted-bubble { @@ -1764,7 +1748,8 @@ a.chat-row-open { } .system-note { font-size: 12px; - color: var(--muted-foreground); + line-height: 1.3; + color: var(--ink-3); text-align: center; padding: 2px 10px; } @@ -1772,11 +1757,6 @@ a.chat-row-open { font-style: italic; } -/* The hover footer (timestamp + copy/fork) sits in its row's reserved bottom lane: out of flow, - so revealing it never reflows, but inside the row's own box, so it never paints over the next - block and needs no opaque backing. The lane is part of the row, so no hover bridge is needed - to reach it either. pointer-events (not visibility) gates interaction, so the buttons stay - keyboard-focusable while hidden — that is what lets :focus-within reveal the footer. */ .message-meta { position: absolute; bottom: 0; @@ -1784,31 +1764,27 @@ a.chat-row-open { z-index: 4; display: flex; align-items: center; - gap: 6px; + gap: 2px; height: var(--meta-lane); - padding: 0 2px; - border-radius: 6px; - color: var(--muted-foreground); - font-size: 11px; + padding: 0; + color: var(--ink-3); + font-size: 12px; opacity: 0; pointer-events: none; - transition: opacity 0.12s ease; + transition: opacity 120ms ease; } .user-row .message-meta { left: auto; right: 0; } .assistant-body .message-meta { - margin-left: -2px; + margin-left: -4px; } .message-row:hover .message-meta, .message-meta:focus-within { opacity: 1; pointer-events: auto; } - -/* Hoverless (touch) devices can't reveal the footer, and a tap can't focus a control that - ignores pointer events — so there it simply stays visible in its lane. */ @media (hover: none) { .message-meta { opacity: 1; @@ -1816,8 +1792,10 @@ a.chat-row-open { } } .message-time { + padding: 0 4px; white-space: nowrap; - letter-spacing: 0.01em; + font-family: var(--font-mono); + font-size: 12px; font-variant-numeric: tabular-nums; } .msg-copy { @@ -1828,20 +1806,24 @@ a.chat-row-open { justify-content: center; padding: 0; border: 0; - border-radius: 6px; + border-radius: var(--radius-chip); background: transparent; - color: var(--muted-foreground); + color: var(--ink-3); cursor: pointer; + transition: + background-color 100ms ease, + color 100ms ease; } .msg-copy:hover { - background: var(--secondary); - color: var(--foreground); + background: var(--hover-2); + color: var(--ink-2); } .msg-copy.copied { - color: var(--foreground); + color: var(--green); } @media (prefers-reduced-motion: reduce) { - .message-meta { + .message-meta, + .msg-copy { transition: none; } } @@ -1850,9 +1832,9 @@ a.chat-row-open { } .assistant-body { min-width: 0; - color: var(--foreground); - font-size: 15px; - line-height: 1.65; + color: var(--ink); + font-size: 14px; + line-height: 1.55; } .assistant-body markdown-block, .user-bubble markdown-block { @@ -1862,17 +1844,7 @@ a.chat-row-open { position: relative; } .streaming-text .tok-in { - animation: tok-in 0.42s ease-out both; -} -@keyframes tok-in { - from { - opacity: 0; - filter: blur(1.5px); - } - to { - opacity: 1; - filter: blur(0); - } + animation: fade-in 240ms var(--ease-out-strong) both; } @media (prefers-reduced-motion: reduce) { .streaming-text .tok-in { @@ -1887,9 +1859,6 @@ a.chat-row-open { .user-bubble markdown-block > *:last-child { margin-bottom: 0; } -/* A streaming reply renders as two stacked blocks (committed prefix + live tail). Restore the - paragraph gap at their seam that the edge-trim rules above would otherwise remove; `revert` - is the UA-default margin, which is what an uncut paragraph boundary gets. */ .assistant-body .streaming-text markdown-block:not(:first-of-type) > *:first-child { margin-top: revert; } @@ -1898,15 +1867,15 @@ a.chat-row-open { } .message-bubble { min-width: 0; - max-width: min(72%, 620px); + max-width: min(75%, 620px); padding: 10px 14px; - border-radius: 18px; - font-size: 15px; - line-height: 1.55; + border-radius: var(--radius-md); + font-size: 14px; + line-height: 1.5; } .user-bubble { - background: color-mix(in srgb, var(--secondary) 76%, var(--background)); - color: var(--foreground); + background: var(--field); + color: var(--ink); overflow-wrap: anywhere; } .message-stack .user-row:not(:has(~ .user-row)) .user-bubble { @@ -2864,11 +2833,11 @@ a.chat-row-open { .assistant-body markdown-block :not(pre) > code, .user-bubble markdown-block :not(pre) > code { - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 0.92em; + font-family: var(--font-mono); + font-size: 0.9em; padding: 1px 5px; - border-radius: 5px; - background: var(--secondary); + border-radius: 4px; + background: var(--field); } .assistant-body markdown-block pre, .user-bubble markdown-block pre { @@ -7392,7 +7361,7 @@ h3.ambient-field-label { padding: var(--chat-scroll-pad-top) var(--chat-pad) var(--chat-scroll-pad-bottom); } [data-density="compact"] .message-row { - --meta-lane: 16px; + --meta-lane: 20px; } [data-density="compact"] .message-stack { gap: 0; diff --git a/plugins/web-ui/src/styles/chat.css b/plugins/web-ui/src/styles/chat.css index e69de29bb..2aaf1ef00 100644 --- a/plugins/web-ui/src/styles/chat.css +++ b/plugins/web-ui/src/styles/chat.css @@ -0,0 +1,19 @@ +.assistant-body markdown-block :is(h1, h2, h3, h4, h5, h6) { + margin: 18px 0 6px; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.3; + color: var(--ink); +} +.assistant-body markdown-block h1 { + font-size: 18px; +} +.assistant-body markdown-block h2 { + font-size: 16px; +} +.assistant-body markdown-block h3 { + font-size: 15px; +} +.assistant-body markdown-block :is(h4, h5, h6) { + font-size: 14px; +} From 18665e91c59bc87947d4635ae51aa96a8f9e82c8 Mon Sep 17 00:00:00 2001 From: Sina Matian <89218912+time-attack@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:02:02 -0400 Subject: [PATCH 03/51] =?UTF-8?q?web-ui:=20loading=20=E2=80=94=20pixel-gri?= =?UTF-8?q?d=20loader,=20shimmer=20label,=20elapsed=20time=20on=20the=20li?= =?UTF-8?q?ve=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web-ui/src/chat.ts | 56 +++--- plugins/web-ui/src/connectors.ts | 4 +- plugins/web-ui/src/shell.css | 164 ++++++++---------- plugins/web-ui/src/styles/loading.css | 30 ++++ plugins/web-ui/src/ui.ts | 12 ++ plugins/web-ui/src/work-duration.ts | 26 ++- plugins/web-ui/test/keychain-flow.test.ts | 2 +- plugins/web-ui/test/pixel-loader.test.ts | 39 +++++ .../test/work-duration-reconciliation.test.ts | 2 +- 9 files changed, 210 insertions(+), 125 deletions(-) create mode 100644 plugins/web-ui/test/pixel-loader.test.ts diff --git a/plugins/web-ui/src/chat.ts b/plugins/web-ui/src/chat.ts index 7add72e8e..37c8c8f14 100644 --- a/plugins/web-ui/src/chat.ts +++ b/plugins/web-ui/src/chat.ts @@ -6,6 +6,7 @@ import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai"; import type { UserMessageWithAttachments } from "@earendil-works/pi-web-ui"; import { markdown } from "./message-markdown"; import { html, nothing, render, type TemplateResult } from "lit"; +import { ref } from "lit/directives/ref.js"; import { Activity, Ban, @@ -98,7 +99,7 @@ import { harnessSupportsEffort, harnessSupportsFastMode, } from "./model-options"; -import { browserRenderableImage, chipBadge, formatBytes, icon, relTime, waveLoader } from "./ui"; +import { browserRenderableImage, chipBadge, formatBytes, icon, pixelLoader, relTime } from "./ui"; import { appState, renderSidebarTop, switchView, syncUrlFromState } from "./shell"; import { contextsState, scopeTitle } from "./contexts"; import { openProjectPage, scopeToolCount, sessionTopbarTpl, setScopedSession } from "./session-scope"; @@ -127,7 +128,7 @@ import { newChatDraftKey, saveDraft, storedDraft } from "./drafts"; import { createForkOriginController, forkOriginView } from "./fork-origin"; import { base64ToBytes } from "./paste-text"; import { tip } from "./tooltip"; -import { workSeconds, workedLabel } from "./work-duration"; +import { elapsedLabel, workSeconds, workStartedAt, workedLabel } from "./work-duration"; import { decorateTextCodeBlocks, normalizePlainTextFences } from "./text-code"; import { createTranscriptViewport } from "./transcript-viewport"; @@ -136,6 +137,11 @@ installMarkdownSanitizer(); const detachedAgents = new WeakSet(); const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); +interface LiveWorkSummary { + label: string; + detail: string; + since: number | null; +} interface SettledRowKey { index: number; activity: WorkBlock["activity"] | undefined; @@ -268,6 +274,7 @@ export function createChatSurface( let ctaThreadRef: string | null | undefined; let ctaText = CHAT_CTAS[0]!; let workTicker: ReturnType | null = null; + let liveElapsedTimer: ReturnType | null = null; let revealedTailLen = 0; let liveWorkExpanded = false; @@ -893,7 +900,7 @@ export function createChatSurface( host.className = "custom-chat"; render( html`
-
${waveLoader()}
+
${pixelLoader("Loading")}
`, host, ); @@ -1800,9 +1807,22 @@ export function createChatSurface( } function typingRow(): TemplateResult { - return html`
- ${waveLoader({ width: 14.1, label: "Thinking" })}${sheenLabel("Thinking", true)} -
`; + return html`
${pixelLoader()}${sheenLabel("Thinking", true)}
`; + } + + function syncLiveElapsed(el?: Element): void { + if (liveElapsedTimer) clearInterval(liveElapsedTimer); + liveElapsedTimer = null; + if (!(el instanceof HTMLElement)) return; + const tick = (): void => { + if (!el.isConnected) { + syncLiveElapsed(); + return; + } + el.textContent = elapsedLabel(Date.now() - Number(el.dataset.since)); + }; + tick(); + liveElapsedTimer = setInterval(tick, reduceMotion.matches ? 1000 : 100); } function syncWorkTicker(): void { @@ -2125,6 +2145,7 @@ export function createChatSurface( const work = chatState.liveWork ?? { status: "thinking", activity: [] }; if (work.status !== "thinking" && work.status !== "working") return nothing; const summary = liveWorkSummary(work); + const since = summary ? summary.since : workStartedAt(work); const expandable = Boolean(summary?.detail); const expanded = expandable && liveWorkExpanded; let title = ""; @@ -2139,10 +2160,11 @@ export function createChatSurface( ${tip(title)} @click=${toggleLiveWorkExpanded} > - ${summary ? html`${icon(summary.icon, 15)}` : nothing} + ${pixelLoader()} ${summary ? summary.label : sheenLabel(`Thinking${usedToolsSuffix(work)}`, true)} + ${since ? html`` : nothing} ${summary?.detail ? html`${summary.detail}` : nothing} ${expandable ? html`${icon(ChevronRight, 14)}` : nothing} @@ -2155,16 +2177,16 @@ export function createChatSurface( drawActiveChat(); } - function liveWorkSummary(work: WorkBlock): { icon: IconNode; label: string; detail: string } | null { + function liveWorkSummary(work: WorkBlock): LiveWorkSummary | null { if (work.stale) { const active = activeToolRow(work); const call = (active?.call?.payload ?? {}) as ToolPayload; const tool = call.tool ?? ""; const verb = active ? (TOOL_META[tool] ?? UNKNOWN_TOOL).active : null; return { - icon: RefreshCw, label: verb ? `${verb} interrupted, resuming…` : "Interrupted, resuming…", detail: active ? toolDetail(tool, call, (active.result?.payload ?? {}) as ToolPayload) : "", + since: active?.call?.createdAt || workStartedAt(work), }; } const active = activeToolRow(work); @@ -2180,24 +2202,18 @@ export function createChatSurface( return null; } - function activeToolSummary(row: ToolRowModel, work: WorkBlock): { icon: IconNode; label: string; detail: string } { + function activeToolSummary(row: ToolRowModel, work: WorkBlock): LiveWorkSummary { const call = (row.call?.payload ?? {}) as ToolPayload; const result = (row.result?.payload ?? {}) as ToolPayload; const tool = call.tool ?? result.tool ?? "unknown"; const meta = TOOL_META[tool] ?? UNKNOWN_TOOL; - const secs = elapsedSeconds(row.call?.createdAt) || workSeconds(work); return { - icon: meta.icon, - label: secs > 0 ? `${meta.active} for ${secs}s` : meta.active, + label: meta.active, detail: toolDetail(tool, call, result), + since: row.call?.createdAt || workStartedAt(work), }; } - function elapsedSeconds(startedAt: number | null | undefined): number { - if (typeof startedAt !== "number" || startedAt <= 0) return 0; - return Math.max(0, Math.round((Date.now() - startedAt) / 1000)); - } - function usedToolsSuffix(work: WorkBlock): string { const n = work.activity.filter((a) => a.type === "tool_call").length; return n > 0 ? ` (used ${n} tool${n === 1 ? "" : "s"})` : ""; @@ -2310,9 +2326,7 @@ export function createChatSurface( } function sheenLabel(label: string, active: boolean): TemplateResult { - return html`${label}`; + return html`${label}`; } function renderTimelineItem(item: TimelineItem, work: WorkBlock): TemplateResult { diff --git a/plugins/web-ui/src/connectors.ts b/plugins/web-ui/src/connectors.ts index 94a10fcca..90367212c 100644 --- a/plugins/web-ui/src/connectors.ts +++ b/plugins/web-ui/src/connectors.ts @@ -2,7 +2,7 @@ import { html, render, type TemplateResult } from "lit"; import { KeyRound, Link, Plug } from "lucide"; import { api } from "./core-bridge"; import { errMessage } from "../../chassis/src/errors"; -import { icon } from "./ui"; +import { icon, pixelLoader } from "./ui"; import { appState, replacePanePreservingFocus } from "./shell"; import { scopedSession, scopedViewTopbar } from "./session-scope"; import { focusDialogCancel, restoreDialogFocus, trapDialogFocus } from "./dialog-focus"; @@ -413,7 +413,7 @@ export function noteConnectorResult(provider: string, status: string): void { } function loadingPlaceholder(label: string): TemplateResult { - return html`
${label}
`; + return html`
${pixelLoader()}${label}
`; } function drawConnectors(): void { diff --git a/plugins/web-ui/src/shell.css b/plugins/web-ui/src/shell.css index cbe3a3fea..772a79b63 100644 --- a/plugins/web-ui/src/shell.css +++ b/plugins/web-ui/src/shell.css @@ -2115,16 +2115,13 @@ a.chat-row-open { .thinking-placeholder { display: flex; align-items: center; - gap: 6px; + gap: 10px; min-height: 24px; - color: var(--muted-foreground); + color: var(--ink-2); font-size: 13px; + font-weight: 500; line-height: 1.65; } -.thinking-placeholder .wl-swell { - flex: 0 0 auto; - color: inherit; -} .work-said { color: var(--foreground); font-size: 15px; @@ -2140,49 +2137,22 @@ a.chat-row-open { line-height: 1.6; } .sheen-label { - position: relative; display: inline-block; color: inherit; } -.sheen-label.thinking-sheen::after { - content: attr(data-sheen); - position: absolute; - inset: 0; - pointer-events: none; +.sheen-label.thinking-sheen { color: transparent; - background-image: linear-gradient( - 100deg, - transparent 0%, - transparent 38%, - color-mix(in srgb, var(--background) 66%, var(--muted-foreground)) 50%, - transparent 62%, - transparent 100% - ); - background-size: 46px 100%; - background-position: -56px 0; - background-repeat: no-repeat; + background-image: linear-gradient(90deg, var(--ink-3) 35%, var(--ink) 50%, var(--ink-3) 65%); + background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; - font: inherit; - letter-spacing: inherit; - animation: thinking-sheen 2.55s linear infinite; - will-change: background-position; -} -@keyframes thinking-sheen { - 0% { - background-position: -56px 0; - } - 46% { - background-position: calc(100% + 56px) 0; - } - 100% { - background-position: calc(100% + 56px) 0; - } + animation: shimmer-text 1.4s linear infinite; } @media (prefers-reduced-motion: reduce) { - .sheen-label.thinking-sheen::after { + .sheen-label.thinking-sheen { animation: none; - opacity: 0; + background: none; + color: inherit; } } @@ -2601,7 +2571,7 @@ a.chat-row-open { .bg-activity { width: min(var(--content-w), calc(100% - 32px)); margin: 0 auto 8px; - color: var(--muted-foreground); + color: var(--ink-2); font-size: 12.5px; } .composer-wrap > .bg-activity { @@ -2614,7 +2584,7 @@ a.chat-row-open { border-radius: inherit; border-bottom-left-radius: 0; border-bottom-right-radius: 0; - border-bottom: 1px solid var(--border); + border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--secondary) 40%, var(--background)); } .readonly-chat .bg-activity { @@ -2625,19 +2595,26 @@ a.chat-row-open { -webkit-appearance: none; display: flex; align-items: center; - gap: 6px; + gap: 8px; width: 100%; margin: 0; - padding: 2px 0; + padding: 3px 6px; border: none; + border-radius: var(--radius-sm); background: none; color: inherit; font: inherit; + font-size: 13px; + font-weight: 500; text-align: left; cursor: pointer; + transition: + background-color 0.1s ease, + color 0.1s ease; } .bg-activity-strip:hover { - color: var(--foreground); + background: var(--hover-2); + color: var(--ink); } .bg-activity-label { min-width: 0; @@ -2648,6 +2625,7 @@ a.chat-row-open { .bg-activity-toggle { flex: 0 0 auto; display: inline-flex; + color: var(--ink-3); transition: transform 0.12s ease; } .bg-activity.expanded .bg-activity-toggle { @@ -2656,18 +2634,20 @@ a.chat-row-open { .bg-panel { margin-top: 6px; - border: 1px solid var(--border); - border-radius: 10px; + border: 1px solid var(--line); + border-radius: var(--radius-md); padding: 4px; display: flex; flex-direction: column; gap: 2px; max-height: 42vh; overflow-y: auto; + background: var(--surface); } .bg-panel-note { padding: 6px 8px; - font-size: 12px; + font-size: 12.5px; + color: var(--ink-3); } .bg-row-head { text-decoration: none; @@ -2675,22 +2655,25 @@ a.chat-row-open { -webkit-appearance: none; display: flex; align-items: center; - gap: 7px; + gap: 8px; width: 100%; margin: 0; padding: 5px 8px; border: none; - border-radius: 7px; + border-radius: var(--radius-sm); background: none; color: inherit; font: inherit; - font-size: 12px; + font-size: 12.5px; text-align: left; cursor: pointer; + transition: + background-color 0.1s ease, + color 0.1s ease; } .bg-row-head:hover { - background: color-mix(in srgb, var(--muted-foreground) 10%, transparent); - color: var(--foreground); + background: var(--hover-2); + color: var(--ink); } .bg-row-head.static { cursor: default; @@ -2704,27 +2687,29 @@ a.chat-row-open { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 11.5px; + font-family: var(--font-mono); + font-size: 12px; } .bg-row.watch .bg-row-cmd { font-family: inherit; - font-size: 12px; + font-size: 12.5px; } .bg-row-meta { flex: 0 0 auto; margin-left: auto; white-space: nowrap; - font-size: 11px; - opacity: 0.85; + color: var(--ink-3); + font-size: 12px; + font-variant-numeric: tabular-nums; } .bg-row-meta code { - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 10.5px; + font-family: var(--font-mono); + font-size: 11px; } .bg-row-toggle { flex: 0 0 auto; display: inline-flex; + color: var(--ink-3); transition: transform 0.12s ease; } .bg-row.open .bg-row-toggle { @@ -2733,10 +2718,12 @@ a.chat-row-open { .bg-row-output { margin: 2px 8px 6px; padding: 8px 10px; - border-radius: 7px; - background: color-mix(in srgb, var(--muted-foreground) 8%, transparent); - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 11px; + border: 1px solid var(--line); + border-radius: var(--radius-sm); + background: var(--inset); + color: var(--ink-2); + font-family: var(--font-mono); + font-size: 12px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; @@ -2748,31 +2735,36 @@ a.chat-row-open { width: 100%; min-width: 0; margin: 8px 0 0; - color: var(--muted-foreground); + color: var(--ink-2); } .live-work-line { appearance: none; -webkit-appearance: none; - margin: 0; - padding: 2px 0; + margin: 0 -8px; + padding: 3px 8px; border: none; background: none; color: inherit; font: inherit; text-align: left; - width: 100%; + width: calc(100% + 16px); min-height: 24px; display: flex; align-items: center; - gap: 8px; + gap: 10px; min-width: 0; - font-size: 15px; + font-size: 13px; + font-weight: 500; line-height: 1.4; cursor: pointer; - border-radius: 6px; + border-radius: var(--radius-sm); + transition: + background-color 0.1s ease, + color 0.1s ease; } .live-work-line:not(.static):hover { - color: var(--foreground); + background: var(--hover-2); + color: var(--ink); } .live-work-line.static, .live-work-line:disabled { @@ -2782,10 +2774,6 @@ a.chat-row-open { outline: 2px solid color-mix(in srgb, var(--brand-accent) 60%, transparent); outline-offset: 2px; } -.live-work-line .tool-icon { - flex: 0 0 auto; - color: color-mix(in srgb, var(--muted-foreground) 86%, var(--background)); -} .live-work-label { flex: 0 1 auto; min-width: 0; @@ -2799,14 +2787,16 @@ a.chat-row-open { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - color: color-mix(in srgb, var(--muted-foreground) 72%, var(--background)); + color: var(--ink-3); + font-size: 12.5px; + font-weight: 400; } .live-work-toggle { flex: 0 0 auto; margin-left: auto; display: inline-flex; align-items: center; - color: color-mix(in srgb, var(--muted-foreground) 70%, var(--background)); + color: var(--ink-3); transition: transform 0.15s ease; } @@ -4166,21 +4156,9 @@ a.chat-row-open { align-items: center; gap: 10px; padding: 24px; - color: var(--muted-foreground); - font-size: 14px; -} -.kc-loading .spinner { - width: 16px; - height: 16px; - border-radius: 50%; - border: 2px solid color-mix(in srgb, var(--muted-foreground) 30%, transparent); - border-top-color: var(--muted-foreground); - animation: chat-spin 0.7s linear infinite; -} -@media (prefers-reduced-motion: reduce) { - .kc-loading .spinner { - animation: none; - } + color: var(--ink-2); + font-size: 13px; + font-weight: 500; } .kc-empty { display: flex; diff --git a/plugins/web-ui/src/styles/loading.css b/plugins/web-ui/src/styles/loading.css index e69de29bb..11fad174e 100644 --- a/plugins/web-ui/src/styles/loading.css +++ b/plugins/web-ui/src/styles/loading.css @@ -0,0 +1,30 @@ +.pixel-loader { + display: grid; + grid-template-columns: repeat(3, 4px); + gap: 1.5px; + flex: 0 0 auto; + color: var(--ink); +} +.pixel-loader > i { + display: block; + width: 4px; + height: 4px; + border-radius: 1px; + background: currentColor; + opacity: 0.15; + animation: pixel-on 650ms ease-in-out infinite; +} +.live-work-elapsed { + flex: 0 0 auto; + color: var(--ink-3); + font-family: var(--font-mono); + font-size: 12px; + font-weight: 400; + font-variant-numeric: tabular-nums; +} +@media (prefers-reduced-motion: reduce) { + .pixel-loader > i { + animation: none; + opacity: 0.5; + } +} diff --git a/plugins/web-ui/src/ui.ts b/plugins/web-ui/src/ui.ts index 8d7459d01..12f467781 100644 --- a/plugins/web-ui/src/ui.ts +++ b/plugins/web-ui/src/ui.ts @@ -36,6 +36,18 @@ export function waveLoader( `; } +const PIXEL_DELAYS = [90, 180, 270, 0, 90, 180, 90, 180, 270]; + +export function pixelLoader(label?: string): TemplateResult { + return html`${PIXEL_DELAYS.map((ms) => html``)}`; +} + export function workingWave(): TemplateResult { return waveLoader({ width: 13.6, diff --git a/plugins/web-ui/src/work-duration.ts b/plugins/web-ui/src/work-duration.ts index ca2a55163..d069b6a3a 100644 --- a/plugins/web-ui/src/work-duration.ts +++ b/plugins/web-ui/src/work-duration.ts @@ -1,18 +1,30 @@ import type { WorkBlock } from "./core-bridge"; +function activityTimes(work: WorkBlock): number[] { + return work.activity.map((a) => a.createdAt).filter((t): t is number => typeof t === "number" && t > 0); +} + +export function workStartedAt(work: WorkBlock): number | null { + if (work.startedAt != null) return work.startedAt; + const times = activityTimes(work); + return times.length ? Math.min(...times) : null; +} + export function workSeconds(work: WorkBlock): number { - const times = work.activity.map((a) => a.createdAt).filter((t) => typeof t === "number" && t > 0); - const start = work.startedAt ?? (times.length ? Math.min(...times) : null); + const start = workStartedAt(work); if (start == null) return 0; const live = work.status === "thinking" || work.status === "working"; - let end = work.finishedAt; - if (end == null) { - if (live) end = Date.now(); - else end = times.length ? Math.max(...times, start) : start; - } + const end = work.finishedAt ?? (live ? Date.now() : Math.max(start, ...activityTimes(work))); return Math.max(0, Math.round((end - start) / 1000)); } export function workedLabel(prefix: string, secs: number): string { return secs > 0 ? `${prefix} for ${secs}s` : prefix; } + +export function elapsedLabel(ms: number): string { + const tenths = Math.max(0, Math.floor(ms / 100)); + if (tenths < 600) return `${(tenths / 10).toFixed(1)}s`; + const secs = Math.floor(tenths / 10); + return `${Math.floor(secs / 60)}m ${String(secs % 60).padStart(2, "0")}s`; +} diff --git a/plugins/web-ui/test/keychain-flow.test.ts b/plugins/web-ui/test/keychain-flow.test.ts index 6f80a54f9..ac9820d40 100644 --- a/plugins/web-ui/test/keychain-flow.test.ts +++ b/plugins/web-ui/test/keychain-flow.test.ts @@ -196,5 +196,5 @@ test("keychain page renders loading placeholders instead of empty states while l /api<\{ providers\?: Record \}>\("\/api\/connectors"\)\.then\(/, ); assert.doesNotMatch(connectorsSource, /drawConnectors\((true|false)\)/); - assert.match(shellCssSource, /\.kc-loading \.spinner/); + assert.match(connectorsSource, /class="kc-loading">\$\{pixelLoader\(\)\}/); }); diff --git a/plugins/web-ui/test/pixel-loader.test.ts b/plugins/web-ui/test/pixel-loader.test.ts new file mode 100644 index 000000000..c9a78b9e1 --- /dev/null +++ b/plugins/web-ui/test/pixel-loader.test.ts @@ -0,0 +1,39 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { JSDOM } from "jsdom"; +import { elapsedLabel } from "../src/work-duration.ts"; + +test("elapsedLabel shows tenths under a minute and zero-padded seconds after", () => { + assert.equal(elapsedLabel(0), "0.0s"); + assert.equal(elapsedLabel(-500), "0.0s"); + assert.equal(elapsedLabel(8_240), "8.2s"); + assert.equal(elapsedLabel(59_960), "59.9s"); + assert.equal(elapsedLabel(60_000), "1m 00s"); + assert.equal(elapsedLabel(64_400), "1m 04s"); + assert.equal(elapsedLabel(3_725_000), "62m 05s"); +}); + +test("pixelLoader renders a nine-cell grid that announces only when labelled", async () => { + const dom = new JSDOM("
"); + Object.assign(globalThis, { window: dom.window, document: dom.window.document }); + const { render } = await import("lit"); + const { pixelLoader } = await import("../src/ui.ts"); + const host = dom.window.document.querySelector("main")!; + + render(pixelLoader("Loading"), host); + const labelled = host.querySelector(".pixel-loader")!; + assert.equal(labelled.querySelectorAll("i").length, 9); + assert.equal(labelled.getAttribute("role"), "status"); + assert.equal(labelled.getAttribute("aria-label"), "Loading"); + assert.equal(labelled.hasAttribute("aria-hidden"), false); + assert.deepEqual( + [...labelled.querySelectorAll("i")].map((cell) => cell.getAttribute("style")), + [90, 180, 270, 0, 90, 180, 90, 180, 270].map((ms) => `animation-delay:${ms}ms`), + ); + + render(pixelLoader(), host); + const decorative = host.querySelector(".pixel-loader")!; + assert.equal(decorative.getAttribute("aria-hidden"), "true"); + assert.equal(decorative.hasAttribute("role"), false); + assert.equal(decorative.hasAttribute("aria-label"), false); +}); diff --git a/plugins/web-ui/test/work-duration-reconciliation.test.ts b/plugins/web-ui/test/work-duration-reconciliation.test.ts index d6295275b..d4537d900 100644 --- a/plugins/web-ui/test/work-duration-reconciliation.test.ts +++ b/plugins/web-ui/test/work-duration-reconciliation.test.ts @@ -89,7 +89,7 @@ test("live and historical rendering consume the same duration helper (source-lev const chat = readFileSync(new URL("../src/chat.ts", import.meta.url), "utf8"); assert.match( chat, - /import \{ workSeconds, workedLabel \} from "\.\/work-duration"/, + /import \{ elapsedLabel, workSeconds, workStartedAt, workedLabel \} from "\.\/work-duration"/, "chat renders via the shared helper", ); From cc53ea95583cff1b932a507eed944bf0fae5bc05 Mon Sep 17 00:00:00 2001 From: Sina Matian <89218912+time-attack@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:03:52 -0400 Subject: [PATCH 04/51] =?UTF-8?q?web-ui:=20approval=20=E2=80=94=20Beautifu?= =?UTF-8?q?l=20UI=20approval=20card=20with=20pager,=20pill=20actions,=20co?= =?UTF-8?q?mpact=20transcript=20marker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web-ui/src/chat.ts | 4 +- plugins/web-ui/src/composer.ts | 130 ++++++++++------ plugins/web-ui/src/shell.css | 168 ++++++++++----------- plugins/web-ui/src/styles/approval.css | 51 +++++++ plugins/web-ui/test/approval-pager.test.ts | 129 ++++++++++++++++ 5 files changed, 339 insertions(+), 143 deletions(-) create mode 100644 plugins/web-ui/test/approval-pager.test.ts diff --git a/plugins/web-ui/src/chat.ts b/plugins/web-ui/src/chat.ts index 37c8c8f14..240ec4f50 100644 --- a/plugins/web-ui/src/chat.ts +++ b/plugins/web-ui/src/chat.ts @@ -2320,9 +2320,7 @@ export function createChatSurface( } function approvalMarker(a: PendingApproval): TemplateResult { - return html`
-
${approvalSummaryView(a)}
-
`; + return html`
${approvalSummaryView(a)}
`; } function sheenLabel(label: string, active: boolean): TemplateResult { diff --git a/plugins/web-ui/src/composer.ts b/plugins/web-ui/src/composer.ts index 8619de53f..f02f23eeb 100644 --- a/plugins/web-ui/src/composer.ts +++ b/plugins/web-ui/src/composer.ts @@ -816,59 +816,91 @@ export function createComposerSurface(ctx: ConvCtx): ComposerSurface { `; } - function composerApprovalPanel(approvals: PendingApproval[]): TemplateResult { + let approvalPage = 0; + + function composerApprovalPanel(approvals: PendingApproval[]): TemplateResult | typeof nothing { + if (!approvals.length) return nothing; const decide = (decision: ApprovalDecision): void => { if (!ctx.chat.state.resolvingApprovals.has(decision.requestId)) ctx.chat.resolveCommandApproval(decision); }; + const page = Math.min(approvalPage, approvals.length - 1); + const a = approvals[page]; + const busy = ctx.chat.state.resolvingApprovals.has(a.requestId); + const turnPage = (delta: number): void => { + approvalPage = page + delta; + ctx.chat.drawActiveChat(); + }; return html`
- ${approvals.map( - (a) => - html`
-
${ctx.chat.approvalSummaryView(a, true)}
-
- - - ${ - a.grantModes?.session === false - ? nothing - : html`` - } - ${ - a.grantModes?.always === false - ? nothing - : html`` - } -
-
`, - )} +
+
${ctx.chat.approvalSummaryView(a, true)}
+
+ ${ + approvals.length > 1 + ? html` + + ${page + 1}/${approvals.length} + + ` + : nothing + } + + + ${ + a.grantModes?.session === false + ? nothing + : html`` + } + ${ + a.grantModes?.always === false + ? nothing + : html`` + } +
+
`; } diff --git a/plugins/web-ui/src/shell.css b/plugins/web-ui/src/shell.css index 772a79b63..65dc528e5 100644 --- a/plugins/web-ui/src/shell.css +++ b/plugins/web-ui/src/shell.css @@ -2277,38 +2277,34 @@ a.chat-row-open { .approval-card { display: flex; flex-direction: column; - gap: 12px; - padding: 14px 16px; - border: 1px solid var(--border); - border-radius: 12px; - background: color-mix(in srgb, var(--foreground) 4%, var(--background)); + gap: 10px; + padding: 14px; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: var(--surface); + color: var(--ink); + font-size: 13px; + line-height: 1.45; } .inline-approval-marker { - gap: 8px; - padding: 10px 12px; -} -.approval-text { - display: flex; - flex-direction: column; gap: 6px; - min-width: 0; + padding: 10px 12px; + font-size: 12.5px; } .approval-title { - font-size: 14px; font-weight: 600; - color: var(--foreground); } .approval-cmd { - font-family: var(--font-mono, ui-monospace, monospace); - font-size: 13px; - padding: 6px 8px; - border-radius: 6px; - background: color-mix(in srgb, var(--foreground) 8%, var(--background)); - color: var(--foreground); + font-family: var(--font-mono); + font-size: 12px; + line-height: 1.5; + padding: 8px 10px; + border-radius: var(--radius-sm); + background: var(--field); + color: var(--ink); overflow-x: auto; white-space: pre; } - .approval-cmd-full { display: block; white-space: pre-wrap; @@ -2324,33 +2320,31 @@ a.chat-row-open { gap: 8px; } .approval-reason-badge { - font-size: 12px; - font-weight: 600; - padding: 2px 9px; + font-size: 11.5px; + font-weight: 500; + line-height: 1.4; + padding: 1px 8px; border-radius: 999px; - background: color-mix(in srgb, var(--destructive, #c0392b) 13%, var(--background)); - color: var(--destructive, #c0392b); - border: 1px solid color-mix(in srgb, var(--destructive, #c0392b) 30%, transparent); + background: var(--red-tint); + color: var(--red); } -.approval-why { - font-size: 13px; - color: var(--foreground); - line-height: 1.4; +.approval-why, +.approval-summary-line { + color: var(--ink-2); +} +.approval-why-label, +.approval-match-label, +.approval-full > summary { + font-size: 11.5px; + color: var(--ink-3); } .approval-why-label { - font-size: 11px; - color: var(--muted-foreground); margin-right: 8px; } -.approval-summary-line { - font-size: 13px; - color: var(--foreground); - line-height: 1.4; -} .approval-summary { - font-family: var(--font-mono, ui-monospace, monospace); - font-size: 13px; - color: var(--foreground); + font-family: var(--font-mono); + font-size: 12px; + color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -2363,30 +2357,29 @@ a.chat-row-open { min-width: 0; } .approval-match-label { - font-size: 11px; - color: var(--muted-foreground); flex: none; } .approval-match-snippet { - font-family: var(--font-mono, ui-monospace, monospace); - font-size: 12px; - padding: 2px 6px; - border-radius: 5px; - background: color-mix(in srgb, var(--destructive, #c0392b) 12%, var(--background)); - color: var(--foreground); + font-family: var(--font-mono); + font-size: 11.5px; + padding: 1px 6px; + border-radius: var(--radius-chip); + background: var(--red-tint); + color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } .approval-full > summary { - font-size: 12px; - color: var(--muted-foreground); cursor: pointer; user-select: none; list-style: none; width: fit-content; } +.approval-full > summary:hover { + color: var(--ink); +} .approval-full > summary::-webkit-details-marker { display: none; } @@ -2404,35 +2397,50 @@ a.chat-row-open { } .approval-actions { display: flex; + align-items: center; flex-wrap: wrap; - gap: 8px; + gap: 6px; } .approval-btn { - font-size: 13px; + display: inline-flex; + align-items: center; + justify-content: center; + height: 28px; + padding: 0 12px; + font: inherit; + font-size: 12.5px; font-weight: 500; - padding: 7px 14px; - border-radius: 8px; - border: 1px solid var(--border); - background: var(--background); - color: var(--foreground); + line-height: 1; + border: 0; + border-radius: 999px; + background: var(--hover-2); + color: var(--ink); cursor: pointer; - transition: opacity 0.12s ease; + transition: + background-color 0.15s ease, + color 0.15s ease, + opacity 0.15s ease; } .approval-btn:hover:not(:disabled) { - opacity: 0.85; + background: var(--line-strong); +} +.approval-btn:focus-visible { + outline: 2px solid var(--blue); + outline-offset: 2px; } .approval-btn.primary { - background: var(--cta); - color: var(--cta-foreground); - border-color: var(--cta); + background: var(--blue); + color: #fff; } -.approval-btn.deny { - color: var(--destructive, #c0392b); - border-color: color-mix(in srgb, var(--destructive, #c0392b) 40%, var(--border)); +.approval-btn.primary:hover:not(:disabled) { + background: var(--blue-ink); +} +.approval-btn.deny:hover:not(:disabled) { + color: var(--red); } .approval-btn:disabled { - opacity: 0.4; - cursor: not-allowed; + opacity: 0.5; + cursor: progress; } .tool-expandable { display: block; @@ -2969,7 +2977,6 @@ a.chat-row-open { display: grid; gap: 10px; margin: 0 0 10px; - max-height: calc(100dvh - 130px); overflow-y: auto; overscroll-behavior: contain; @@ -2977,21 +2984,13 @@ a.chat-row-open { .composer-approval { display: flex; flex-direction: column; - align-items: stretch; - gap: 12px; - min-height: 48px; - padding: 4px 4px 8px; } .composer-approval-copy { display: grid; width: 100%; - gap: 5px; + gap: 6px; min-width: 0; } -.composer-approval .approval-title { - font-size: 12px; - color: var(--muted-foreground); -} .composer-approval .approval-cmd { max-width: 100%; } @@ -4705,9 +4704,6 @@ a.chat-row-open { .message-bubble { max-width: 88%; } - .composer-approval .approval-actions { - justify-content: flex-start; - } .composer-toolbar { align-items: flex-end; } @@ -9745,16 +9741,6 @@ h3.ambient-field-label { } } -@media (max-width: 560px) { - .composer-approval .approval-actions { - grid-template-columns: 1fr 1fr; - gap: 8px; - } - .composer-approval .approval-btn { - min-height: 48px; - } -} - @media (max-width: 860px) and (max-height: 520px) { .sidebar { overflow-y: auto; diff --git a/plugins/web-ui/src/styles/approval.css b/plugins/web-ui/src/styles/approval.css index e69de29bb..fef7717bf 100644 --- a/plugins/web-ui/src/styles/approval.css +++ b/plugins/web-ui/src/styles/approval.css @@ -0,0 +1,51 @@ +.approval-pager { + display: inline-flex; + align-items: center; + gap: 2px; + margin-inline-end: auto; + color: var(--ink-3); +} +.approval-pager-count { + font-family: var(--font-mono); + font-size: 11.5px; + font-weight: 500; + font-variant-numeric: tabular-nums; + line-height: 1; +} +.approval-pager-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + padding: 0; + border: 0; + border-radius: 5px; + background: none; + color: inherit; + font: inherit; + font-size: 15px; + line-height: 1; + cursor: pointer; +} +.approval-pager-btn:hover:not(:disabled) { + color: var(--ink); + background: var(--hover); +} +.approval-pager-btn:focus-visible { + outline: 2px solid var(--blue); + outline-offset: 1px; +} +.approval-pager-btn:disabled { + opacity: 0.3; + cursor: default; +} +@media (max-width: 560px) { + .approval-pager { + justify-content: center; + } + .approval-pager-btn { + width: 36px; + height: 36px; + } +} diff --git a/plugins/web-ui/test/approval-pager.test.ts b/plugins/web-ui/test/approval-pager.test.ts new file mode 100644 index 000000000..403fa0554 --- /dev/null +++ b/plugins/web-ui/test/approval-pager.test.ts @@ -0,0 +1,129 @@ +import { metadata } from "./model-metadata.ts"; +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { JSDOM } from "jsdom"; +import { createServer } from "vite"; +import type { Conversation } from "../src/conv-types.ts"; +import type { PendingApproval } from "../src/core-bridge.ts"; + +async function until(check: () => boolean): Promise { + for (let i = 0; i < 200; i++) { + if (check()) return; + await new Promise((resolve) => setTimeout(resolve, 10)); + } + assert.fail("condition did not settle"); +} + +test("the composer pages through pending approvals one card at a time", async () => { + const dom = new JSDOM('
', { url: "http://localhost/" }); + Object.defineProperty(dom.window, "matchMedia", { + value: () => ({ matches: false, addEventListener() {}, removeEventListener() {} }), + }); + const globals = { + window: dom.window, + document: dom.window.document, + location: dom.window.location, + history: dom.window.history, + localStorage: dom.window.localStorage, + navigator: dom.window.navigator, + HTMLElement: dom.window.HTMLElement, + customElements: dom.window.customElements, + Node: dom.window.Node, + Event: dom.window.Event, + InputEvent: dom.window.InputEvent, + KeyboardEvent: dom.window.KeyboardEvent, + requestAnimationFrame: (callback: FrameRequestCallback) => setTimeout(() => callback(Date.now()), 0), + cancelAnimationFrame: clearTimeout, + getComputedStyle: dom.window.getComputedStyle.bind(dom.window), + EventSource: undefined, + }; + for (const [key, value] of Object.entries(globals)) + Object.defineProperty(globalThis, key, { configurable: true, writable: true, value }); + + const pending: PendingApproval[] = ["rm -rf build", "git push --force", "npm publish"].map((command, i) => ({ + requestId: `a${i + 1}`, + command, + reason: "requires approval", + })); + const row = { id: "s1", threadRef: "web:owner:test", scopeId: "personal:owner", title: "Test" }; + const entries = [{ seq: 1, type: "user", createdAt: Date.now(), payload: { text: "ship it" } }]; + const originalFetch = globalThis.fetch; + globalThis.fetch = async (input) => { + const path = String(input); + if (path.includes("runtime-config")) + return Response.json({ + scopeId: row.scopeId, + approvedHarnesses: ["pi"], + modelsByHarness: { pi: ["gpt-5.6-sol"] }, + modelCatalog: { "gpt-5.6-sol": metadata("gpt-5.6-sol", "GPT-5.6 Sol") }, + orgDefault: { harnessId: "pi", modelId: "gpt-5.6-sol", revision: 0 }, + effective: { harnessId: "pi", modelId: "gpt-5.6-sol" }, + scopeOverride: null, + }); + if (path.includes("/api/runs/active")) return Response.json({ runId: null, queued: [] }); + if (path.endsWith("/approvals")) return Response.json({ approvals: pending }); + if (path.startsWith("/api/sessions/s1")) return Response.json({ session: row, entries, earlierEntries: 0 }); + if (path === "/api/sessions") return Response.json({ sessions: [row] }); + if (path === "/api/contexts") return Response.json({ contexts: [] }); + throw new Error(`Unexpected request: ${path}`); + }; + const vite = await createServer({ server: { middlewareMode: true, hmr: false }, appType: "custom" }); + let conv: Conversation | undefined; + try { + await vite.ssrLoadModule("/src/shell.ts"); + const { appState } = await vite.ssrLoadModule("/src/shell-state.ts"); + const { sessionsState } = await vite.ssrLoadModule("/src/sessions.ts"); + const { createConversation } = await vite.ssrLoadModule("/src/conversations.ts"); + const { entriesToMessages, attachPendingApprovals } = await vite.ssrLoadModule("/src/core-bridge.ts"); + const { transcriptModel } = await vite.ssrLoadModule("/src/model-options.ts"); + const { seedRuntimeConfig } = await vite.ssrLoadModule("/src/composer.ts"); + seedRuntimeConfig(row.scopeId, await (await fetch("/api/runtime-config")).json()); + appState.me = { user: "owner", org: "test" }; + appState.currentView = "chats"; + sessionsState.list = [row]; + const host = document.querySelector("#main")!; + appState.mainEl = host; + conv = createConversation({ + pane: true, + ownsUrl: false, + container: () => host, + claimContainer: () => host, + visible: () => true, + density: () => "full", + onDensityChange() {}, + ensureDeliveryStream() {}, + }) as Conversation; + const messages = entriesToMessages(entries, transcriptModel()); + attachPendingApprovals(messages, pending, transcriptModel()); + conv.mountContinuable(row.threadRef, row.id, row.scopeId, messages); + await until(() => !!conv!.composer.currentModelOption() && !!host.querySelector(".approval-pager-count")); + + const panel = () => host.querySelector(".composer-approval-panel")!; + const count = () => panel().querySelector(".approval-pager-count")?.textContent?.trim(); + const shown = () => [...panel().querySelectorAll(".approval-cmd")].map((el) => el.textContent?.trim()); + const pagerButton = (label: string) => panel().querySelector(`[aria-label="${label}"]`)!; + + assert.equal(count(), "1/3"); + assert.deepEqual(shown(), ["rm -rf build"]); + assert.equal(pagerButton("Previous approval").disabled, true); + + pagerButton("Next approval").click(); + await until(() => count() === "2/3"); + assert.deepEqual(shown(), ["git push --force"]); + assert.equal(pagerButton("Previous approval").disabled, false); + + pagerButton("Next approval").click(); + await until(() => count() === "3/3"); + assert.deepEqual(shown(), ["npm publish"]); + assert.equal(pagerButton("Next approval").disabled, true); + assert.equal(panel().querySelectorAll(".approval-btn").length, 4); + } finally { + conv?.state.agent?.abort(); + await conv?.state.agent?.waitForIdle(); + conv?.composer.dispose(); + conv?.dispose(); + await vite.close(); + globalThis.fetch = originalFetch; + dom.window.close(); + } +}); From 5960e8bdddc1f6503675ab93f436067412ce7e75 Mon Sep 17 00:00:00 2001 From: Sina Matian <89218912+time-attack@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:04:09 -0400 Subject: [PATCH 05/51] =?UTF-8?q?web-ui:=20settings=20=E2=80=94=20the=20se?= =?UTF-8?q?ttings=20page=20becomes=20a=20stack=20of=20inspector=20cards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web-ui/src/settings.ts | 44 ++++++++--- plugins/web-ui/src/shell.css | 89 +++++++++++++++-------- plugins/web-ui/src/styles/settings.css | 50 +++++++++++++ plugins/web-ui/test/settings-page.test.ts | 21 ++++++ 4 files changed, 160 insertions(+), 44 deletions(-) diff --git a/plugins/web-ui/src/settings.ts b/plugins/web-ui/src/settings.ts index 8da128889..794e5ed80 100644 --- a/plugins/web-ui/src/settings.ts +++ b/plugins/web-ui/src/settings.ts @@ -159,7 +159,7 @@ function themeRow(): TemplateResult {
- ${THEME_OPTIONS.map((option) => themeOption(option.value, current, option.label, icon(option.glyph, 15)))} + ${THEME_OPTIONS.map((option) => themeOption(option.value, current, option.label, icon(option.glyph, 14)))} ${custom ? themeOption("custom", current, custom.name, themeSwatches(custom)) : nothing}
@@ -171,7 +171,7 @@ function themeRow(): TemplateResult { @change=${(e: Event) => void onThemeFileChosen(e)} />
- + ${icon(ShieldUser, 15)}Open admin${icon(ExternalLink, 14)}
@@ -257,7 +257,7 @@ function aboutRow(): TemplateResult { Why Y Combinator built this open-source agent harness, and how to run your own. - + ${icon(BookOpen, 15)}Read the announcement${icon(ExternalLink, 14)} @@ -269,24 +269,44 @@ function accountRow(): TemplateResult { return html`
-
Account
+
Signed in as
${me?.user ?? "Not signed in"}${me?.org ? ` · ${me.org}` : ""}
-
`; } +function signOutAction(): TemplateResult { + return html` + + `; +} + +function settingsCard( + title: string, + action: TemplateResult | typeof nothing, + rows: Array, +): TemplateResult { + return html` +
+
+

${title}

+ ${action} +
+ ${rows} +
+ `; +} + function settingsPane(): TemplateResult { return html`

Settings

-
- ${themeRow()} ${sidebarSurfaceRow()} ${can("admin") ? adminRow() : nothing} ${aboutRow()} ${accountRow()} -
+ ${settingsCard("Appearance", nothing, [themeRow(), sidebarSurfaceRow()])} + ${settingsCard("Account", signOutAction(), [accountRow(), can("admin") ? adminRow() : nothing, aboutRow()])} `; } diff --git a/plugins/web-ui/src/shell.css b/plugins/web-ui/src/shell.css index 65dc528e5..d022d6397 100644 --- a/plugins/web-ui/src/shell.css +++ b/plugins/web-ui/src/shell.css @@ -1114,66 +1114,91 @@ a.chat-row-open { .settings-group { display: flex; flex-direction: column; - gap: 1px; max-width: 960px; margin: 12px auto 0; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--surface); } .settings-row { display: flex; align-items: center; justify-content: space-between; - gap: 16px; + gap: 12px 16px; flex-wrap: wrap; - padding: 16px 2px; - border-bottom: 1px solid var(--border); + padding: 12px; + border-bottom: 1px solid var(--line); } .settings-row:last-child { border-bottom: 0; } .settings-row-title { - font-size: 14px; + font-size: 12px; + font-weight: 500; + color: var(--ink-2); } .settings-row-note { margin-top: 2px; font-size: 12.5px; - color: var(--muted-foreground); + color: var(--ink-3); } .settings-row-action { display: inline-flex; align-items: center; - gap: 7px; + gap: 6px; + min-height: 28px; + padding: 0 11px; + border: 1px solid var(--line-strong); + border-radius: 999px; + background: transparent; + color: var(--ink-2); + font: inherit; + font-size: 12.5px; text-decoration: none; + cursor: pointer; + transition: + background 0.12s ease, + color 0.12s ease; +} +.settings-row-action:hover { + background: var(--hover); + color: var(--ink); } .settings-choice { - display: inline-flex; + display: grid; + grid-auto-flow: column; + grid-auto-columns: 1fr; gap: 2px; padding: 2px; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: color-mix(in srgb, var(--secondary) 55%, var(--background)); + border-radius: var(--radius-sm); + background: var(--field); } .settings-choice-option { display: inline-flex; align-items: center; + justify-content: center; gap: 6px; - padding: 6px 11px; - border: 0; - border-radius: var(--radius-sm); + min-height: 26px; + padding: 0 12px; + border: 1px solid transparent; + border-radius: var(--radius-chip); background: transparent; - color: var(--muted-foreground); + color: var(--ink-3); cursor: pointer; font: inherit; - font-size: 13px; + font-size: 12.5px; + white-space: nowrap; transition: - background 0.12s ease, - color 0.12s ease; + background 0.2s var(--ease-out-strong), + color 0.2s ease; } .settings-choice-option:hover { - color: var(--foreground); + color: var(--ink-2); } .settings-choice-option.selected { - background: var(--background); - color: var(--foreground); + border-color: var(--line-strong); + background: var(--surface); + color: var(--blue-ink); } .settings-theme-controls { display: flex; @@ -1187,7 +1212,7 @@ a.chat-row-open { gap: 6px; } .settings-row-error { - color: var(--destructive, #c0392b); + color: var(--red); } .theme-swatches { display: inline-flex; @@ -1195,9 +1220,9 @@ a.chat-row-open { } .theme-swatch { width: 8px; - height: 14px; + height: 8px; border-radius: 2px; - border: 1px solid color-mix(in srgb, var(--foreground) 25%, transparent); + border: 1px solid var(--line-strong); } .main { @@ -6542,7 +6567,7 @@ a.chat-row-open { display: inline-flex; align-items: center; max-width: 100%; - color: var(--foreground); + color: var(--ink); } .field-select > select { min-width: 0; @@ -6551,10 +6576,10 @@ a.chat-row-open { box-sizing: border-box; padding: 0 36px 0 10px; appearance: none; - border: 1px solid var(--border); - border-radius: var(--radius-sm); + border: 1px solid var(--line-strong); + border-radius: var(--radius-chip); outline: 0; - background: var(--background); + background: var(--inset); color: inherit; font: inherit; font-size: 13px; @@ -6563,7 +6588,7 @@ a.chat-row-open { .field-select > .icon { position: absolute; right: 12px; - opacity: 0.55; + color: var(--ink-3); pointer-events: none; } .field-select.compact > select { @@ -6575,11 +6600,11 @@ a.chat-row-open { right: 10px; } .field-select > select:hover:not(:disabled) { - border-color: color-mix(in srgb, var(--foreground) 30%, var(--border)); + border-color: var(--ink-3); } .field-select > select:focus-visible { - border-color: var(--foreground); - outline: 2px solid color-mix(in srgb, var(--foreground) 65%, transparent); + border-color: var(--blue); + outline: 2px solid var(--ring); outline-offset: 2px; } .field-select:has(> select:disabled) { diff --git a/plugins/web-ui/src/styles/settings.css b/plugins/web-ui/src/styles/settings.css index e69de29bb..34d7afffd 100644 --- a/plugins/web-ui/src/styles/settings.css +++ b/plugins/web-ui/src/styles/settings.css @@ -0,0 +1,50 @@ +.settings-group-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + border-bottom: 1px solid var(--line); +} +.settings-group-title { + margin: 0; + font-size: 13px; + font-weight: 500; + color: var(--ink); +} +.settings-group-action { + display: inline-flex; + align-items: center; + gap: 5px; + min-height: 24px; + padding: 0 6px; + border: 0; + border-radius: var(--radius-chip); + background: transparent; + color: var(--blue-ink); + font: inherit; + font-size: 12.5px; + font-weight: 500; + cursor: pointer; + transition: background 0.12s ease; +} +.settings-group-action:hover { + background: var(--blue-tint); +} +.settings-row-copy { + flex: 1 1 240px; + min-width: 0; +} +@media (max-width: 560px) { + .settings-choice, + .settings-theme-controls { + width: 100%; + } + .settings-theme-controls { + justify-content: flex-start; + } + .settings-row-action, + .settings-group-action { + min-height: 44px; + } +} diff --git a/plugins/web-ui/test/settings-page.test.ts b/plugins/web-ui/test/settings-page.test.ts index 2a8c68622..d3b0c4cbc 100644 --- a/plugins/web-ui/test/settings-page.test.ts +++ b/plugins/web-ui/test/settings-page.test.ts @@ -7,6 +7,7 @@ const shell = readFileSync(new URL("../src/shell.ts", import.meta.url), "utf8"); const shellState = readFileSync(new URL("../src/shell-state.ts", import.meta.url), "utf8"); const main = readFileSync(new URL("../src/main.ts", import.meta.url), "utf8"); const css = readFileSync(new URL("../src/shell.css", import.meta.url), "utf8"); +const settingsCss = readFileSync(new URL("../src/styles/settings.css", import.meta.url), "utf8"); test("settings is a routable view, so /settings survives a reload and a shared link", () => { assert.match(shellState, /"settings",?\s*\] as const/); @@ -93,6 +94,26 @@ test("the imported palette is painted as one style element that applyTheme owns assert.match(css, /\.theme-swatch \{/); }); +test("settings rows live in titled inspector cards, with the radiogroups as segmented controls", () => { + assert.match(settings, /settingsCard\("Appearance", nothing, \[themeRow\(\), sidebarSurfaceRow\(\)\]\)/); + assert.match(settings, /settingsCard\("Account", signOutAction\(\)/); + assert.match( + settings, + /
\s*
\s*

/, + ); + assert.match(settings, /class="settings-group-action"[^>]*@click=\$\{\(\) => void signOut\(\)\}/); + assert.doesNotMatch(settings, /class="btn settings-row-action"/, "row actions are ghost pills, not bordered buttons"); + assert.match(css, /\.settings-group \{[^}]*border: 1px solid var\(--line\);[^}]*background: var\(--surface\);/); + assert.match(css, /\.settings-choice \{[^}]*grid-auto-columns: 1fr;[^}]*background: var\(--field\);/); + assert.match(css, /\.settings-choice-option\.selected \{[^}]*background: var\(--surface\);/); + assert.match(settingsCss, /\.settings-group-action \{[^}]*color: var\(--blue-ink\);/); + assert.match( + settingsCss, + /@media \(max-width: 560px\) \{\s*\.settings-choice,\s*\.settings-theme-controls \{\s*width: 100%;/, + "segmented controls span the phone width", + ); +}); + const chat = readFileSync(new URL("../src/chat.ts", import.meta.url), "utf8"); test("the chat greeting rotates across reloads, not just within one page session", () => { From a8f367ec239e478c1da52b4fd6d41528ce24ce40 Mon Sep 17 00:00:00 2001 From: Sina Matian <89218912+time-attack@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:05:18 -0400 Subject: [PATCH 06/51] =?UTF-8?q?web-ui:=20search=20=E2=80=94=20restyle=20?= =?UTF-8?q?the=20=E2=8C=98K=20palette=20to=20the=20Beautiful=20UI=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web-ui/src/search.ts | 20 +-- plugins/web-ui/src/shell.css | 116 +++++++++--------- plugins/web-ui/src/styles/search.css | 26 ++++ .../web-ui/test/search-palette-source.test.ts | 41 +++++++ 4 files changed, 132 insertions(+), 71 deletions(-) create mode 100644 plugins/web-ui/test/search-palette-source.test.ts diff --git a/plugins/web-ui/src/search.ts b/plugins/web-ui/src/search.ts index eeb565c14..54560f59e 100644 --- a/plugins/web-ui/src/search.ts +++ b/plugins/web-ui/src/search.ts @@ -1,10 +1,3 @@ -/** Chat search — a ⌘K palette over every conversation the viewer can see. - * - * Results come from the core's Postgres full-text index and are grouped by - * session, newest first. Enter opens the selected conversation; ⌘/Ctrl+Enter - * (or the pinned bottom row) pipes the query to QM as the prompt of a new - * chat, so a fruitless search becomes a question. - */ import { html, nothing, render, type TemplateResult } from "lit"; import { CornerDownLeft, Search } from "lucide"; import { api, userSendMessage, type CoreSession } from "./core-bridge"; @@ -39,7 +32,6 @@ const searchState = { hits: [] as ChatSearchHit[], failed: false, loading: false, - /** Selection over hits (0..hits.length-1) plus the trailing ask-QM row. */ sel: 0, }; @@ -143,8 +135,6 @@ async function runSearch(q: string): Promise { draw(); } -/** Cluster hits by session (sessions ordered by their newest hit) so a - * conversation never fragments into repeated group headers. */ function groupHitsBySession(hits: ChatSearchHit[]): ChatSearchHit[] { const order: string[] = []; const bySession = new Map(); @@ -261,7 +251,9 @@ function resultRows(): TemplateResult[] { ${highlight(hitSnippet(hit))} ${hit.entryType === "user" ? (hit.author ?? "you") : "agent"} · - ${new Date(hit.createdAt).toLocaleDateString()}${new Date(hit.createdAt).toLocaleDateString()} @@ -300,13 +292,13 @@ function paletteTpl(): TemplateResult { if (q.length < MIN_QUERY_LEN) { body = html`
Search every chat you can see: messages, not just titles.
`; } else if (searchState.loading && !searchState.hits.length) { - body = html`
Searching…
`; + body = html`
Searching…
`; } else if (searchState.failed) { body = html`
Search failed. Check the connection and try again.
`; } else if (!searchState.hits.length) { - body = html`
No messages match “${q}”.
`; + body = html`
No results for “${q}”
`; } else { body = html`${resultRows()}`; } @@ -319,7 +311,7 @@ function paletteTpl(): TemplateResult { >