You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Today apps/loopover-miner-ui has no chat UI at all — confirmed by audit, no chat-shaped component exists anywhere in apps/loopover-miner-ui/src/**, and none of @loopover/ui-kit's components are chat-shaped either (nearest is command.tsx, a filtered-list palette, not a message thread). The redesign's architecture decision keeps the routed dashboard as primary content (apps/loopover-miner-ui/src/routes/__root.tsx renders a fixed header/nav and a <main className="max-w-5xl"> wrapping <Outlet/>, followed by <GrafanaFooterLink/>, currently at __root.tsx:8-39) and adds a persistent, collapsible ~380px chat rail mounted once in __root.tsx so it survives route navigation across all four existing routes (index.tsx, run-history.tsx, portfolio.tsx, ledgers.tsx). On narrow viewports the rail collapses to a slide-over by reusing packages/loopover-ui-kit/src/components/sidebar.tsx's existing mobile-sheet mechanism for that one responsive behavior only.
This issue is the integration point, not a build-from-scratch issue. Five prerequisite issues each build one standalone, independently-reviewable piece:
the chat rail shell issue — mounts the persistent collapsible rail container in apps/loopover-miner-ui/src/routes/__root.tsx and wires its narrow-viewport slide-over behavior off sidebar.tsx's mobile-sheet mechanism.
the chat composer issue — a submit-on-Enter (Shift+Enter for newline), auto-growing input built from the shared packages/loopover-ui-kit/src/components/textarea.tsx/input.tsx/button.tsx primitives (none of which have submit or auto-grow logic today).
the chat message list issue — the scrollable message timeline, built on packages/loopover-ui-kit/src/components/scroll-area.tsx and avatar.tsx, and depending on LoadingState/EmptyState/ErrorState/StateBoundary being ported from apps/loopover-ui/src/components/site/state-views.tsx into @loopover/ui-kit proper first (confirmed reusable as-is by the audit landed in PR docs(ui): audit ui-kit for existing chat-adjacent UI primitives #6474, docs(ui): audit ui-kit for existing chat-adjacent UI primitives).
the streaming renderer issue — the live, chunked text-rendering consumer (there is no EventSource/ReadableStream/streaming consumer anywhere in either app today).
the read-only chat backend issue — the server-side endpoint that grounds chat answers in AMS's own local data using only the 11 existing, entirely read-only loopover_miner_* MCP tools already registered in packages/loopover-miner/bin/loopover-miner-mcp.js (ping, get_portfolio_dashboard, get_manage_status, list_claims, get_audit_feed, get_run_state, list_plans, get_plan, get_governor_decisions, status, get_calibration_report). No new tool surface ships in v1 — this is the same middleware pattern the existing routes already use (e.g. GET /api/run-state is backed by a vite-run-state-api.ts-style middleware per apps/loopover-miner-ui/src/lib/run-history.ts:6).
By the time this issue is picked up, all five of the above are merged and independently reviewed. Nothing here introduces a new design decision: it is pure wiring — mount the composer and message list inside the rail shell's content slot, pipe composer submissions to the backend endpoint, pipe the backend's streamed response through the streaming renderer into the message list, and let loading/empty/error states render via the now-shared StateBoundary primitives. This is why the issue is contributor-eligible even though "chat" as a feature area carries real safety scope elsewhere in the redesign — the safety-relevant surface (action-dispatch: discover/attempt, portfolio release/requeue, governor pause/resume) is explicitly not part of this issue and ships later, gated behind its own config flag, off by default.
Requirements
⚠️ Read this before starting. This issue's entire deliverable lives under apps/loopover-miner-ui/src/** (the AMS miner dashboard). It is a different app, a different audience, and a different scope than the separate, still-unscoped maintainer-chat work that lives in apps/loopover-ui/** — that work has its own issue and is explicitly excluded from this redesign. A PR that adds or edits any file under apps/loopover-ui/** as part of this issue does NOT resolve it and will be closed. This issue is also read-only: it must not add, call, or stub any write/action endpoint (/api/portfolio-queue/release, /api/portfolio-queue/requeue, /api/governor/pause, /api/governor/resume, or any new discover/attempt-mirroring route) from the chat surface. A PR that wires the composer to trigger any of those, even behind a feature flag, does NOT resolve this issue and will be closed — action-dispatch is a separate, later issue.
The chat panel built here must be strictly ask-a-question / read-only: the only network traffic the composer triggers is a request to the read-only chat backend endpoint delivered by the read-only chat backend issue. No other endpoint may be called from any file added or edited by this issue.
Import and mount the composer, message list, and streaming renderer components exactly as built by their respective prerequisite issues — do not fork, reimplement, or duplicate their internals inline in this issue's integration code. If a needed prop/hook/event doesn't exist on one of those components, that is a signal to fix the upstream component in its own PR, not to route around it here.
The chat panel must mount inside the rail shell's existing content slot introduced by the chat rail shell issue in apps/loopover-miner-ui/src/routes/__root.tsx. Do not create a second, parallel rail container, and do not move, wrap, or restyle the routed <Outlet/> or any of the four existing route files (routes/index.tsx, routes/run-history.tsx, routes/portfolio.tsx, routes/ledgers.tsx).
Do not touch the four existing routes' data-fetching layer: apps/loopover-miner-ui/src/lib/use-polled-fetch.ts, lib/run-history.ts, lib/portfolio-queue.ts, lib/portfolio-queue-actions.ts, and lib/ledgers.ts must be unmodified by this PR. Their poll cadence (DEFAULT_POLL_INTERVAL_MS) and the existing release/requeue/pause/resume buttons are out of scope here — that restyle-only work belongs to the separate visual-polish track.
Grounding must go only through the 11 existing loopover_miner_* read-only tools (or the HTTP contract the read-only chat backend issue exposes over them) registered in packages/loopover-miner/bin/loopover-miner-mcp.js. Do not add a 12th tool, and do not touch src/mcp/server.ts or packages/loopover-engine/src/miner/local-write-tools.ts (the separate, write-capable ORB tool surface) — none of that is reachable from a read-only miner chat and pulling it in would cross the exact boundary this issue is scoped to stay inside of.
Loading, empty, and error states inside the mounted chat panel (initial load, no-messages-yet, backend-request-failed) must use the ported LoadingState/EmptyState/ErrorState/StateBoundary primitives from @loopover/ui-kit (post-port destination of apps/loopover-ui/src/components/site/state-views.tsx) via whatever prop contract the chat message list issue exposes for them — not a fifth hand-rolled inline loading/error/empty branch. (The four existing routes each already hand-duplicate that exact 3-branch pattern — run-history.tsx:26-42, portfolio.tsx:39-56, ledgers.tsx:117-134 — this issue must not add a fifth copy.)
The composer must be disabled (not merely visually dimmed) while a response is streaming, and re-enabled on stream completion or error, so a user cannot fire a second request into an in-flight one.
Verify the mounted chat panel renders correctly in both of the rail shell's two presentations: the persistent ~380px rail on desktop widths and the slide-over sheet on narrow viewports (reusing sidebar.tsx's mobile-sheet mechanism per the chat rail shell issue) — this issue is the first point either presentation actually contains real content, so it is the first real test of both.
No new config flag is introduced by this issue. A future action-dispatch phase will add one (off by default, per house convention) — do not preemptively add a flag or a disabled action-button affordance here.
Deliverables
A chat panel integration component/hook in apps/loopover-miner-ui/src/** that composes the composer, message list, and streaming renderer inside the rail shell's content slot
Request/response plumbing: composer submit → read-only chat backend endpoint → streaming renderer → message list append, with the composer disabled for the duration of an in-flight stream
Loading/empty/error states wired through the ported StateBoundary family, not a hand-rolled branch
Manual verification (screenshots or recording in the PR) of the working chat panel in both the desktop persistent-rail presentation and the narrow-viewport slide-over presentation
Confirmation (in the PR description) that none of the four existing routes' files or their lib/*.ts fetchers were modified
Test Coverage Requirements
apps/loopover-miner-ui/** is outside Codecov's coverage.include (only src/** is measured — see CLAUDE.md), so this PR is not Codecov-patch-gated. That does not relax the bar: npm run test:ci must still pass locally and in CI, and this issue's whole value is the first end-to-end proof that chat works, so it needs real component/integration tests covering:
composer submit triggers exactly one request to the read-only backend endpoint and no other endpoint
the composer is disabled while a stream is in flight and re-enabled after completion and after an error
a successful streamed response ends up appended to the message list in the correct order
the loading, empty, and error states each render via the shared StateBoundary primitives (not an inline branch)
the panel renders inside both the persistent-rail and slide-over presentations without throwing
Expected Outcome
After this ships, opening the miner dashboard shows a working, persistent, collapsible chat rail that survives navigation across all four routes. Typing a question into the composer streams a real, backend-grounded answer — grounded in the 11 existing read-only loopover_miner_* tools — into the message list, with correct loading/empty/error states in both the desktop rail and mobile slide-over presentations. This is the first point the chat feature is end-to-end functional, in a strictly read-only, ask-a-question capacity; no action-dispatch exists yet, and none of the four existing dashboard routes or their data-fetching logic are touched.
Links & Resources
apps/loopover-miner-ui/src/routes/__root.tsx — current header/nav/<Outlet/>/GrafanaFooterLink shell (:8-39); the rail shell issue's mount point, and this issue's integration point
apps/loopover-miner-ui/src/routes/index.tsx, run-history.tsx, portfolio.tsx, ledgers.tsx — existing routes, must remain untouched
apps/loopover-miner-ui/src/lib/use-polled-fetch.ts, lib/run-history.ts, lib/portfolio-queue.ts, lib/portfolio-queue-actions.ts, lib/ledgers.ts — existing data layer, must remain untouched
packages/loopover-ui-kit/src/components/scroll-area.tsx, avatar.tsx, textarea.tsx, input.tsx, button.tsx, sidebar.tsx — shared primitives the five prerequisite issues build on
packages/loopover-miner/bin/loopover-miner-mcp.js — the 11 read-only loopover_miner_* tools this issue's chat panel is grounded in
packages/loopover-engine/src/miner/local-write-tools.ts, src/mcp/server.ts — the separate, write-capable ORB tool surface this issue must not touch
the chat rail shell issue, the chat composer issue, the chat message list issue, the streaming renderer issue, and the read-only chat backend issue — the five direct prerequisites this issue integrates
Context
Today
apps/loopover-miner-uihas no chat UI at all — confirmed by audit, no chat-shaped component exists anywhere inapps/loopover-miner-ui/src/**, and none of@loopover/ui-kit's components are chat-shaped either (nearest iscommand.tsx, a filtered-list palette, not a message thread). The redesign's architecture decision keeps the routed dashboard as primary content (apps/loopover-miner-ui/src/routes/__root.tsxrenders a fixed header/nav and a<main className="max-w-5xl">wrapping<Outlet/>, followed by<GrafanaFooterLink/>, currently at__root.tsx:8-39) and adds a persistent, collapsible ~380px chat rail mounted once in__root.tsxso it survives route navigation across all four existing routes (index.tsx,run-history.tsx,portfolio.tsx,ledgers.tsx). On narrow viewports the rail collapses to a slide-over by reusingpackages/loopover-ui-kit/src/components/sidebar.tsx's existing mobile-sheet mechanism for that one responsive behavior only.This issue is the integration point, not a build-from-scratch issue. Five prerequisite issues each build one standalone, independently-reviewable piece:
apps/loopover-miner-ui/src/routes/__root.tsxand wires its narrow-viewport slide-over behavior offsidebar.tsx's mobile-sheet mechanism.packages/loopover-ui-kit/src/components/textarea.tsx/input.tsx/button.tsxprimitives (none of which have submit or auto-grow logic today).packages/loopover-ui-kit/src/components/scroll-area.tsxandavatar.tsx, and depending onLoadingState/EmptyState/ErrorState/StateBoundarybeing ported fromapps/loopover-ui/src/components/site/state-views.tsxinto@loopover/ui-kitproper first (confirmed reusable as-is by the audit landed in PR docs(ui): audit ui-kit for existing chat-adjacent UI primitives #6474,docs(ui): audit ui-kit for existing chat-adjacent UI primitives).EventSource/ReadableStream/streaming consumer anywhere in either app today).loopover_miner_*MCP tools already registered inpackages/loopover-miner/bin/loopover-miner-mcp.js(ping,get_portfolio_dashboard,get_manage_status,list_claims,get_audit_feed,get_run_state,list_plans,get_plan,get_governor_decisions,status,get_calibration_report). No new tool surface ships in v1 — this is the same middleware pattern the existing routes already use (e.g.GET /api/run-stateis backed by avite-run-state-api.ts-style middleware perapps/loopover-miner-ui/src/lib/run-history.ts:6).By the time this issue is picked up, all five of the above are merged and independently reviewed. Nothing here introduces a new design decision: it is pure wiring — mount the composer and message list inside the rail shell's content slot, pipe composer submissions to the backend endpoint, pipe the backend's streamed response through the streaming renderer into the message list, and let loading/empty/error states render via the now-shared
StateBoundaryprimitives. This is why the issue is contributor-eligible even though "chat" as a feature area carries real safety scope elsewhere in the redesign — the safety-relevant surface (action-dispatch:discover/attempt, portfolio release/requeue, governor pause/resume) is explicitly not part of this issue and ships later, gated behind its own config flag, off by default.Requirements
apps/loopover-miner-ui/src/routes/__root.tsx. Do not create a second, parallel rail container, and do not move, wrap, or restyle the routed<Outlet/>or any of the four existing route files (routes/index.tsx,routes/run-history.tsx,routes/portfolio.tsx,routes/ledgers.tsx).apps/loopover-miner-ui/src/lib/use-polled-fetch.ts,lib/run-history.ts,lib/portfolio-queue.ts,lib/portfolio-queue-actions.ts, andlib/ledgers.tsmust be unmodified by this PR. Their poll cadence (DEFAULT_POLL_INTERVAL_MS) and the existing release/requeue/pause/resume buttons are out of scope here — that restyle-only work belongs to the separate visual-polish track.loopover_miner_*read-only tools (or the HTTP contract the read-only chat backend issue exposes over them) registered inpackages/loopover-miner/bin/loopover-miner-mcp.js. Do not add a 12th tool, and do not touchsrc/mcp/server.tsorpackages/loopover-engine/src/miner/local-write-tools.ts(the separate, write-capable ORB tool surface) — none of that is reachable from a read-only miner chat and pulling it in would cross the exact boundary this issue is scoped to stay inside of.LoadingState/EmptyState/ErrorState/StateBoundaryprimitives from@loopover/ui-kit(post-port destination ofapps/loopover-ui/src/components/site/state-views.tsx) via whatever prop contract the chat message list issue exposes for them — not a fifth hand-rolled inline loading/error/empty branch. (The four existing routes each already hand-duplicate that exact 3-branch pattern —run-history.tsx:26-42,portfolio.tsx:39-56,ledgers.tsx:117-134— this issue must not add a fifth copy.)sidebar.tsx's mobile-sheet mechanism per the chat rail shell issue) — this issue is the first point either presentation actually contains real content, so it is the first real test of both.Deliverables
apps/loopover-miner-ui/src/**that composes the composer, message list, and streaming renderer inside the rail shell's content slotStateBoundaryfamily, not a hand-rolled branchlib/*.tsfetchers were modifiedTest Coverage Requirements
apps/loopover-miner-ui/**is outside Codecov'scoverage.include(onlysrc/**is measured — seeCLAUDE.md), so this PR is not Codecov-patch-gated. That does not relax the bar:npm run test:cimust still pass locally and in CI, and this issue's whole value is the first end-to-end proof that chat works, so it needs real component/integration tests covering:StateBoundaryprimitives (not an inline branch)Expected Outcome
After this ships, opening the miner dashboard shows a working, persistent, collapsible chat rail that survives navigation across all four routes. Typing a question into the composer streams a real, backend-grounded answer — grounded in the 11 existing read-only
loopover_miner_*tools — into the message list, with correct loading/empty/error states in both the desktop rail and mobile slide-over presentations. This is the first point the chat feature is end-to-end functional, in a strictly read-only, ask-a-question capacity; no action-dispatch exists yet, and none of the four existing dashboard routes or their data-fetching logic are touched.Links & Resources
apps/loopover-miner-ui/src/routes/__root.tsx— current header/nav/<Outlet/>/GrafanaFooterLinkshell (:8-39); the rail shell issue's mount point, and this issue's integration pointapps/loopover-miner-ui/src/routes/index.tsx,run-history.tsx,portfolio.tsx,ledgers.tsx— existing routes, must remain untouchedapps/loopover-miner-ui/src/lib/use-polled-fetch.ts,lib/run-history.ts,lib/portfolio-queue.ts,lib/portfolio-queue-actions.ts,lib/ledgers.ts— existing data layer, must remain untouchedapps/loopover-ui/src/components/site/state-views.tsx—LoadingState/EmptyState/ErrorState/StateBoundarysource, pending port into@loopover/ui-kit(see PR docs(ui): audit ui-kit for existing chat-adjacent UI primitives #6474's audit)packages/loopover-ui-kit/src/components/scroll-area.tsx,avatar.tsx,textarea.tsx,input.tsx,button.tsx,sidebar.tsx— shared primitives the five prerequisite issues build onpackages/loopover-miner/bin/loopover-miner-mcp.js— the 11 read-onlyloopover_miner_*tools this issue's chat panel is grounded inpackages/loopover-engine/src/miner/local-write-tools.ts,src/mcp/server.ts— the separate, write-capable ORB tool surface this issue must not touch