fix(desktop/transcript): quiescence-based reveal + retired hydration pin, reserved image box (#331)#350
Merged
Conversation
…pin, reserved image box (physercoe#331)
physercoe
approved these changes
Jul 20, 2026
physercoe
left a comment
Owner
There was a problem hiding this comment.
Reviewed — approve. Focused on what CI can't catch (timer/reveal correctness, CSS geometry).
- Diff matches the description; no hidden changes.
- Timers:
reveal()is idempotent (setssettledRefsynchronously, then state, then clears both timers); effect cleanup clears bothsettleTimerandbackstopTimeron unmount/re-arm; post-reveal thesettledRefguard stops any further re-pin. No leak, no double-reveal. - No stuck-hidden: the 1800ms backstop is armed unconditionally and calls
reveal()regardless of height-change churn, so reveal is guaranteed even iftotalListHeightChangedfires forever. - Bottom-following post-reveal: retiring the re-pin loop relies on
followOutput+atBottomStateChange, which covers appended items; an existing bottom row growing after reveal is exactly what the reserved-image-box CSS mitigates — the two halves are complementary by design. - CSS:
.md img { max-width:100%; max-height:360px; height:auto }scales tall images down preserving aspect ratio (no clip/squash, soobject-fitcorrectly unneeded);.md-img-loadingis an empty placeholder so itsaspect-ratio:16/10never distorts real content.
Non-blocking: guaranteed-reveal latency for the no-height-change path grew 500ms→1800ms (in practice the 150ms quiet path reveals fast); wide-short real images can still be shorter than the 16:10 placeholder (acknowledged as approximate). Fine.
physercoe
pushed a commit
that referenced
this pull request
Jul 20, 2026
…) + quiescence reveal / #331 (#350) Co-Authored-By: Claude Opus 4.8 <[email protected]>
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.
Closes #331
What
Replaces the time-capped hide-until-settled reveal (6b461bb) with a quiescence-based reveal plus a retired hydration pin, and reserves the attachment-image box so blob swaps stop growing rows mid-hydration.
desktop/src/surfaces/AgentTranscript.tsxtotalListHeightChangedre-arms a 150ms quiet timer, so the feed staysvisibility:hiddenfor the whole hydration storm on heavy transcripts instead of popping in mid-storm.settledRefretires the re-pin loop at reveal: post-reveal,pinBottomneither scrolls nor re-arms, so late height changes (font swap, async image pop) no longer cause visible jumps. Bottom-following after reveal remains covered by the existingfollowOutput+atBottomStateChangecontract, unchanged.desktop/src/styles/partials/05-transcript-boards.css.md-img-loadingnow reserves ≈ the final image box (width:100%; aspect-ratio:16/10; max-height:360px) and.md imggains the matchingmax-height:360px, so the placeholder → blob swap is a geometric near-no-op.Why
Per the diagnosis posted on the issue (comment thread), the shipped fix fails on heavy transcripts for three reasons: (1) the 500ms cap reveals unconditionally mid-hydration-storm while the 80ms debounce can only reveal early, never delay; (2) the re-pin loop never retires, so every late post-reveal height change is a visible jump; (3)
AttachmentImageswaps a 120×80 placeholder for a full-widthheight:autoimage with no reserved dimensions, growing rows by hundreds of px up to seconds later. This PR implements items A+B+C from that diagnosis.Item D (
document.fonts.readygating) is deliberately skipped: a font swap changes rendered-row heights, which already firestotalListHeightChangedand pushes the quiet timer out — the quiescence window covers it implicitly, so a separate fonts gate would be redundant machinery.How verified
npm ci && npm run build(tsc --noEmit + vite build) — clean.scripts/lint-desktop-tokens.sh— clean (no new hex / primitive-var / phantom tokens).git diff origin/main --stat— only the two intended files; no version bumps or manifest changes.