Skip to content

refactor(remote): put the phone on the desktop's rendering seams #1177

Description

@Juliusolsson05

Motivation

The phone companion (src/remote-client) mounts the real desktop Feed, but it gets there through copies instead of shared code, and each copy has drifted. Fixes keep landing in only one copy, which is why the phone keeps looking broken after many fixes.

These divergences are observable on the phone:

  • It renders raw entries instead of selectMergedEntries, so the ghost fallback is missing.
  • It skips provider normalizeConditions and uses a separate condition dispatch.
  • lastJsonlEntryAt is hardcoded to 0, so the collapsed-running rule never fires (ARCHITECTURE §8.3).
  • Main's remote tap has no semantic coalescing and no ordering barriers against JSONL, history-boundary or removal. The desktop forwarder has both.
  • The phone never receives transcript-diagnostic or provider-session-changed.
  • A second sub-agent watcher polls the same directories as the first.
  • History loading sits outside the SessionFeed contract, with two separate paths.
  • SessionView.tsx hand-mirrors TileLeaf's Feed props, citing line numbers that are out of date.
  • Six Vite aliases stub renderer modules, and the app-store stub is not type-checked.

There are also three rendering-modularity debts:

  • Per-provider policy is hard-coded in the shared decide layer: SUPPRESSION_POLICY, plus inline provider === ... checks.
  • Provider rows import feed and workspace internals.
  • 'claude' is used as a silent fallback for unknown providers.

A planned screenshot viewer, which runs live agents and captures their rendering, needs exactly the same seams.

Intended behavior

Each concern gets one implementation, with the transport or host injected:

  1. Main: one transport-neutral session feed tap, with window and remote sinks.
  2. Contract: SessionFeed.loadHistory on the contract.
  3. Ingest: one pure session-ingest core used by both the desktop hub and the phone store. Desktop-only planes stay layered on top.
  4. Host: a typed RendererHost context in place of the phone aliases and the stray window.api calls in the feed subtree.
  5. View: one AgentFeedView, mounted by both TileLeaf and the phone.
  6. Provider policy: moves into the renderer registry.
  7. Imports: provider rows import feed primitives only through features/feed/public, and the reverse import boundary is tested.
  8. Docs: ARCHITECTURE and the rendering docs are updated to match.

Plan: docs/plans/2026-09-24-phone-shared-rendering.md

Acceptance criteria

  • The phone paints from the same merged entries, normalized conditions and lastJsonlEntryAt as the desktop.
  • The phone receives the same ordering barriers and the two missing channels.
  • The remote-client Vite config has no component or store aliases, and its bundle contains no Monaco.
  • A test replays one recording through both clients' ingest and gets the same ledger items.
  • No behavior change on the desktop: the existing suites pass, and npx tsc -b is clean.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:choreMaintenance, deps, tests, docs

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions