Repository navigation
Remote: the desktop feed on the phone (semantic rendering + history backfill) - #438
Merged
Merged
Conversation
…d via alias substitution Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The phone connects mid-session; live frames only cover what happens after.
New read-only get-history message (scope widened consciously, pinned both
ways in scope.test.ts): beforeMarker absent = newest-N initial chunk,
present = the page immediately before it, raw records in the live frames'
entry shape so the client runs ONE mapper path for backfill and live.
Main-side key resolution: SessionManager cannot reconstruct
{cwd, providerSessionId} for a live session, but the transcript FILE path
rides every jsonl-entry event — cached now (same lifetime as the snapshot
caches) with a getter, and historyLoader gains path-based variants
(the resolver entrypoints become thin wrappers; desktop behavior
unchanged). Also fixes vitest alias drift: opencode-headless was missing
from the deliberately-duplicated map, breaking any test whose import graph
reaches registry.main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…anscript store The phone now mounts the REAL desktop Feed component tree (rows, semantic streaming turns, markdown prose, diffs, todos, subagent cards) — zero rendering drift by construction. The vite alias table in remote-client/vite.config.ts is the compatibility contract: exactly five substitutions (static-hljs CodeBlock, frozen-settings app-store hook, no-op perf client, plain-anchor SafeMarkdownLink, plain SafeInlineCode), each stub documenting its divergence; everything else is literally the desktop's code. Desktop styles + theme boot via @renderer/styles.css and applyTheme(DEFAULT_SETTINGS) under @tailwindcss/vite. TranscriptStore is the phone's minimal SessionRuntime, built from the desktop's OWN reducers (foldSemanticEvent, registry transcript mappers, indexEntryIntoMaps): one seen-uuid set gates backfill and live entries, history prepends / live appends, one stateful mapper per session (codex turn cursor), the stream-phase machine reduced outside the fold exactly like useIpcSubscriptions. Ghosts and the desktop-only subsystems are deliberately skipped (documented in the design doc + store header). SessionView maps store snapshot → Feed props mirroring TileLeaf's own runtime→Feed contract; SessionFeedProvider wraps the tree so desktop rows (AskUserQuestionRow) answer through the WS transport unmodified. Bundle: 450 KB gzip, no monaco. Pipeline pinned end-to-end by a real-socket test (mapper + dedupe + backfill-prepend + fold + phase). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The big one: the stream-phase machine is now a SHARED pure reducer (semantic/streamPhaseMachine.ts) extracted verbatim from useIpcSubscriptions — the desktop hook and the phone TranscriptStore both call it, so the hand-copied fork (with its three silent divergences) is structurally impossible now, not merely reviewed against. TranscriptStore ingest corrections, each traced to its desktop counterpart: history chunks map through FRESH chunk-scoped mappers (the session-lifetime codex cursor was being corrupted in both directions); the prepend marker only advances on raws that yielded KEPT entries; a missing marker kills the load-older affordance instead of leaving a dead button; failed initial backfills retry on session-list frames (reconnect) and on live-entry arrival; per-session state evicts when sessions leave the list; the kind fallback is never baked into a memoized mapper. Transcript ROLL handling (/clear, resume onto a new provider session): get-history replies now carry the file identity, live frames already do, and the store resets + discards on disagreement — the stale-cache window documented on SessionManager.lastTranscriptFile can no longer prepend the previous conversation permanently. Phone UX regressions restored: the TUI-text fallback renders for pre-transcript states (trust dialog body, login errors) instead of a blank feed, and AskUserQuestion is back in the server-verified tap bar as the guaranteed answer path when the inline row can't render its options. Also: RemoteServer.apply is exception-guarded (structured error reply, not a phone-side timeout), historyLoader's resolver entrypoints delegate to their FromFile twins (span/catch scaffolding deduped, filePath back in failure records), and dead code dropped (unused lastTimestamp, App's unreachable second PairScreen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
The rendering upgrade tracked in #420: the phone now renders sessions with the real desktop feed — markdown prose, streaming turns, tool cards, Edit/Write diffs, todo lists, subagent cards — instead of raw TUI text.
Architecture (docs/superpowers/specs/2026-07-06-remote-semantic-rendering-design.md)
@rendererfeed component tree; the Vite alias table inremote-client/vite.config.tsis the compatibility contract — exactly five documented stub swaps (static-hljs CodeBlock, frozen-settings store hook, no-op perf, plain markdown link/inline-code). Everything else renders with literally the desktop's code, styled by the desktop's own Tailwind theme. No monaco in the bundle (450 KB gzip total).foldSemanticEvent, provider transcript mappers,indexEntryIntoMaps, and a stream-phase machine extracted into a shared pure reducer (semantic/streamPhaseMachine.ts) that the desktop hook now consumes too — the fork class is structurally dead.get-historywire message (scope pinned inscope.test.tsboth directions), served from aSessionManagertranscript-file cache + path-basedhistoryLoadervariants (main can't reconstruct{cwd, providerSessionId}for live sessions; the file path rides every jsonl event). Chunks carry the file identity so the client detects transcript rolls (/clear) and discards stale serves.Review
High-effort multi-agent review over the branch confirmed 16 defects (codex cursor corruption, stale /clear serve, AUQ fallback loss, retry gaps, eviction, marker gates, the phase-machine fork…) — all fixed in
17ee6b5, each fix traced to its desktop counterpart in comments.Verification
tscclean on both projects (remote-client type-checks against the REAL desktop modules); phone bundle buildsCloses the rendering item in #420.
🤖 Generated with Claude Code