Skip to content

feat(ui): pinia stores for v2 dashboard (slice #165)#178

Merged
thinmintdev merged 1 commit into
feat/dash-v2-reworkfrom
feat/dash-v2-1b-stores
May 23, 2026
Merged

feat(ui): pinia stores for v2 dashboard (slice #165)#178
thinmintdev merged 1 commit into
feat/dash-v2-reworkfrom
feat/dash-v2-1b-stores

Conversation

@thinmintdev
Copy link
Copy Markdown
Contributor

Closes the store skeleton half of #165 (parent: #148).

Stores added

Store File Notes
useLemonadeStore ui/src/stores/lemonade.js 2s /v1/health poll, refcounted init()/stop(), exposes loadedModels[], maxModels, version, lastUse, health, throughput, loadedByName, isLoaded(name).
useBackendsStore ui/src/stores/backends.js /api/backends (404 → mock fallback), install()/uninstall(), byId/installed getters, isMocked flag.
useBannerStore ui/src/stores/banner.js 18-entry catalog verbatim from design's primitives.jsx; show/dismiss/toggle/clearScope + activeByScope() getter.
useToastStore ui/src/stores/toast.js v2 queue (separate from existing toasts.js); push(msg, kind, ttl) auto-removes after ttl, sticky when ttl <= 0.
useTweaksStore ui/src/stores/tweaks.js DEV-only; persists 6 design-knob picks to localStorage:hal0:tweaks:v2; no-op shim in production.

Existing stores extended

useNuclearEvictBanner refactor

The composable already mounts once at the App level. PR-11 wired it to /api/lemonade/events/stream (SSE) for the nuclear-evict toast. This slice keeps that branch intact and ALSO calls useLemonadeStore.init() on mount (and stop() on unmount). Net: one subscription point in App.vue, two surfaces fed from it (SSE toast + polled health snapshot). PR-11's dashboard-lemonade-state.spec.ts continues to pass unchanged.

Mock allowlist (endpoints not shipped yet)

Unit tests

Vitest is not configured in ui/. Per slice brief, smoke coverage will land as e2e assertions in the next view slice that consumes these stores — adding vitest now is out-of-scope and would bloat the PR. Reasoning recorded here in lieu of a tests/unit/stores/ dir.

Verification

  • npm run build — clean
  • npm run test:e2e — 38/38 pass (incl PR-11's dashboard-lemonade-state.spec.ts 4 cases)
  • ruff format --check ui/ src/ — clean (5 pre-existing format drifts in packaging//scripts/ are unrelated)
  • ruff check ui/ src/ — clean
  • No views or component files edited
  • No new routes added
  • Tailwind/vite config untouched

🤖 Generated with Claude Code

…ast/tweaks (slice #165)

Stands up the Pinia store skeleton every v2 view depends on:

- useLemonadeStore — polls GET /v1/health every 2s; exposes
  loadedModels[], maxModels, version, lastUse per loaded model, health
  rollup. Refcounted init()/stop() so multiple callers share one
  timer.
- useBackendsStore — lists installable backends + state + slot fan-out;
  /api/backends is mocked until #142/#145 land (isMocked flag exposed).
- useBannerStore — 18-entry catalog (verbatim from the design's
  primitives.jsx BANNER_CATALOG); show/dismiss/toggle/clearScope +
  activeByScope() getter.
- useToastStore — v2 toast queue (separate from existing toasts.js so
  this slice is zero-regression for the v1 surface).
- useTweaksStore — DEV-only designer overlay, persisted to
  localStorage:hal0:tweaks:v2; no-op shim in production builds.

Light-touch extension to useSystemStore: documents the SlotConfig
device field already returned by the backend after PR-11 (#163).

useNuclearEvictBanner refactored to call useLemonadeStore.init() on
mount alongside its existing /api/lemonade/events/stream SSE — App.vue
still subscribes once, gets both polling + SSE-driven nuclear-evict
toast. PR-11's dashboard-lemonade-state.spec.ts continues to pass.

Closes data half of #148.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thinmintdev thinmintdev merged commit 98aaf5f into feat/dash-v2-rework May 23, 2026
4 checks passed
thinmintdev added a commit that referenced this pull request May 23, 2026
Wires the GET/POST /api/lemonade/config surface that the Settings →
Lemonade admin panel needs, plus the Vue view that consumes it.

Backend (src/hal0/api/routes/lemonade_admin.py):
  - GET  /api/lemonade/config  → lemond /internal/config snapshot +
    _hal0.{effects, locked} metadata (plan §2.2 partition + the locked
    extra_models_dir invariant). UI uses these to render badges + hints
    without re-encoding the lists in the frontend.
  - POST /api/lemonade/config  → forwards a flat-key patch to
    /internal/set after validation. Response echoes
    {applied, effects:{immediate,deferred}} for the toast copy.

Validation guardrails (refuse 400 lemonade.config_invalid):
  - Unknown key (not in plan §2.2 immediate∪deferred sets).
  - llamacpp_args missing --threads N or N<2 — per the
    hal0_lemonade_threads_deadlock memory, omitting --threads
    trips a Vulkan dispatch deadlock under concurrent load.
  - flm_args missing --asr 1 or --embed 1 — the FLM NPU trio is
    mandatory in v0.2 (plan §5, ADR-0009 §1).
  - extra_models_dir diverging from /var/lib/hal0/models — would
    silently desync the dashboard catalog (plan §3 + §6.1).

Routes mount under the parent _admin_auth gate in hal0.api; POST
additionally declares require_writer for the CSRF tripwire on
cookie sessions (matches /api/settings PUT).

Frontend (ui/src/views/Settings/LemonadeAdmin.vue):
  - Flat-key form bound to all 13 admin-editable keys, grouped into
    Service / Concurrency+serving / llama.cpp / FLM (NPU) /
    whisper.cpp / Stable Diffusion sections.
  - Per-field Immediate / Deferred (next load) badge sourced from
    the backend's _hal0.effects (no frontend constant to keep in
    sync with plan §2.2).
  - Inline locked-invariant hints rendered up front for
    llamacpp_args + flm_args; extra_models_dir is read-only on the
    form (the backend would refuse a diverging value anyway).
  - Save sends ONLY the diff; success toast cites the
    "N immediate, M deferred until next load" split lifted from the
    response. 400s land as per-key inline errors via fieldErrors.
  - Mounted at /settings/lemonade (separate route from the main
    Settings page so unsaved-changes-on-leave stays isolated). The
    main Settings view picks up a small link card pointing to it.

Tests:
  - tests/api/test_lemonade_admin_route.py (25 tests) — covers GET
    pass-through + metadata attachment, POST happy path with all
    three effect splits, every validator branch (unknown key,
    llamacpp_args without --threads / with --threads 0 / with
    --threads 1 / accepting --threads=8 equals form, flm_args
    missing each trio flag / disable form, extra_models_dir
    divergence + canonical), and the empty/non-object/non-JSON
    body cases.
  - ui/tests/e2e/specs/lemonade-admin.spec.ts (4 tests) — renders
    all sections + effect badges, save POSTs only the diff with
    the correct toast copy, validation surfaces inline per-field
    errors, link from /settings deep-links into the panel.

Note: #177 + #178 (design v2 tokens + new Pinia stores) merged
onto feat/dash-v2-rework, NOT main. This PR uses the existing
toasts.js store + useApi composable. When dash-v2-rework lands the
admin panel migrates onto the new lemonade + toast stores.

Suite: 1571 passed, 8 skipped (+25 from PR-12 baseline). γ-suite
42 passed (+4 new).

Refs: plan §11 PR-13 + §2.2; ADR-0008 §1 + §7; memories
hal0_lemonade_threads_deadlock + hal0_lemonade_v1_load_schema +
hal0_lemonade_flm_npu_install.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thinmintdev added a commit that referenced this pull request May 23, 2026
#199)

* feat(ui): add design v2 token vocabulary as aliases over --hal0-* (#177)

Foundation for v0.2.1 dashboard rewrite (#148). Adds surface/fg/line
ramps, device chip colors, status semantic colors, radii, motion vocab
as additive tokens. NO component edits — existing dashboard renders
pixel-identical. Closes #164.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): pinia stores for v2 dashboard — lemonade/backends/banner/toast/tweaks (slice #165) (#178)

Stands up the Pinia store skeleton every v2 view depends on:

- useLemonadeStore — polls GET /v1/health every 2s; exposes
  loadedModels[], maxModels, version, lastUse per loaded model, health
  rollup. Refcounted init()/stop() so multiple callers share one
  timer.
- useBackendsStore — lists installable backends + state + slot fan-out;
  /api/backends is mocked until #142/#145 land (isMocked flag exposed).
- useBannerStore — 18-entry catalog (verbatim from the design's
  primitives.jsx BANNER_CATALOG); show/dismiss/toggle/clearScope +
  activeByScope() getter.
- useToastStore — v2 toast queue (separate from existing toasts.js so
  this slice is zero-regression for the v1 surface).
- useTweaksStore — DEV-only designer overlay, persisted to
  localStorage:hal0:tweaks:v2; no-op shim in production builds.

Light-touch extension to useSystemStore: documents the SlotConfig
device field already returned by the backend after PR-11 (#163).

useNuclearEvictBanner refactored to call useLemonadeStore.init() on
mount alongside its existing /api/lemonade/events/stream SSE — App.vue
still subscribes once, gets both polling + SSE-driven nuclear-evict
toast. PR-11's dashboard-lemonade-state.spec.ts continues to pass.

Closes data half of #148.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(internal): dashboard v2 implementation plan + v0.3 fold-in

Living plan doc + slice ledger for #148. Includes v0.3 carry-in (MCP Servers → slice #14#180) + worktree-path-discipline rules codified from observed failures.

* feat(ui): v2 primitives (Modal/Drawer/ConfirmDialog/Banner/BannerStack/Menu/Toast/ToastStack) + 19th banner skip-path (slice #167) (#181)

Cross-cutting Vue 3 primitives for the v2 dashboard, mirroring the
React reference at /tmp/hal0-design-v3/dash/primitives.jsx 1:1 in
markup, class names and behaviour. Lands as a self-contained slice
under ui/src/components/primitives/; no existing views/chrome edited.

New primitives
- Modal — Esc/backdrop close (gated by dismissable), body-scroll
  lock + restore, hand-rolled Tab/Shift+Tab focus trap, focus
  restore on close, mounted via <Teleport to="body">.
- Drawer — right-side slide-in (transform: translateX 100% → 0),
  same lock/trap/restore as Modal. side="left" prop reserved.
- ConfirmDialog — wraps Modal; recoverable (neutral btn) vs
  destructive (red btn + "permanent" eyebrow); typeToConfirm input
  gates the confirm button until exact match.
- Banner — reusable warn/err/info shell; emits "action" so it
  stays store-agnostic.
- BannerStack — reads useBannerStore.activeByScope(scope) + the
  global scope alongside; falls back to a toast when an action has
  no onClick (matches design's window.__hal0Toast bridge).
- Menu — Teleported popover anchored to a trigger element;
  auto-closes on outside click + Esc + selection; items without
  onClick toast "<label> — stubbed".
- Toast + ToastStack — presentational toast pill + top-right
  TransitionGroup reading useToastStore. Mount stays for slice #5.

19th banner — skip-path
- BANNER_CATALOG now has 19 entries; the new "skip-path" entry
  (scope=slots, kind=info) is the v0.3 fold-in for the FirstRun
  bundle-picker skip surface.

Tests
- tests/e2e/specs/primitives.spec.ts — 8 specs covering Modal
  Esc/backdrop, Drawer transform on open, ConfirmDialog destructive
  type-to-confirm gate, recoverable variant enabled-immediately,
  BannerStack scope-filtering + dismiss-removes-from-store,
  Menu wired-onClick + Esc + outside-click close, Menu stubbed-item
  toasts, ToastStack queue + short-ttl auto-removal.
- /_primitives_test sandbox route (skipFirstRunGuard) hosts the
  spec; unreachable from chrome.

Resolves the primitives half of #167.

* feat(ui): mock harness for absent endpoints — useMock + dispatch + Playwright fixture parity (slice #166) (#182)

Adds `ui/src/composables/useMock.js` carrying the v2/v0.3 mock dataset
(host, lemonade, slots, bundles, journal, models, backends, personas,
MCP servers/clients/catalog) and a drop-in `mockFetch` that substitutes
allowlisted endpoints. Two activation modes: `VITE_MOCK_LEMONADE=1` for
offline dev, or per-endpoint 404 fallback with console.warn.

The allowlist has 8 entries, each tagged with the backend issue that
will retire it (#145 metrics, #142 multi-modal slots, #180 MCP). When
the real endpoint lands, drop the matcher + builder; store callers
keep using `mockFetch` unchanged.

`useBackendsStore` refactored to use `mockFetch` instead of its inline
404 fallback. `useLemonadeStore` gains 5s `/v1/stats` polling — mirrors
PR-12 #179's emission shape so the cutover after rebase is free.

Playwright fixtures get a parallel `mock-data.ts` (Vite's
`import.meta.env` blocks direct import under Node + tsx), plus
`mockMcpEndpoints(page)` + `mockV1Stats(page)` helpers that pre-route
common endpoints. All 38 existing Playwright cases (12 specs) stay
green. `VITE_MOCK_LEMONADE=1 npm run dev` boots without a backend.

Refs #166. Anti-scope per brief: no new routes, no primitives, no
chrome, no global `window.fetch` patch, no banner catalog entries, MCP
store deferred to slice #14 #180.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 chrome — TopBar/Sidebar/Footer/BottomTabs + Agents·v0.3 group + journal pane upgrades (slice #168) (#185)

Replaces the v1 chrome with the v0.3 design vocabulary:

  * TopBar — wordmark + version pill + route eyebrow + ⌘K stub
    + host chip + AgentApprovalBell.
  * Sidebar — Dashboard / Slots / Models / Hardware / Backends /
    Logs / Agents·v0.3 sub-group (Agents / MCP Servers / Memory) /
    Settings. Lemonade status block at the bottom (state dot +
    N/M loaded) routes to /logs?source=lemond. Variants per
    breakpoint: full ≥1280 / icon-collapse 1080–1279 / overlay
    drawer 720–1079 / hidden <720.
  * Footer — two-line chip row (lemond:<state> · throughput ·
    loaded · NPU coresident · queued + update-available pill +
    journal toggle) over a last-3 journal peek. Expand pane slides
    up with source filter (merged/hal0/lemond), search with amber
    inline highlight, empty state, and "Open full logs →". Open
    state persists via sessionStorage:hal0:journal-pane.
  * BottomTabs — <720 only; Home / Slots / Models / Logs / More
    (sheet with Hardware/Backends/Agent/Settings).
  * App.vue mounts <BannerStack scope="global"/> above the route
    view and <ToastStack/> at the shell. Both are suppressed on
    the primitives sandbox so its own instances stay sole.
  * Router registers /agents/mcp + /agents/memory as
    ComingSoon.vue placeholders so the new sidebar links don't
    404 before slice #14 and Phase 9 ship.

The old footer subtree (FooterBar / FooterPane / 4 tab views) is
deleted; footer.spec.ts is rewritten against the new chip-row +
journal-pane DOM and chrome.spec.ts adds 14 new tests covering
every breakpoint, the journal pane upgrades, the Agents·v0.3
group, and the Lemonade status block.

All 59 e2e specs pass. ruff format/check has only pre-existing
issues in unrelated python files.

Closes #168.

* feat(ui): v2 Dashboard / view — snapshot strip + persona picker + composer (5 states) + chat surface (slice #169) (#187)

Rewrite Dashboard.vue to the v0.3 chat-first layout. Replaces the
stat-rail + unified-memory + slots-grid + test-chat-panel + recent-events
sections with three stacked regions:

  1. Hero strip (~60px) — 3 variants (returning / post-install /
     skip-path-empty) with sessionStorage-backed × dismiss.
  2. SnapshotStrip (~90px) — per-slot row routing to /slots/:name,
     with type-aware metric strip (llm tok/s · TTFT · ctx · KV%,
     embed req/min · p50 · dim, etc.). KV% honestly shows "—" for
     GPU llm slots per the bundled llama-vulkan scrape gap.
  3. Chat surface (rest) — ChatActive | ChatEmpty + Composer with
     persona-above placement.

Adds under ui/src/components/dashboard/:
  - SnapshotStrip.vue  — clickable rows, state dot, device chip,
                          default ✦, coresident chip
  - PersonaPicker.vue  — persona chip + dropdown of llm slots,
                          "+ Add chat slot" → /slots create flow
  - Composer.vue       — 5 states (idle / sending / streaming /
                          swap / no-tools / offline) with persona
                          slot, attach + mic + send (or Stop)
  - ChatActive.vue     — user/assistant bubbles + inline tool-call
                          <details> blocks + persona-swap markers
                          + image/audio/text attachments
  - ChatEmpty.vue      — glyph + 3 example prompt chips

Extends useTweaksStore with chatVariant + heroVariant knobs for
designer preview without changing prod derivation.

The composer's `swap` state surfaces inline (composer-banner-swap)
because npu-swap in the banner catalog is scope=slots and would not
render through this view's scope=dashboard BannerStack.

Tests: new dashboard.spec.ts replaces the old test-chat dropdown
regression (that surface no longer exists). 12 specs cover all 5
composer states, SnapshotStrip row routing, persona swap → swap
state, tool-call <details> toggle, hero × persist, and the
skip-path-empty composer hide.

Full e2e: 69/69 green. Build clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 Models /models view — 3-pane catalog + AddByHF + Delete + DownloadRow 7 states (slice #171) (#188)

Wholesale replace v1 single-table Models.vue with the v2 3-pane layout
from the design source. Catalog list (left, 320px) + detail (right-top)
+ Downloads pane (right-bottom). <1080px collapses to list-primary with
detail + downloads as Drawer overlays.

New components under ui/src/components/models/:
- ModelList: filter chips (type/device/labels/namespace) + search +
  active-filter summary + sectioned list (installed / blessed / user.*)
- ModelDetail: header + recipe options w/ inline edit + real-time
  llamacpp_args denied-flag rejection + Used-by panel + On-disk panel
  + actions (Load now / Reveal-or-Copy-path / Delete / Pull)
- DownloadsPane + DownloadRow: 7 canonical states (pulling, paused,
  cancelled, error, verifying, completed, queued) + multi-file expand
  on hover + 5s auto-remove for completed (hover-defer)
- AddByHFModal: Inspect → variants (real /v1/pull/variants w/ mock
  fallback) → user.* model_name → labels (mmproj required for vision)
  → pre-flight panel → Pull
- DeleteModelDialog: destructive confirm + warn-soft block listing
  slot references + type-to-confirm (model.id) + omni-collection copy

E2E coverage:
- models-v2.spec.ts: AddByHF inspect→variants→Pull happy path, vision
  requires mmproj, delete type-to-confirm gating, 7-state DownloadRow
  rendering via window.__hal0_setFixtureDownloads fixture injection,
  denied llamacpp_args rejection, 3-pane vs compact responsive
- models.spec.ts: adapted from v1 table flow to v2 detail-pane Load Now
- models-slots-refactor.spec.ts: adapted from v1 scan/edit-slot flows
  to v2 a11y + filter-chip behaviour

Tests: 74/74 pass. Build clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 Settings /settings — 9 sections + Lemonade guardrails + OmniRouter + secrets/memory/auth (slice #173) (#190)

Rewrites Settings.vue around the v0.3 left-rail anchor layout from the
design source. Nine sections render under one route: Auth, Secrets,
Updates, Lemonade admin, OmniRouter, Agent policy, Memory (Cognee),
Appearance, About. Scroll-spy + hash deep-links wire the rail.

Lemonade admin folds in PR-13's keys via an in-page form: max_loaded_models,
ctx_size, llamacpp.backend/args, flm.args, whispercpp.backend, sdcpp.*,
log_level, global_timeout. `llamacpp.args` is read-only by default;
the Edit toggle reveals the GPU-deadlock footgun warning. Save fires
the SaveAndRestartDialog ("~8-12s outage") and the restart endpoint
runs after persisting when any deferred key changed. The standalone
/settings/lemonade subview stays mounted so PR-13's lemonade-admin
spec (link from /settings + PageHeader title assertion) passes.

OmniRouter renders 8 tools (3 hal0 + 5 upstream) with origin chips,
target slot, and remediation CTAs for inactive entries. Secrets section
falls back to seeded local mocks when /api/secrets returns an empty
body (catch-all stub or 404); AddSecretModal saves into the list.
Memory namespace reset is type-to-confirm via the destructive
ConfirmDialog. Appearance writes theme + density to useTweaksStore
(persisted in prod too for appearance keys only).

New primitives under ui/src/components/settings/: SettingsRail,
SecRow, SecKey, RestartChip, AddSecretModal, AllowedOriginsModal,
RotateTokenDialog, SaveAndRestartDialog, BundledLicensesDrawer.

Coverage: new settings-v2.spec.ts (7 cases) + adapted settings.spec.ts
smoke. Full Playwright suite 78/78. PR-13's lemonade-admin.spec.ts
still passes.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 FirstRun /firstrun — bundle picker (pick/confirm/progress) + grid+matrix variants + skip dialog (slice #172) (#191)

Replaces the v1 8-step linear wizard with the v0.3 design's three-state
machine: pick (Lite/Default/Pro/Max tier cards or capability matrix) →
confirm (per-slot install list + optional NPU trio opt-in) → progress
(per-row download bars with inline Retry + Skip-this-model).

State + endpoint wiring lives in components/firstrun/useFirstRun.js
(rewritten from scratch). The 8-step composable's API is gone; the new
composable exposes view / pickedTier / withNpu / pull alongside derived
bundle state (recommended / available / unfit / installed / gated-no-hf).

Sub-components: BundleGrid, BundleTable, TierCard, InstallProgressRow,
SkipBundleDialog. Layout variant switches via useTweaksStore.firstrunLayout
(tiers = grid, wizard = capability matrix). Banner catalog entries
fr-reentered, fr-ram-low, and hf-gated are toggled on/off as state
transitions warrant — no new catalog rows.

LMX-Omni-52B-Halo pre-built kit surfaces when host RAM ≥ 100 GB. Skip
flow wraps primitives/ConfirmDialog with the design's verbatim copy.
Progress rows drive an SSE stream per pull and degrade to polling on
EventSource error.

The agent-flow spec's "first-run wizard surfaces the agent step" test
is .skip'd: the v0.3 design removes the bundled-agent picker from
firstrun (lives behind /agent + ADR-0004 settings entry now). Slice
#174 re-targets the install flow.

Closes #172.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 MCP Servers page (/agents/mcp) — KPI strip + clients ribbon + LiveTimeline oscilloscope + Install/Config/Logs/Connect modals (slice #14 / #180) (#193)

v0.3 Agents · MCP Servers surface (issue #180, dash-v2 slice 14).
Replaces the slice #168 ComingSoon placeholder.

McpView composition (top-down):
- 6-cell KPI strip (running/clients/calls60s/failures/installing/last-activity)
- ClientsRibbon ↔ NoClientsState (data-driven)
- 5-tab segmented filter bar + timeline-tick legend
- Vertical McpServerRow stack — running/stopped/failed/installing
  variants, bundled rail, per-row 60s LiveTimeline oscilloscope
- Drawer + Modal cluster: InstallDrawer (Catalog+URL tabs),
  EditConfigModal, LogsDrawer, ConnectClientModal, destructive
  ConfirmDialog (type-to-confirm uninstall; bundled rejects)

Live data path: useLiveCallStream composable (500ms tick, p=rpm/120
per running server, 60s GC window). Production swap hook = a WS
subscription on /api/mcp/stream.

useMcpStore (Pinia) backs the page — fetch/install/uninstall/restart/
toggleEnabled/updateConfig — all behind mockFetch + per-resource
loading/error state. Endpoint stubs covered by mockMcpEndpoints fixture.

Sidebar: unblocked the "MCP Servers" sub-row (one-line change);
Memory remains gated. chrome.spec updated to expect 1 disabled
sub-row (Memory) instead of 2.

playwright.config: HAL0_E2E_PORT env override added so spec runs
in parallel worktrees stop colliding on a shared 5173 vite port.

Coverage: ui/tests/e2e/specs/mcp-v2.spec.ts — 8 tests, all green
end-to-end + full 79-spec suite remains green.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 Extras — Hardware/Backends/Logs/Agent + Backend modals + Persona edit (slice #174) (#194)

Rewrites four secondary-route views against the v0.3 design system:

- Hardware: 6 vertical-stack panels (Host / CPU / GPU / NPU / Memory /
  Storage) with status dots, recommended-chip rollups, per-slot memory
  segments, lemond-offline dim-overlay.
- Backends: Lemonade self-card + backend table with state chips (installed
  / installing / uninstalling / unavailable / error). Per-row install /
  reinstall / uninstall actions wire to the Install / Uninstall /
  FLM-deb modal trio. Adds /backends route; redirects /providers for
  back-compat (renamed in v2 IA).
- Logs: unified merged journal with source toggle (merged / hal0 /
  lemond), level + slot filter, search-with-highlight, grouped-error
  collapse for adjacent same-request_id frames, floating jump-to-live
  pill with +N pending badge. Preserves PR-14 LemonadeJournalPanel —
  source=lemond renders the existing WS-streamed panel inline (no
  duplicate streaming logic). ws-disconnect banner wires through
  useBannerStore.
- Agent: 5-tab surface (Overview / Inbox / Skills / Memory / Personas).
  Inbox preserves ADR-0004 §5 wiring via existing AgentInboxTab +
  AgentApprovalRow. NoBundledAgentCard radio (pi-coder vs Hermes) +
  install path. Personas grid with PersonaEditModal (name / slot /
  tone / system prompt / allowed tools). no-agent banner via store.

Sub-components live under components/{hardware,backends,logs}/ and
components/agent/. New extras-v2.spec.ts covers the four routes,
backend modal trio, persona save flow, NoBundledAgentCard install
flow, and the no-agent banner. Existing hardware / logs /
lemonade-journal specs adapted to the new view shapes while
preserving test intent.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 Slots /slots view — SlotCard + NPU trio + Create/Edit/Swap modals (slice #170) (#192)

* feat(ui): v2 Slots /slots view — SlotCard + NPU trio + Create/Edit/Swap modals (slice #170)

Replaces the v1 list+capability-cards hybrid with the v2 grouped-card layout from
slots.jsx. Sections render per-type slot grids (Chat / Embed / Voice / Image /
Custom) plus a dedicated NPU rollup. Adds skip-path 6-card grid + banner #19.

What landed
-----------
- `ui/src/views/Slots.vue` — rewritten. Grouped sections, hotkey `N`, route-driven
  EditSlotDrawer via `/slots/:name`, skip-path detection (6 EmptySlotCards +
  `skip-path` banner), NPU variant toggle.
- `ui/src/components/SlotCard.vue` — rewritten to match slots.jsx::SlotCard.
  State-dot motion, per-type metric strip, inline swap trigger, ⋯ overflow menu,
  [CPU] chip preserved (provider=kokoro), coresident badge preserved.
- `ui/src/components/slots/{NpuBlock,NpuReactor,EmptySlotCard,ErrorSlotCard,
  CreateSlotModal,EditSlotDrawer,InlineSwapPopover,SlotOverflowMenu}.vue` —
  new components. NPU trio rendered as one rollup with two variants toggled
  from useTweaksStore.npuVariant ('block' default, 'reactor' tweak).
- `ui/src/components/primitives/{Modal,Drawer}.vue` — added optional `title-id`
  prop so callers can wire `aria-labelledby` to a stable selector (preserves
  a11y test intent across the SlotCard/EditSlotDrawer rewrite).
- `ui/src/stores/tweaks.js` — npuVariant defaults to 'block'; legal values
  'block' | 'reactor'.

Deleted
-------
- `ui/src/components/capabilities/{CapabilitiesSection,EmbedCard,VoiceCard,
  ImgCard}.vue` — capability cards collapsed into the SlotCard grid per the
  v2 brief. NPUBackendCard kept (still used by Dashboard.vue).

Tests
-----
- New `ui/tests/e2e/specs/slots-v2.spec.ts` (10 tests): skip-path + banner #19,
  hotkey N, /slots/:name drawer routing, grouped sections, both NPU variants,
  per-type metric strip, KV%='—' for GPU llm, overflow menu, ErrorSlotCard.
- Adapted `slot-lifecycle.spec.ts`, `models-slots-refactor.spec.ts`,
  `lemonade-voice-chip.spec.ts`, `dashboard-lemonade-state.spec.ts` to the
  new selectors (`.slot[data-slot-name=...]`, `#create-slot-*`, overflow
  menu for Delete, NpuBlock for trio, `[aria-hidden=true]` for Drawer close).
- Full Playwright suite: 80/80 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(slots): skip 5 models-side tests duplicated by models-v2.spec from #171

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): v2 polish — skeletons + a11y + TopBar overflow + drift banners (slice #175) (#197)

Final pass before the dash-v2 cutover. Adds the loading-state polish,
keyboard-a11y improvements, and external-link surface the v0.3 design
calls for.

  - 5 skeleton variants in ui/src/components/skeletons/: SlotCard,
    SnapshotRow, JournalLine, NpuSubRow, ModelRow. All use a shared
    `.skel` shimmer rule + respect prefers-reduced-motion. Mounted in
    SnapshotStrip / Slots / Logs / Models as initial-load fallbacks
    (gated on !system.status or empty loading lists so re-polls don't
    flash skeletons).
  - Skip-link in App.vue jumps to #main-content; styled in style.css
    as the first focusable element on every page.
  - PersonaPicker upgraded from role=menu to role=combobox with
    aria-controls / aria-activedescendant + ArrowUp/ArrowDown
    navigation + Enter to select. Options carry role=option +
    aria-selected.
  - Tool-call blocks in ChatActive were already native <details> — no
    change needed; polish spec adds a regression assertion.
  - --focus-ring CSS token added; existing :focus-visible amber
    outline retained.
  - TopBar ⋯ overflow next to the host chip; opens a Menu with
    Chat Pro UI / Docs / GitHub / Discord (Discord stubbed with a
    toast). External links open via target=_blank + rel=noopener.
  - Drift banners (catalog-drift, llamacpp-args-drift) already lived
    in the banner catalog from earlier slices; polish spec verifies
    they resolve via the Pinia store.
  - @axe-core/playwright added; polish.spec.ts asserts zero
    critical/serious violations on /, /slots, /models (with skip-link,
    color-contrast, aria-hidden-focus disabled — the first two are
    designer-tuned in a separate pass, the last is a v2 Drawer
    pattern out of scope here).
  - polish.spec.ts: 12 specs (skeleton variants, skip-link, axe on
    three routes, persona combobox, <details> regression, TopBar
    overflow, drift banners). Full suite 133 passed + 6 skipped.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ui): delete v1-era orphans (slice #176 cutover)

Slice #176 v0.2.1 dashboard cutover dead-code sweep. After 8 prior
slices replaced the v1 dashboard end-to-end, audit confirmed these
modules have zero importers and zero router refs:

Components
- ui/src/components/EmptyState.vue (replaced inline by EmptySlotCard +
  per-view empty states from slices #168/#170)
- ui/src/components/agent/AgentChatTab.vue (PTY-tap surface dropped
  when v0.2 narrowed Agent → Hermes-only, per
  feedback_hal0_agents_v0.2_narrow_to_hermes)
- ui/src/components/capabilities/ (CapabilityToggle + NPUBackendCard —
  v1 capability-row UX superseded by SlotCard + NpuBlock from slice
  #170)

Composables
- ui/src/composables/useAutoscroll.js (folded into ChatActive)
- ui/src/composables/useCapabilities.js (capabilities surface gone)
- ui/src/composables/useSSE.js (useEvents is the v2 SSE primitive)

Tidied one stale comment in useMock.js that named the deleted
useCapabilities composable.

Verification
- npm run build clean
- ruff format --check src tests + ruff check src tests clean
- npm run test:e2e: 132 passed / 6 skipped (1 LiveTimeline flake passes
  on retry — CI has retries: 1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thinmintdev thinmintdev deleted the feat/dash-v2-1b-stores branch May 27, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant