Skip to content

Webview: entity rail self-heals /amicode/problem on stream reconnect #1585

Description

@jeonghun-jj-lee

Parent: #1583

Context

The session entity rail's /amicode/problem resource (packages/app-bundle/overlay/packages/ui/src/amicode/entity-rail.tsx:135) is keyed only on the completed-amicode-part counter. When a fetch fails during a connection blip (amicodeGet has no timeout and rejects on any socket error / non-2xx — utils/amicode-fetch.ts:8), railState() (problem.ts:400-404) flips the rail to status unavailable (entity-rail.tsx:221-243) or a stale view. It then never refetches when the stream reconnects — only a new completed part, the Retry button, or a window reload recovers it. Slice 1 makes the stream itself reconnect; this slice makes the rail heal itself once it does.

Change

Refetch /amicode/problem on the stream's disconnect→connect edge (a genuine reconnect), driven by the app's existing serverSDK.event.status() signal — not on the first connect and not while steadily connected.

Acceptance criteria (TDD, red→green)

  1. Pure decision fn, unit-tested. Add shouldRefetchOnReconnect(previous: boolean | undefined, next: boolean): boolean to packages/app-bundle/overlay/packages/ui/src/amicode/problem.ts, returning true iff previous === false && next === true (a rising edge that follows a real disconnect; undefined previous — the initial connect — returns false). Add tests to problem.test.ts (bun:test) covering: undefined → true ⇒ false (first connect, no refetch); false → true ⇒ true (reconnect); true → true ⇒ false; true → false ⇒ false. Runs in-place: cd packages/app-bundle/overlay/packages/ui && bun test --conditions=solid ./src/amicode/problem.test.ts.
  2. Rail wired. AmicodeEntityRail accepts an optional streamConnected?: () => boolean prop. A createEffect tracks the previous value and, on a shouldRefetchOnReconnect-true transition, calls the resource's refetch(). No refetch fires on the initial connect or while steadily connected. Omitting the prop leaves behavior unchanged (host that can't supply status).
  3. App supplies status. The rail mount in packages/app-bundle/overlay/packages/app/src/pages/session/timeline/message-timeline.tsx passes streamConnected={() => serverSDK.event.status() === "connected"} (serverSDK is already in scope in that component).
  4. Compiles. pnpm --filter amicode run build:binary succeeds.

Files

  • packages/app-bundle/overlay/packages/ui/src/amicode/problem.ts (+ problem.test.ts)
  • packages/app-bundle/overlay/packages/ui/src/amicode/entity-rail.tsx (prop + effect)
  • packages/app-bundle/overlay/packages/app/src/pages/session/timeline/message-timeline.tsx (pass the prop)

Notes for the implementer

  • The decision fn is pure and lives in the UI package's problem.ts, which already runs its tests in-place (no solid-js/workspace imports needed for the test) — keep it dependency-free.
  • The edge effect must not create a feedback loop: track previous in a plain closure let, don't read the resource's own state inside the effect.
  • bun is at /Users/jj/.bun/bin/bun. Independent of Slice 1 (disjoint files) — grabbable in parallel.

Activity

  1. jeonghun-jj-lee commented on Sep 27, 2026

    @jeonghun-jj-lee
    ContributorAuthor

    Merged into integration branch amico/issue-1583-webview-stream-liveness (PR #1587). Gates green: unit 38/38, drift PASS, overlay typecheck clean, build:binary OK.

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

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions