Skip to content

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

Merged
thinmintdev merged 2 commits into
feat/dash-v2-reworkfrom
feat/dash-v2-3-slots
May 23, 2026
Merged

feat(ui): v2 Slots /slots view — SlotCard + NPU trio + Create/Edit/Swap modals (slice #170)#192
thinmintdev merged 2 commits into
feat/dash-v2-reworkfrom
feat/dash-v2-3-slots

Conversation

@thinmintdev
Copy link
Copy Markdown
Contributor

Summary

Slice 7 of the dash v2 rework — replaces the v1 list+capability-cards hybrid on /slots with the v2 grouped-card layout from slots.jsx.

  • Grouped sections (Chat / Embed / Voice / Image / NPU / Custom), 2-col @ 1280 / 3-col @ 1600+, hotkey N opens Create modal.
  • SlotCard instrumented: state-dot motion (halo-glow / pulse), per-type metric strip (llm / embed / rerank / transcription / tts / image), inline swap , overflow menu, [CPU] chip preserved for provider=kokoro.
  • NPU trio rendered as a single rollup card with two variants — NpuBlock (default) and NpuReactor (tweak) — toggled from useTweaksStore.npuVariant.
  • EmptySlotCard skip-path: when zero slots are configured, render 6 seeded placeholder cards + raise banner chore(installer): remove dead installer/systemd/ directory #19 (skip-path). Each Configure button opens Create pre-filled.
  • CreateSlotModal (kebab-case + collision validation, device picker with NPU disabled when no FLM), EditSlotDrawer (520px, route-driven /slots/:name, restart-required markers, effective-flags preview), InlineSwapPopover (body-teleported), SlotOverflowMenu (View logs / Set default / Copy curl / Delete).
  • primitives/{Modal,Drawer} learn a title-id prop so callers can wire aria-labelledby to a stable selector — keeps a11y test intent stable across the SlotCard rewrite.
  • CapabilitiesSection / EmbedCard / VoiceCard / ImgCard deleted (folded into the SlotCard grid). NPUBackendCard kept since Dashboard still consumes it.

Test plan

  • npm run build clean
  • npm run test:e2e — full suite 80/80 green (including 26 slot-related tests across slots-v2.spec.ts, slot-lifecycle.spec.ts, models-slots-refactor.spec.ts, lemonade-voice-chip.spec.ts, dashboard-lemonade-state.spec.ts)
  • New slots-v2.spec.ts covers: skip-path 6-card grid + banner chore(installer): remove dead installer/systemd/ directory #19, hotkey N, /slots/:name → EditSlotDrawer, grouped sections, both NPU variants (block + reactor), per-type metric strip, KV%='—' for GPU llm slot (Lemonade gap), overflow menu items, ErrorSlotCard banner on failed load
  • [CPU] chip from PR-15 preserved (data-testid="cpu-only-chip" + verbatim tooltip)
  • PR-11 coresident_group slot field still drives the trio rollup
  • No leaks to /home/halo/dev/hal0/ or /home/halo/dev/hal0-v2/ worktrees

🤖 Generated with Claude Code

…ap 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>
@thinmintdev thinmintdev force-pushed the feat/dash-v2-3-slots branch from 2e7b288 to 19df137 Compare May 23, 2026 06:40
@thinmintdev thinmintdev merged commit 2da3a83 into feat/dash-v2-rework May 23, 2026
4 checks passed
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-3-slots 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