Stabilize transcript scrolling and Markdown surfaces / 稳定 transcript 滚动与 Markdown 展示 - #9754
Open
SivanCola wants to merge 15 commits into
Open
Stabilize transcript scrolling and Markdown surfaces / 稳定 transcript 滚动与 Markdown 展示#9754SivanCola wants to merge 15 commits into
SivanCola wants to merge 15 commits into
Conversation
Problem: empty fenced code blocks rendered as bordered phantom cards in shared Markdown surfaces.\n\nRoot cause: the shared code component always mounted CodeViewer for block-shaped code, including whitespace-only fences.\n\nFix: omit whitespace-only fenced blocks while preserving non-empty code and add renderer parity coverage.\n\nVerification: pnpm exec tsx src/__tests__/markdown-pipeline.test.tsx; pnpm test:transcript; pnpm build.
Problem: history prepends, virtualized measurement, scrollbar drags, and question jumps could restore stale anchors and move the reader by multiple screens.\n\nRoot cause: small sessions were fully mounted during reader gestures, prepend coverage waited on the whole list, scrollbar mapping stayed on stale extents, and diagnostics did not classify restore-anchor reversals.\n\nFix: add tokenized surface transaction telemetry, use bounded reader corridors with logical-anchor coverage, rebase custom scrollbar drags after geometry changes, add a stable 16px question-jump offset correction, and report unauthorized scroll reversals.\n\nVerification: pnpm test:transcript; PLAYWRIGHT_BROWSERS_PATH=.pw-browsers pnpm test:transcript-browser; PLAYWRIGHT_BROWSERS_PATH=.pw-browsers pnpm test:transcript-reader-browser; pnpm test:typecheck; pnpm build.
Problem: a native thumb completion could outlive the transcript surface that started it.\n\nRoot cause: native scrollbar ownership tracked pointer identity but not the surface generation.\n\nFix: bind native thumb transactions to generationRef and reject stale observations, finishes, and activity checks.\n\nVerification: pnpm exec tsx src/__tests__/transcript-reader-extent-race.test.tsx; pnpm exec tsx src/__tests__/transcript-native-scrollbar.test.ts; pnpm exec eslint src/lib/useTranscriptNativeScrollbarOwnership.ts src/lib/useTranscriptScrollArbiter.ts.
Problem: the verified transcript transaction and native-thumb generation fence exceed the previous exact raw startup budget by the measured build delta.\n\nFix: retain the smallest decimal raw budget ratchet and document the attributable geometry/diagnostic additions.\n\nVerification: pnpm build.
Problem: the Windows field capture for esengine#9711 was not represented in deterministic replay coverage.\n\nFix: add a content-free fixture for all nine restore-anchor reversals and the 27,104px maximum, with build identity and tolerance assertions.\n\nVerification: pnpm exec tsx src/__tests__/transcript-geometry-replay.test.ts; pnpm exec eslint src/__tests__/transcript-geometry-replay.test.ts src/__tests__/transcript-diagnostic-replay.fixtures.ts.
Problem: stale native-scrollbar observations called the cancellation callback through a closure not owned by the observation callback.\n\nFix: clear the stale transaction inline, publish the terminal transition, and keep the generation fence dependency-complete.\n\nVerification: pnpm exec eslint src/lib/useTranscriptNativeScrollbarOwnership.ts; pnpm exec tsx src/__tests__/transcript-reader-extent-race.test.tsx.
Verification on the final native-thumb generation-fenced tree measured 463.5 KiB initial gzip and 2472.6 KiB initial raw JavaScript/CSS. Keep the smallest one-decimal ceilings above those measurements.
Problem: the native smoke contract waited for every logical row, which conflicts with the bounded reader corridor used to prevent measurement storms. Root cause: the test encoded the removed full-mount behavior instead of the new corridor contract. Fix: wait for a painted mounted corridor row and visible viewport, then retain the existing stability, blank-frame, reversal, and tail assertions. Verification: Windows 11 WebView2 transcript smoke; native composer smoke; native selection compositor smoke; node --check desktop/transcript_native_smoke_contract.js.
Problem: the latest main-v2 anchor-compensation baseline raises the measured initial raw bundle to 2472.9 KiB. Root cause: the existing 2472.7 KiB ceiling was calibrated before PR esengine#9746 landed on main-v2. Fix: retain the narrow one-decimal raw budget ceiling at 2473.0 KiB and document the exact post-rebase measurement. Verification: pnpm build.
Problem: the integrated transcript arbiter crossed the repository 800-line file-size ceiling after adding transaction wiring. Fix: remove redundant section separators without changing behavior or ownership logic. Verification: pnpm exec eslint src/lib/useTranscriptScrollArbiter.ts; go run ./tools/repolint.
Problem: delayed WebKit geometry callbacks could start an anchor-compensation writer while an active reader gesture still owned the viewport. Root cause: generic geometry scheduling and the imperative writer had no final reader-intent fence. Fix: expose the live reader layout lease to geometry scheduling and reject anchor-compensation writes while the transcript declares reader intent. Add a deterministic writer regression for the ownership fence. Verification: focused ESLint; transcript scroll-writer, anchor-compensation, and reader-extent race suites; repolint.
Problem: the reader-lease writer fence raises the measured initial raw bundle to 2473.1 KiB. Fix: retain the smallest one-decimal raw budget ceiling at 2473.2 KiB and document the attributable fence. Verification: pnpm build.
Collaborator
Author
|
Merge-order note: #9753 (reduced-motion visual-guard fix) should land first. Both PRs touch adjacent hunks in |
Problem: WebKit can briefly unmount the logical reader anchor during a delayed range replacement, exposing a blank viewport before the corridor remounts it. Root cause: reader visual guarding depended on an anchor row rect, while the replacement can remove that row for one or more paints. Fix: retain a bounded visual hold from the accepted native scroll position when a rejected range is blank and the anchor row is temporarily unavailable. Keep the writer and reader ownership fences unchanged. Verification: focused reader-extent and anchor-compensation race suites; pnpm build.
Problem: the blank-range visual hold adds a small raw bundle delta beyond the previous calibrated ceiling. Fix: retain a narrow one-decimal 2473.5 KiB raw ceiling for the measured 2473.3 KiB build output. Verification: pnpm check:bundle; go run ./tools/repolint; git diff --check.
This was referenced Sep 3, 2026
SivanCola
added a commit
that referenced
this pull request
Sep 3, 2026
Problem: a fenced code block with no content (or only whitespace) renders as a bordered one-line CodeViewer. In the #9711 capture these appear as empty cards between paragraphs and add phantom height to transcript rows. Root cause: createComponents() treats every fence as a block and hands the empty value to CodeViewer; PlainMarkdownBlock likewise renders an empty <pre> for whitespace-only plain blocks. Fix: return null for a fenced block or plain block whose trimmed value is empty. Comment-only and other non-empty code keeps its code-block surface. Verification: markdown-pipeline.test.tsx gains four assertions (empty fences render nothing, surrounding text survives, comment-only blocks keep their surface). The new assertions fail against the previous component. typecheck, lint:hooks, related transcript suites, vite build, and bundle budgets pass without a budget change. Extracted from #9754 so the display fix can land independently of the scroll-ownership work.
SivanCola
added a commit
that referenced
this pull request
Sep 3, 2026
Problem (#9711): scrollbar interaction fights the app's own scroll handling. Dragging the custom scrollbar while rows are still mounting writes through a mapping frozen at pointerdown, so the next pointer move lands on a stale extent. A native-thumb transaction is only ended by call ordering (cancel before generation bump), not by the transaction itself. Root cause: the custom-scrollbar drag captures overflow/maxThumbTop once and never rebases when geometry changes mid-drag. The native-thumb transaction carries no generation, so observe()/finish() cannot tell a stale transaction from a live one and rely on every generation-advancing caller to cancel first. Fix: - useCreationTranscriptScrollbar: rebase the frozen drag mapping at the current physical ratio whenever overflow/maxThumbTop/thumbHeight change, keeping the last pointer sample so the thumb does not visibly jump. The rail-click settle loop waits for two quiet frames but is bounded by a 1000 ms wall-clock budget (RAIL_SETTLE_BUDGET_MS) instead of spinning while geometry keeps changing. - useTranscriptNativeScrollbarOwnership: bind each transaction to the surface generation. A stale-generation observe() ends ownership without claiming the tail; a stale finish() clears the marker and reports no transaction; isActive() is false. The arbiter passes generationRef. Verification: - transcript-native-scrollbar-generation.test.tsx (new): 14 assertions covering same-generation claim-tail, stale observe, stale finish with and without prior observe. Four fail against the previous hook. - creation-transcript-scrollbar.test.ts: rebase preserves the physical ratio and later movement uses the current extent. - transcript-reader-extent-race (native-thumb section), test:transcript, typecheck, test:typecheck, lint:hooks, check:scroll-writer, repolint, git diff --check pass. - Bundle: gzip 463.102 KiB (budget 462.9 -> 463.2), raw 2470.932 KiB (2469.9 -> 2471.0). The generation fence and drag rebase are extracted from #9754; the settle deadline is new.
Problem: a delayed WebView2 range replacement can unmount the logical reader anchor for one paint, making stale transcript pixels appear duplicated during a running turn. Root cause: the reader extent regression suite covered mounted-anchor corrections but not the blank-range interval where no anchor row is available. Fix: add a deterministic race fixture that collapses the native extent, unmounts the logical anchor, and asserts that the visual guard is retained. Verification: pnpm exec tsx src/__tests__/transcript-reader-extent-race.test.tsx (59 passed).
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.
Summary
Fixes the Reasonix transcript behavior reported in #9711: upward scrolling could jump to another location, the scrollbar could move independently of the viewport, and the question navigator could land on the wrong content. It also removes the empty fenced Markdown cards shown in the attached capture.
What changed
rowKeyand viewport offset across history prepends; stale generations and writers are rejected.Verification
pnpm test:transcript— 177 tests passed.pnpm test:typecheck,pnpm typecheck,pnpm check:scroll-writer— passed.pnpm build— passed; initial JavaScript gzip 463.6 KiB, raw JavaScript/CSS 2473.3 KiB within the narrow calibrated budgets.10.0.26200, runtime152.0.4191.53: sustained native wheel smoke passed (936 rows, 4,459 frames, maximum reverse 0px, blank frames 0); native composer smoke passed (168 samples, maximum reverse 0px); native selection/compositor smoke passed 3/3 iterations.main-v2(a1faaf5), macOS WKWebView smoke exposed delayed blank range replacement. The reader-lease writer fence plus a bounded visual hold for a temporarily unmounted anchor now cover that path; focused reader/anchor race suites and the corrected build pass.Scope and compatibility
Cache-impact: none - this PR only changes transcript viewport ownership and shared Markdown presentation; provider-visible prompt/tool bytes are unchanged.
Cache-guard: no cache-sensitive files changed; frontend transcript and Markdown regression suites plus the existing cache guard remain green.
Documentation-impact: none - the change fixes internal transcript ownership and rendering behavior without changing user-facing commands, configuration, or public API documentation.