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
apps/loopover-miner-ui/src/routes/__root.tsx (lines 8-39) currently renders a fixed <header> (uppercase mono kicker "LoopOver Miner" + <h1>Local dashboard</h1> + a 4-link nav for Overview/Run history/Portfolio/Ledgers), a <main className="max-w-5xl"> wrapping the routed <Outlet/>, and a <GrafanaFooterLink/>. All four routes (index.tsx, run-history.tsx, portfolio.tsx, ledgers.tsx) render inside that single <Outlet/> and are swapped in/out by TanStack Router on navigation — there is currently no persistent UI element that survives a route change other than the header itself.
The miner dashboard redesign keeps this dashboard-first shape: the routed pages stay the primary content area (restyled in place by separate, later issues), and a persistent chat rail is added alongside them rather than replacing navigation. Because a component rendered inside a route file gets unmounted/remounted on every navigation, the rail has to be mounted exactly once, in __root.tsx itself, so its open/closed state and (eventually) its conversation state survive moving between routes.
@loopover/ui-kit (packages/loopover-ui-kit/src/components/) already ships a full sidebar.tsx component and a hooks/use-mobile.tsx breakpoint hook, confirmed unused anywhere in apps/loopover-miner-ui today. sidebar.tsx's own mobile behavior — collapsing to a Sheet-based slide-over below use-mobile.tsx's breakpoint instead of staying docked — is the exact mechanism this rail must reuse for its own narrow-viewport behavior, so the app doesn't end up with two different "how do we collapse a side panel on mobile" implementations.
This issue is scoped as a pure structural shell: no chat message list, no composer, no streaming, no backend call of any kind. The recently-merged ui-kit chat-primitives audit (apps/loopover-ui/src/chat-ui-primitives-audit.md, landed via PR #6474) found that state-views.tsx's LoadingState/EmptyState/ErrorState/StateBoundary need to be ported from apps/loopover-ui into @loopover/ui-kit proper before any chat component that renders its own async state (the eventual message list) is built — that porting requirement does not apply here, since this rail renders no async state and consumes none of those primitives. This issue has no dependency on that port and can proceed independently of it.
Requirements
⚠️ Read this before starting. This issue is a structural shell only. Do not build a message composer, a message list/scrollback, a typing indicator, streaming text rendering, or any fetch/EventSource/backend call — those are separate, later issues layered on top of this one. Do not implement the narrow-viewport behavior as a new custom modal, a new matchMedia/CSS-only breakpoint, or any mobile pattern other than packages/loopover-ui-kit/src/components/sidebar.tsx's existing Sheet-based slide-over, gated by packages/loopover-ui-kit/src/hooks/use-mobile.tsx. A PR that adds a working composer, wires any endpoint, invents a second mobile-collapse mechanism, or defines its own breakpoint constant instead of reusing use-mobile.tsx's does not resolve this issue and will be closed.
Add a new rail component under apps/loopover-miner-ui/src/components/ (e.g. apps/loopover-miner-ui/src/components/chat-rail.tsx, or a small set of files in a chat-rail/ subdirectory) — apps/loopover-miner-ui/src currently has no local components/ directory, so this is new.
Mount the rail exactly once inside apps/loopover-miner-ui/src/routes/__root.tsx, alongside the existing <header>/<main className="max-w-5xl">/<Outlet/>/<GrafanaFooterLink/>. It must not be mounted inside, imported by, or duplicated across any of the four route files (index.tsx, run-history.tsx, portfolio.tsx, ledgers.tsx) — mounting it per-route would defeat the entire point of surviving navigation.
On wide viewports the rail is docked at ~380px wide, rendered beside (not overlapping) the existing routed content — this requires restructuring __root.tsx's layout container into a row that holds the main content area and the rail side by side, without changing the visual output of the four existing routes.
Provide a visible collapse/expand toggle. Collapsing must not unmount the rail's own state (so re-expanding doesn't lose it later, once real content exists) — it only changes whether the ~380px panel is visually docked/visible.
Collapse/expand state must live in __root.tsx (or a hook/context it owns), not in a route file, so it persists across client-side navigation between the four routes. It does not need to persist across a full page reload — no localStorage/sessionStorage requirement for this issue; in-memory state scoped to the __root.tsx mount is sufficient.
Below use-mobile.tsx's existing breakpoint, the rail renders as a slide-over/sheet overlay instead of the docked ~380px panel, using the same Sheet component and use-mobile.tsx hook that sidebar.tsx already uses for its own mobile mode. Import and reuse that hook directly rather than re-deriving the breakpoint value.
Content is static placeholder only: e.g. a header label ("Chat" or similar) and a single placeholder/empty-state line. No <textarea>/<input> submit handling, no message bubbles, no scrollback.
No new usage of @loopover/ui-kit's scroll-area.tsx, avatar.tsx, or state-views.tsx primitives in this PR — those belong to the follow-up message-list/composer work, not this shell.
No TanStack Router route-tree changes — this is a __root.tsx + new-component change only.
No config flag is needed for this issue. The config-gated piece described in the wider chat design is action-dispatch specifically (a separate, later issue); this shell carries no privileged or write behavior, so it always renders.
Deliverables
apps/loopover-miner-ui/src/components/chat-rail.tsx (or equivalent small set of files) — the new rail shell component
apps/loopover-miner-ui/src/routes/__root.tsx — updated to mount the rail once, in a layout that places it beside the existing <main>/<Outlet/> without altering the four routes' own content
Collapse/expand toggle control wired to component/context-local state that survives client-side route navigation
This change is entirely under apps/loopover-miner-ui/**, which is outside Codecov's coverage.include (only src/** is measured — see the repo's contributing skill/reference doc), so this PR will not be gated on a Codecov patch-coverage percentage. That does not mean untested: npm run test:ci still runs the full local gate including this app's existing tests, and this PR must add real tests for the new behavior, following this app's existing test conventions. At minimum, cover:
The rail renders once inside __root.tsx and its open/closed state does not reset when simulating a client-side navigation between two of the four routes (e.g. Overview → Portfolio).
The collapse/expand toggle correctly flips the rail's docked/collapsed rendered state (and any aria-expanded/aria-hidden-equivalent attributes used).
Below use-mobile.tsx's mobile breakpoint, the rail renders via the Sheet/slide-over path rather than the docked ~380px panel (assert the same mechanism, not a re-implementation).
The four existing routes' own rendered output is unchanged by the __root.tsx restructuring — a regression check that this is a layout-only addition.
Expected Outcome
After this ships, apps/loopover-miner-ui has a persistent ~380px chat rail visible from every route (Overview/Run history/Portfolio/Ledgers) that survives navigation without being re-mounted per page, is collapsible/expandable via a visible control, and correctly degrades to a slide-over sheet on narrow viewports using the exact mechanism sidebar.tsx already ships elsewhere in the design system — with placeholder-only content, no message list, composer, or backend wiring of any kind. The four existing routes render exactly as they did before. This becomes the mount point that the chat message-list-and-composer issue, the chat streaming-renderer issue, and (once their own config flag and endpoint wiring are decided) the chat action-dispatch scaffolding issue and its three action-family children build inside, without any of them needing to touch __root.tsx's mounting logic again.
Links & Resources
apps/loopover-miner-ui/src/routes/__root.tsx (lines 8-39) — mount point for the rail
apps/loopover-miner-ui/src/routes/index.tsx, run-history.tsx, portfolio.tsx, ledgers.tsx — must render visually unchanged
packages/loopover-ui-kit/src/components/sidebar.tsx — source of the mobile-sheet mechanism to reuse
packages/loopover-ui-kit/src/hooks/use-mobile.tsx — breakpoint hook to reuse, not reimplement
apps/loopover-miner-ui/src/styles.css — already imports @loopover/ui-kit/theme.css, so no new token wiring is needed for this shell
The ui-kit chat-primitives audit (apps/loopover-ui/src/chat-ui-primitives-audit.md, merged via PR docs(ui): audit ui-kit for existing chat-adjacent UI primitives #6474) — confirms scroll-area.tsx/avatar.tsx/state-views.tsx as reusable building blocks for the next chat issues, not needed here
The chat message-list-and-composer issue, the chat streaming-renderer issue, and the chat action-dispatch scaffolding issue (plus its three action-family children) all build on top of this rail shell but are separate, later issues — none of their scope belongs in this PR
The separate maintainer-chat scoping issue (ORB's read-only maintainer chat) is unrelated scope with its own open audience/backend/placement questions — nothing in this issue should reference or share code with it
Context
apps/loopover-miner-ui/src/routes/__root.tsx(lines 8-39) currently renders a fixed<header>(uppercase mono kicker "LoopOver Miner" +<h1>Local dashboard</h1>+ a 4-link nav for Overview/Run history/Portfolio/Ledgers), a<main className="max-w-5xl">wrapping the routed<Outlet/>, and a<GrafanaFooterLink/>. All four routes (index.tsx,run-history.tsx,portfolio.tsx,ledgers.tsx) render inside that single<Outlet/>and are swapped in/out by TanStack Router on navigation — there is currently no persistent UI element that survives a route change other than the header itself.The miner dashboard redesign keeps this dashboard-first shape: the routed pages stay the primary content area (restyled in place by separate, later issues), and a persistent chat rail is added alongside them rather than replacing navigation. Because a component rendered inside a route file gets unmounted/remounted on every navigation, the rail has to be mounted exactly once, in
__root.tsxitself, so its open/closed state and (eventually) its conversation state survive moving between routes.@loopover/ui-kit(packages/loopover-ui-kit/src/components/) already ships a fullsidebar.tsxcomponent and ahooks/use-mobile.tsxbreakpoint hook, confirmed unused anywhere inapps/loopover-miner-uitoday.sidebar.tsx's own mobile behavior — collapsing to aSheet-based slide-over belowuse-mobile.tsx's breakpoint instead of staying docked — is the exact mechanism this rail must reuse for its own narrow-viewport behavior, so the app doesn't end up with two different "how do we collapse a side panel on mobile" implementations.This issue is scoped as a pure structural shell: no chat message list, no composer, no streaming, no backend call of any kind. The recently-merged ui-kit chat-primitives audit (
apps/loopover-ui/src/chat-ui-primitives-audit.md, landed via PR #6474) found thatstate-views.tsx'sLoadingState/EmptyState/ErrorState/StateBoundaryneed to be ported fromapps/loopover-uiinto@loopover/ui-kitproper before any chat component that renders its own async state (the eventual message list) is built — that porting requirement does not apply here, since this rail renders no async state and consumes none of those primitives. This issue has no dependency on that port and can proceed independently of it.Requirements
apps/loopover-miner-ui/src/components/(e.g.apps/loopover-miner-ui/src/components/chat-rail.tsx, or a small set of files in achat-rail/subdirectory) —apps/loopover-miner-ui/srccurrently has no localcomponents/directory, so this is new.apps/loopover-miner-ui/src/routes/__root.tsx, alongside the existing<header>/<main className="max-w-5xl">/<Outlet/>/<GrafanaFooterLink/>. It must not be mounted inside, imported by, or duplicated across any of the four route files (index.tsx,run-history.tsx,portfolio.tsx,ledgers.tsx) — mounting it per-route would defeat the entire point of surviving navigation.__root.tsx's layout container into a row that holds the main content area and the rail side by side, without changing the visual output of the four existing routes.__root.tsx(or a hook/context it owns), not in a route file, so it persists across client-side navigation between the four routes. It does not need to persist across a full page reload — nolocalStorage/sessionStoragerequirement for this issue; in-memory state scoped to the__root.tsxmount is sufficient.use-mobile.tsx's existing breakpoint, the rail renders as a slide-over/sheet overlay instead of the docked ~380px panel, using the sameSheetcomponent anduse-mobile.tsxhook thatsidebar.tsxalready uses for its own mobile mode. Import and reuse that hook directly rather than re-deriving the breakpoint value.<textarea>/<input>submit handling, no message bubbles, no scrollback.@loopover/ui-kit'sscroll-area.tsx,avatar.tsx, orstate-views.tsxprimitives in this PR — those belong to the follow-up message-list/composer work, not this shell.__root.tsx+ new-component change only.Deliverables
apps/loopover-miner-ui/src/components/chat-rail.tsx(or equivalent small set of files) — the new rail shell componentapps/loopover-miner-ui/src/routes/__root.tsx— updated to mount the rail once, in a layout that places it beside the existing<main>/<Outlet/>without altering the four routes' own contentsidebar.tsx'sSheet+use-mobile.tsxmechanismTest Coverage Requirements
This change is entirely under
apps/loopover-miner-ui/**, which is outside Codecov'scoverage.include(onlysrc/**is measured — see the repo's contributing skill/reference doc), so this PR will not be gated on a Codecov patch-coverage percentage. That does not mean untested:npm run test:cistill runs the full local gate including this app's existing tests, and this PR must add real tests for the new behavior, following this app's existing test conventions. At minimum, cover:__root.tsxand its open/closed state does not reset when simulating a client-side navigation between two of the four routes (e.g. Overview → Portfolio).aria-expanded/aria-hidden-equivalent attributes used).use-mobile.tsx's mobile breakpoint, the rail renders via theSheet/slide-over path rather than the docked ~380px panel (assert the same mechanism, not a re-implementation).__root.tsxrestructuring — a regression check that this is a layout-only addition.Expected Outcome
After this ships,
apps/loopover-miner-uihas a persistent ~380px chat rail visible from every route (Overview/Run history/Portfolio/Ledgers) that survives navigation without being re-mounted per page, is collapsible/expandable via a visible control, and correctly degrades to a slide-over sheet on narrow viewports using the exact mechanismsidebar.tsxalready ships elsewhere in the design system — with placeholder-only content, no message list, composer, or backend wiring of any kind. The four existing routes render exactly as they did before. This becomes the mount point that the chat message-list-and-composer issue, the chat streaming-renderer issue, and (once their own config flag and endpoint wiring are decided) the chat action-dispatch scaffolding issue and its three action-family children build inside, without any of them needing to touch__root.tsx's mounting logic again.Links & Resources
apps/loopover-miner-ui/src/routes/__root.tsx(lines 8-39) — mount point for the railapps/loopover-miner-ui/src/routes/index.tsx,run-history.tsx,portfolio.tsx,ledgers.tsx— must render visually unchangedpackages/loopover-ui-kit/src/components/sidebar.tsx— source of the mobile-sheet mechanism to reusepackages/loopover-ui-kit/src/hooks/use-mobile.tsx— breakpoint hook to reuse, not reimplementapps/loopover-miner-ui/src/styles.css— already imports@loopover/ui-kit/theme.css, so no new token wiring is needed for this shellapps/loopover-ui/src/chat-ui-primitives-audit.md, merged via PR docs(ui): audit ui-kit for existing chat-adjacent UI primitives #6474) — confirmsscroll-area.tsx/avatar.tsx/state-views.tsxas reusable building blocks for the next chat issues, not needed here