Skip to content

fix(app): hydrate timeline message parents (#35269) - #7

Open
maschwenk wants to merge 4 commits into
devfrom
forge-cherry-pick-hydration
Open

fix(app): hydrate timeline message parents (#35269)#7
maschwenk wants to merge 4 commits into
devfrom
forge-cherry-pick-hydration

Conversation

@maschwenk

Copy link
Copy Markdown

Cherry-pick of upstream commit a12d50e15 (fix(app): hydrate timeline message parents) which adds parent-message backfill to loadMessages — the core fix for the Forge deep-link blank-chat race.

What:

  • Cherry-picks a12d50e15 (parent-hydration) from anomalyco/opencode onto fork dev
  • Ports sessionNotFoundError / isLocalSessionNotFoundError from server-errors.ts (upstream 4a42caef2) which server-session.ts depends on
  • Reverts the retry-loop hack in model.ts to upstream's clean sync(id) resource (now unnecessary since parent-hydration makes the limit=2 load renderable)
  • Keeps only the one still-needed fork divergence: release meta.loading when the finishing load owns the slot (owns fix) — upstream still gates solely on the generation and can wedge loading=true forever

Net result: fork diverges from upstream by 8 lines in server-session.ts. model.ts and server-errors.ts are byte-identical to upstream.

Tests: 229 pass in packages/app (context + timeline dirs); forge-ui typecheck clean.

Supersedes #6.

aidansunbury and others added 4 commits June 29, 2026 15:54
The message pane could stay permanently empty for a session even though
its message API returned 200 with the full transcript — no error, green
network tab. Two interacting issues in the session-sync layer:

1. `loadMessages` bails when `meta.loading[sessionID]` is set (guard at
   the top), but its `.finally` only clears `loading` when the generation
   is unchanged (`generations.get === active`). A generation change
   mid-load therefore left `loading` stuck true forever, so every later
   load — including a forced one — no-ops and the transcript never fills.
2. On a cold, deep-linked mount, the page-sync and the background
   prefetch race: the forced sync can be deduped into the prefetch's
   in-flight promise, and a load whose page is dropped on a generation
   change is never retried. Result: nothing commits to
   `data.message[sessionID]`, which is the only thing the timeline
   renders from.

Fix:
- Release the `loading` flag whenever the finishing load still owns the
  slot (`messageLoads.get === load`), regardless of generation, so a
  session can never get permanently stuck.
- For the session being viewed, force the load and re-drive it until the
  store actually holds the session's messages (or the view moves on).
  Redundant refetch on the cold path is acceptable and intentional.

Exposed reliably by the Forge embedding (opencode 1.17.11), which mounts
straight onto /session/:id so the page-sync and prefetch start against a
cold store simultaneously.
fix(app): stop the viewed session's transcript from staying blank
Cherry-pick of anomalyco/opencode@a12d50e15 with conflict resolution:
keep upstream's parent-hydration in loadMessages, plus apply the
pre-existing fork fix that releases meta.loading when the finishing load
still owns the slot (upstream still gates this solely on the generation,
which can wedge loading=true forever after a generation change).

Also cherry-picks the sessionNotFoundError / isLocalSessionNotFoundError
exports from server-errors.ts that upstream added in 4a42cae, since
server-session.ts depends on them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants