Skip to content

fix(desktop): attach transcript observers after render and key the conversation column - #1415

Open
tonyfettes wants to merge 1 commit into
mainfrom
fix/transcript-observers-after-render
Open

fix(desktop): attach transcript observers after render and key the conversation column#1415
tonyfettes wants to merge 1 commit into
mainfrom
fix/transcript-observers-after-render

Conversation

@tonyfettes

Copy link
Copy Markdown
Contributor

Summary

Split out of #1409 (and originally found while rebasing #1375). Two latent transcript bugs, independent of any rendering optimization:

  • Stale content observers after a conversation switch. A new transcript component's subscription started during the graph read that created it, while the previous conversation's scroller was still on screen. Its first attach observed that scroller's #stream; Rabbita then patched the same <section> in place (same tag and position in the root's positional children) and only replaced the keyed #stream, so the section identity check never re-attached and content growth went unobserved. The root view now keys the conversation column (Map[String, Html] children), so a conversation's section is its own element, and the observers attach through an after-render command once this component's DOM is committed.
  • Image reads dropped while the root is unresolved. With observers attaching right after render, an image placeholder enters the viewport before the conversation's checkout root resolves; begin_read used to drop that read, and nothing retried it unless some later content mutation happened to rescan. Such reads are now kept as ImageAwaitingRoot; the root is a switch_by branch keyed by its value, so its arrival reaches update as one ImageRootChanged message and rebase issues the waiting reads.

One E2E test needed a change: "Codex creates a thread, sends its first turn, and stops it" filled the composer as soon as the draft-open request was sent, before the Codex page had rendered. With positional children the OpenSeek textarea was patched into the Codex one, which hid the race; with the keyed column the fill landed in an element that was then replaced. The test now waits for the Codex composer.

On the two Codex P1s raised against #1409

Both target the image-root change and describe behaviour that already exists on main:

  • Settled images keep the previous root's bytes when the root moves from one resolved path to another. main never rebased the cache at all, and the settled <img> has already lost its data-transcript-image marker, so it is not re-requested there either. The transition this change handles is None → Some (a worktree bound after the first send); checkout_root offers no path from one resolved root to another.
  • A root that path_for rejects is classified as awaiting. ImageOwner takes channel and root from the same Input, so a root owned by another channel cannot be produced by the app; main silently dropped that read as well. Not changed here.

Verification

🤖 Generated with Claude Code

https://claude.ai/code/session_01Dp6jf4fKxaVzVq2heQR6dX

…nversation column

A new transcript component's subscription started during the graph read
that created it, while the previous conversation's scroller was still on
screen. Its first attach observed that scroller's #stream; Rabbita then
patched the same <section> in place (same tag and position in the root's
positional children) and only replaced the keyed #stream, so the section
identity check never re-attached and content growth went unobserved.

The root view now keys the conversation column, so a conversation's
section is its own element, and the observers attach through an
after-render command once this component's DOM is committed.

Image reads that arrive before the conversation root resolves are kept
as ImageAwaitingRoot and requested when the owner gains a root. The root
is a branch keyed by its value, so the change reaches update as one
message instead of through a DOM attribute.

The Codex first-turn E2E test filled the composer as soon as the
draft-open request was sent, before the Codex page had rendered; with
positional children the OpenSeek textarea used to be patched into the
Codex one, which hid the race. It now waits for the Codex composer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dp6jf4fKxaVzVq2heQR6dX
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-09T09:28:07.749608Z be64bda PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be64bdade0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let image_root = input
.map(input => input.root)
.switch_by(_ => message_on_create(emit(ImageRootChanged)), by=root => {
root.map(uri => uri.to_string()).unwrap_or("")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve root absence in the branch key

When input.root is None, unwrap_or("") converts that absence into a sentinel string instead of retaining a distinct optional/tagged branch. This makes the switch identity depend on an implicit promise that no serialized root can equal the sentinel and violates the repository invariant for representing missing values; construct an explicitly tagged key for None and Some(uri) instead.

AGENTS.md reference: AGENTS.md:L27-L30

Useful? React with 👍 / 👎.

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.

1 participant