feat(ui): v0.2.1 dashboard rewrite — cutover (slice #176, closes #148)#199
Merged
Conversation
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>
…ast/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>
…k/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.
…aywright 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>
…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.
…poser (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>
…+ 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>
…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>
…s) + 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>
…bon + 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>
… 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>
…ap 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>
…ers (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>
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>
Brings in PR-16 (omni-router), PR-17 (bundles + bundle picker), and PR-18 (legacy chat-surface) that landed on main while the feat/dash-v2-rework train was in flight. Conflict resolutions - ui/src/router.js — kept both route definitions (bundle-picker from PR-17 + primitives-sandbox from slice #167). - ui/src/views/Dashboard.vue — kept v2 (slice #169 chat-first view). Origin/main's variant was the v1 dashboard with PR-18 ChatSurface bolted on; the slice #169 redesign supersedes it end-to-end. - ui/tests/e2e/specs/dashboard.spec.ts — kept v2 spec suite. PR-18 had deleted the v1 dashboard.spec; the v2 spec covers the slice #169 surface that ships now. Followups - PR-18's ChatSurface.vue + chat-surface.spec.ts were tightly coupled to the v1 dashboard markup. v2 already carries PersonaPicker + a Composer with no-tools/voice/attach states; the deeper PR-18 features (image modal, OmniRouter opt-in toggle, tool-call inline cards) need a follow-up issue to port into v2's chat surface. Removed both files in this merge to keep the suite green. Verification - npm run build clean - ruff format --check src tests + ruff check src tests clean - npm run test:e2e: 139 passed / 6 skipped (was 132 before the merge, new specs from PR-17 bundle-picker accounted for the delta) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
5 tasks
thinmintdev
added a commit
that referenced
this pull request
May 23, 2026
Backend
-------
- New ``hal0.dispatcher.npu_swap_status`` module with a pure
``compute_npu_swap_status()`` helper plus an async
``fetch_npu_swap_status()`` wrapper. The signal is published when:
* an ``device=npu, type=llm, enabled=true`` slot exists,
* its ``model.default`` differs from the FLM model currently in
lemond ``/v1/health.loaded[]``, and
* a ``recipe=flm`` entry is still serving (old trio chat).
- New ``GET /api/npu/swap-status`` route returning
``{in_progress, from_model, to_model}``. Read-only, always 200,
degrades to ``in_progress=false`` on lemond outage so the global
``lemond-offline`` banner owns that surface.
- PR-11's ``SlotManager._check_npu_exclusivity`` already gates the
write path with a 409 ``NpuExclusivityViolation``; verified intact.
- PR-16's ``route_to_chat`` NPU↔NPU refusal already in place
(``hal0.omni_router.route_to_chat``); verified intact.
Frontend (v2 surface, post-#199 cutover)
-----------------------------------------
- ``useNpuSwapStatus`` composable polls the new endpoint on a 2s
cadence and reflects the result into the slots-scoped banner store,
driving the existing ``npu-swap`` catalog entry with live
``from_model → to_model`` strings.
- ``NpuBlock`` + ``NpuReactor`` accept a ``swapStatus`` prop and
render a spinner + "Loading <to_model>…" line in place of the chat
sub-row's static label whenever ``in_progress`` is true.
- ``EditSlotDrawer`` pops a ``ConfirmDialog`` whenever the user saves
a changed model on a slot identified as NPU LLM (device=npu /
backend=flm + type=llm/llama-server/flm). Cancel keeps the form
open; Confirm runs the existing /swap fast path.
Tests
-----
- ``tests/dispatcher/test_npu_swap_status.py`` — 20 cases covering
the pure helper, the async wrapper's error-swallowing contract, and
a TestClient smoke test of the new route.
- ``ui/tests/e2e/specs/npu-swap-ux.spec.ts`` — 5 cases:
1. banner + spinner render when ``in_progress=true``,
2. banner auto-dismisses on flip to ``false``,
3. swap confirmation modal pops on intentional NPU chat-model
change,
4. cancel leaves the form intact + no POST,
5. PR-11's 409 ``slot.npu_exclusivity_violation`` surfaces as a
toast without crashing the dashboard.
Refs: plan §5.3, §11 PR-20. ADR-0008 §5. ADR-0009.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Final ship of the v0.2.1 dashboard. Closes the Phase 3 epic (#148) by
merging the 9-slice
feat/dash-v2-reworktrain intomainplus thisslice #176 cleanup pass.
What's in this train
/Dashboard — chat-first hero + SnapshotStrip + PersonaPicker + Composer (5 states) + ChatActive/ChatEmpty/slots— SlotCard + NpuBlock + Create/Edit/Swap modals + ErrorSlotCard/models— 3-pane catalog + AddByHF + Delete + 7-state DownloadRow/firstrun— bundle picker (pick/confirm/progress) + grid+matrix variants/settings— 9 sections + Lemonade guardrails + OmniRouter + secrets/memory/auth/hardware/backends/logs/agent+ Backend modals + Persona edit/agents/mcpMCP Servers — KPI strip + clients ribbon + LiveTimeline + modalsSlice #176 (this PR) — cleanup
ui/src/components/EmptyState.vueui/src/components/agent/AgentChatTab.vue(v0.2 narrowed Agent → Hermes-only)ui/src/components/capabilities/CapabilityToggle.vueui/src/components/capabilities/NPUBackendCard.vueui/src/composables/useAutoscroll.jsui/src/composables/useCapabilities.jsui/src/composables/useSSE.jsorigin/main(PR-16 omni-router, PR-17 bundles, PR-18 chat-surface)ChatSurface.vue+chat-surface.spec.tswere coupled to the v1dashboard markup; v2 supersedes them via its own PersonaPicker + Composer.
Deeper PR-18 features (image modal, OmniRouter opt-in toggle, tool-call
inline cards) need a follow-up issue to port into v2's chat surface.
Acceptance criteria (issue #176)
ruff format --check src tests+ruff check src tests+npm run test:e2e)feat/dash-v2-rework→main(this PR)v0.2.1-alpha.1tagged + released on GitHub (orchestrator owns)Hal0ai/hal0-web(orchestrator owns)Test plan
cd ui && npm run build— cleancd ui && npm run test:e2e— 139 passed / 6 skippedruff format --check src tests && ruff check src tests— cleanFollow-ups for the orchestrator
mainv0.2.1-alpha.1(NOT marked prerelease so/latestresolves, per the v0.1.0-alpha launch pattern)Closes #148
Closes #176