From d5d867831c3b048ff51fd1a2a2c0efaac3f668c5 Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Sat, 10 Oct 2026 14:33:44 -0500 Subject: [PATCH 1/3] feat(ui): per-card VRAM readings and a sidebar that marks the open workspace The header meter, status popover and server page now read /api/memory's `workers` list - one reading per card - instead of the top level, which only ever repeats the first card. The header stacks one bar per card. The sidebar gains a Workspaces label, chevrons, single-line truncated names, and an open-workspace block with a --select rail, tint and a guide line down its sections. Each workspace carries a hue hashed from its name (wsColor), shown in the sidebar and the breadcrumb. --select is a new token for the user's place (nav selection and the focus ring, which moves off --live); design-rules.test.ts holds where it may appear, and app.css's header records the three colour meanings. Co-Authored-By: Claude Opus 5.5 --- ui/CLAUDE.md | 2 +- ui/scripts/design-rules.test.ts | 20 ++++- ui/src/App.svelte | 52 +++++++------ ui/src/app.css | 39 ++++++---- ui/src/lib/Breadcrumb.svelte | 51 +++++++++---- ui/src/lib/CardMemoryList.svelte | 59 +++++++++++++++ ui/src/lib/Sidebar.svelte | 111 +++++++++++++++++++++++----- ui/src/lib/Sidebar.test.ts | 19 +++++ ui/src/lib/StatusPopover.svelte | 19 +---- ui/src/lib/cardMemory.test.ts | 51 +++++++++++++ ui/src/lib/cardMemory.ts | 69 +++++++++++++++++ ui/src/lib/pages/ServerPage.svelte | 17 +++-- ui/src/lib/pages/ServerPage.test.ts | 34 ++++++++- ui/src/lib/wsColor.test.ts | 8 ++ ui/src/lib/wsColor.ts | 15 ++++ 15 files changed, 464 insertions(+), 102 deletions(-) create mode 100644 ui/src/lib/CardMemoryList.svelte create mode 100644 ui/src/lib/cardMemory.test.ts create mode 100644 ui/src/lib/cardMemory.ts create mode 100644 ui/src/lib/wsColor.test.ts create mode 100644 ui/src/lib/wsColor.ts diff --git a/ui/CLAUDE.md b/ui/CLAUDE.md index bc8c6ff8..0090ac5c 100644 --- a/ui/CLAUDE.md +++ b/ui/CLAUDE.md @@ -8,4 +8,4 @@ Checks, from `ui/`: `npm run check`, `npm run lint`, `npm test`, and `npx playwr - Show the proof: a list of things that produce images shows the images. - Every token pair should pass WCAG AA in both themes. -- `--live` marks machine state and nothing else; where it may appear is held by `scripts/design-rules.test.ts`. +- `--live` marks machine state and `--select` the user's place (focus, the open workspace, the active section), each nothing else; where they may appear is held by `scripts/design-rules.test.ts`. diff --git a/ui/scripts/design-rules.test.ts b/ui/scripts/design-rules.test.ts index 91b483b4..21791a44 100644 --- a/ui/scripts/design-rules.test.ts +++ b/ui/scripts/design-rules.test.ts @@ -1,6 +1,6 @@ // @vitest-environment node -// Where the --live state colour may appear, held mechanically (ui/CLAUDE.md; -// app.css's header says why --live means machine state and nothing else) +// Where the --live state colour and the --select place colour may appear, +// held mechanically (ui/CLAUDE.md; app.css's header says what each means) import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' @@ -11,8 +11,8 @@ const UI = join(dirname(fileURLToPath(import.meta.url)), '..') // The places --live may appear: the header's running link and VRAM // pressure, a running job's row and status chip, the job page's progress -// bar and current step, the flow view's active step, a model download in -// flight, and the focus ring +// bar and current step, the flow view's active step, and a model download +// in flight const LIVE_FILES = [ 'src/App.svelte', 'src/app.css', @@ -29,3 +29,15 @@ it('uses --live only where machine state is shown', () => { .map((f) => f.slice(UI.length + 1).replaceAll('\\', '/')) expect(using).toEqual(LIVE_FILES) }) + +// The places --select may appear: the focus ring and the sidebar's open +// workspace and active section. It is chrome only - never a surface behind +// an image +const SELECT_FILES = ['src/app.css', 'src/lib/Sidebar.svelte'] + +it('uses --select only for the focus ring and the nav', () => { + const using = sourceFiles() + .filter((f) => readFileSync(f, 'utf8').includes('var(--select)')) + .map((f) => f.slice(UI.length + 1).replaceAll('\\', '/')) + expect(using).toEqual(SELECT_FILES) +}) diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 656b2adb..04709092 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -1,6 +1,6 @@