Context
ChatRail's own header comment (apps/loopover-miner-ui/src/components/chat-rail.tsx:1-5) and in-code comment at line 68 state the design intent explicitly: "Collapsing only hides it (never unmounts it), so any future in-rail state is preserved across an expand/collapse cycle." On desktop (lines 76-85) this is true — the <aside hidden={!open}> wrapper keeps RailBody/ChatConversation mounted, just visually hidden.
On mobile (lines 36-56), the same RailBody is instead rendered inside @loopover/ui-kit's Sheet/SheetContent (packages/loopover-ui-kit/src/components/sheet.tsx:10,52+), which wraps Radix's Dialog.Root/Dialog.Content. Radix's Dialog.Content uses Presence internally and unmounts its subtree when open becomes false unless forceMount is passed — which it isn't anywhere in this codebase (confirmed via repo-wide grep for forceMount across apps/loopover-miner-ui, apps/loopover-ui, packages/loopover-ui-kit: zero hits).
So on a mobile viewport, closing the chat sheet (accidental tap-outside, Escape, explicit toggle) discards ChatConversation's entire messages/activeSource/streaming state (apps/loopover-miner-ui/src/components/chat/conversation.tsx:70-73) — the whole conversation and any in-progress typed draft (ChatComposer's own value state) is silently lost and reinitializes empty on reopen. Desktop and mobile behave inconsistently despite the code explicitly claiming otherwise.
Untested branch: apps/loopover-miner-ui/src/chat-rail.test.tsx covers docked-vs-sheet rendering, the toggle, and open-state persistence across route navigation, but nothing asserts that conversation content survives a mobile sheet close/reopen cycle — precisely the gap that would have caught this.
Requirements
Make mobile behavior match the documented intent (state survives close/reopen), the same way desktop already does. The most direct fix is passing forceMount to the relevant Radix Dialog.Content (via SheetContent's existing prop passthrough, if it has one — check before adding a new prop) combined with the existing hidden/visibility toggle pattern desktop already uses, so the sheet's content stays mounted but visually hidden when closed, matching the desktop <aside hidden={!open}> approach.
Deliverables
Test Coverage Requirements
apps/loopover-miner-ui is not covered by the src/** 99% patch gate - the new regression test is this issue's own coverage deliverable.
Expected Outcome
A mobile user's in-progress chat conversation and draft message survive closing and reopening the chat sheet, matching the component's own documented intent and desktop's actual behavior.
Links & Resources
apps/loopover-miner-ui/src/components/chat-rail.tsx:1-5,36-56,68,76-85, packages/loopover-ui-kit/src/components/sheet.tsx:10,52+, apps/loopover-miner-ui/src/components/chat/conversation.tsx:70-73
Context
ChatRail's own header comment (apps/loopover-miner-ui/src/components/chat-rail.tsx:1-5) and in-code comment at line 68 state the design intent explicitly: "Collapsing only hides it (never unmounts it), so any future in-rail state is preserved across an expand/collapse cycle." On desktop (lines 76-85) this is true — the<aside hidden={!open}>wrapper keepsRailBody/ChatConversationmounted, just visually hidden.On mobile (lines 36-56), the same
RailBodyis instead rendered inside@loopover/ui-kit'sSheet/SheetContent(packages/loopover-ui-kit/src/components/sheet.tsx:10,52+), which wraps Radix'sDialog.Root/Dialog.Content. Radix'sDialog.ContentusesPresenceinternally and unmounts its subtree whenopenbecomes false unlessforceMountis passed — which it isn't anywhere in this codebase (confirmed via repo-wide grep forforceMountacrossapps/loopover-miner-ui,apps/loopover-ui,packages/loopover-ui-kit: zero hits).So on a mobile viewport, closing the chat sheet (accidental tap-outside, Escape, explicit toggle) discards
ChatConversation's entiremessages/activeSource/streamingstate (apps/loopover-miner-ui/src/components/chat/conversation.tsx:70-73) — the whole conversation and any in-progress typed draft (ChatComposer's ownvaluestate) is silently lost and reinitializes empty on reopen. Desktop and mobile behave inconsistently despite the code explicitly claiming otherwise.Untested branch:
apps/loopover-miner-ui/src/chat-rail.test.tsxcovers docked-vs-sheet rendering, the toggle, and open-state persistence across route navigation, but nothing asserts that conversation content survives a mobile sheet close/reopen cycle — precisely the gap that would have caught this.Requirements
Make mobile behavior match the documented intent (state survives close/reopen), the same way desktop already does. The most direct fix is passing
forceMountto the relevant RadixDialog.Content(viaSheetContent's existing prop passthrough, if it has one — check before adding a new prop) combined with the existinghidden/visibility toggle pattern desktop already uses, so the sheet's content stays mounted but visually hidden when closed, matching the desktop<aside hidden={!open}>approach.Deliverables
ChatConversation'smessages/activeSource/streamingstate and any in-progress composer draft, matching desktop behavior.chat-rail.test.tsxasserting conversation content survives a mobile sheet close/reopen cycle.Test Coverage Requirements
apps/loopover-miner-ui is not covered by the src/** 99% patch gate - the new regression test is this issue's own coverage deliverable.
Expected Outcome
A mobile user's in-progress chat conversation and draft message survive closing and reopening the chat sheet, matching the component's own documented intent and desktop's actual behavior.
Links & Resources
apps/loopover-miner-ui/src/components/chat-rail.tsx:1-5,36-56,68,76-85,packages/loopover-ui-kit/src/components/sheet.tsx:10,52+,apps/loopover-miner-ui/src/components/chat/conversation.tsx:70-73