Release exp-v0.52.278: stop tearing live progress words (#7082, @ruizanthony) - #7473
Merged
Merged
Conversation
Transparent-stream progress rows are built incrementally by
`_anchorProseIncrementalNode`, which streams source text into the vendored
streaming-markdown parser. That parser always holds the most recently written
character in its pending buffer, so a live row's rendered `.msg-body` text
trails its source text by one character.
`_refreshTransparentFadeProseRow` resumed appending from the
`data-stream-fade-text` cursor but fell back to `body.textContent` when the
attribute was absent — exactly the case for incrementally built rows, which
never set it. That fallback mixed two coordinate spaces (rendered text vs
source text), so the computed delta started one character early, in the middle
of a word. The delta was then appended as a sibling of the parser's open `<p>`,
and since `<p>` is a block box the tail rendered on its own line: a single word
split across two lines ("Ces deu" / "x fichiers").
Two independent fixes:
1. Resume strictly from the source-space cursor. When no cursor exists we
cannot know how much source is already rendered, so rebuild the body
instead of guessing a delta from rendered text.
2. Append into the trailing block element rather than the `.msg-body` root, so
a continuation of the current sentence stays on the same line even if a
cursor is ever stale.
Regression tests drive the real vendored parser to build a live row exactly as
production does, assert the parser really does lag its source, and fail if any
word is torn across a block boundary. The existing reconcile harness gains the
new helper dependency.
…w rebuild Review follow-ups for #7082: - Maintainer should-fix: the no-cursor rebuild branch of _refreshTransparentFadeProseRow cleared the body and re-wrapped every word as .is-new, so the whole visible row dipped to opacity 0 and faded back (~620ms), and wholesale node replacement invited the one-time scroll-anchor bounce (#6257). Now the rendered text is snapshotted before the clear and the messages.js _streamFadeMuteRenderedPrefix idiom is re-applied after the rebuild, so only genuinely-new tail words animate. The helper is exported on window (__streamFadeMuteRenderedPrefix) the same way __anchorProseIncrementalNode is. - Greptile P1: the same branch passed raw source text to _appendTransparentFadeText, flattening live markdown rows to literal syntax (links/emphasis/headings lost until settlement). The rebuild now adopts a deep clone of the candidate's parsed .msg-body (the incremental streaming-markdown output) when one exists, so the parsed DOM survives; the plain-text rebuild remains only as fallback for candidates without a parsed body. The resume cursor stays in source space when rendered text diverges from source (markdown), and shrinks to the rendered prefix for plain prose so the parser's held-back character is re-appended on the next delta instead of dropped. Regression tests: rebuild must not re-animate the already-rendered prefix (tail-only .is-new), and rebuild must keep parsed <a>/<strong> DOM with no literal markdown in the visible text. The two existing tests of this PR are unchanged and still pass.
… markdown rebuild Greptile P1 follow-up on the fade-row rebuild: when a cursorless live markdown row adopts the incremental node's cloned DOM, that DOM lags the source text by the streaming parser's held-back tail (pending/text buffers). Keeping the full source-length resume cursor made the next reconciliation append nothing, so the pending characters stayed missing from the live row until settlement. Read the held-back length from the parser bound on the candidate body (_smdBindParserIdentity) and trim the source-space cursor by exactly that length, so the cursor matches the adopted content and the next reconciliation re-appends the pending tail. Regression: rebuild while the parser holds an unrendered tail, then reconcile again — the pending text must appear in the live row before settlement.
Cursorless markdown rebuilds cloned the live parser tree, then later source growth appended raw bytes through _appendTransparentFadeText. Incomplete emphasis (Hello ** / Hello **world**) became literal **world** while the candidate already had <strong>world</strong>. Parser-owned candidates now keep reconciling from the parsed DOM. Pending and MEDIA tails stay on the parser. Rendered-prefix mute is unchanged.
After rewind/cache-miss the keyed visible row stayed distinct from the persistent parser candidate, so every later growth frame deep-cloned the parser DOM into the old row. That cut off the previous tail word's ~620ms fade and broke word-node identity. Promote the parser-owned candidate into the existing row's DOM position once, mute the already-rendered prefix on the real parser body, and return the adopted node so later keyed renders hit existing === node. Regression: two post-transition growth frames keep the first new tail span as the same .is-new node until an explicit animationend.
nesquena-hermes
enabled auto-merge
September 8, 2026 21:48
Contributor
|
| Filename | Overview |
|---|---|
| static/ui.js | Reconciles transparent live prose using parser-owned DOM, source-space cursors, block-aware appends, and rendered-prefix fade muting. |
| static/messages.js | Exposes the stateless rendered-prefix mute helper for reuse by the transparent-stream reconciler. |
| tests/test_live_prose_fade_cursor_source_space.py | Adds extensive browserless regression coverage for source/render cursor boundaries, Markdown ownership, pending tails, fade identity, and repeated growth. |
| tests/test_issue5367_transparent_live_row_reconcile.py | Updates the transparent-row reconciliation harness for the new block-aware append helper. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Streamed prose source] --> B[Incremental Markdown parser]
B --> C[Parser-owned live row]
C --> D{Existing keyed row uses same owner?}
D -->|No, owner replaced| E[Adopt parser-owned row once]
E --> F[Mute already-rendered prefix]
D -->|Yes| G[Keep existing node identity]
F --> G
G --> H[Parser appends later source deltas]
H --> I[Stable Transparent Stream prose]
I --> J[Settled durable transcript replaces live view]
Reviews (1): Last reviewed commit: "changelog: #7082 stop tearing live progr..." | Re-trigger Greptile
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.
Release exp-v0.52.278 — stop tearing live progress words across block boundaries (#7082, @ruizanthony)
Ships #7082: fixes a client-side render artifact where live progress text in Transparent Stream could split a word across a block boundary and render the tail on its own line (e.g.
Ces deu/x fichiers passent.). The streamed text was always intact — the tearing was purely in the live fade/reconcile render path.The renderer now adopts the parser-owned DOM once after a parser-owner replacement (keeping the per-token fast path for normal ticks) and handles the source-space cursor at block boundaries, so live prose stays whole.
Gate (crown-jewel streaming surface — full gate)
Ces de | ux…while head preserves the complete text; 227 focused tests + 18 subtests pass.proseReversals=0,nonProseReanims=0, no alternating (modeFlips=0). This is the definitive motion proof for a live-render fix.Credited via
Co-authored-byin the release commit.