Skip to content

fix(virtualizer): hold the reader by position when a prepend retires every key - #8001

Merged
iamwhatever merged 1 commit into
mainfrom
fix/chat-scroll-flicker-and-history-truncation
Sep 3, 2026
Merged

fix(virtualizer): hold the reader by position when a prepend retires every key#8001
iamwhatever merged 1 commit into
mainfrom
fix/chat-scroll-flicker-and-history-truncation

Conversation

@bolichen97

@bolichen97 bolichen97 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Problem / Motivation

Two power users report that, in a long session, the transcript "suddenly jumps back to the start of the session" with no scroll input, and they then have to click the jump-to-bottom arrow to get back to the latest message. The jump happens around the end of a turn.

Why it matters

It hits exactly the people who use the dashboard the most: only a transcript longer than the entry page can grow in front, so short sessions never see it, while an all-day session pays it at turn boundaries. Losing the reading position to the very beginning of a transcript is the most disorienting form of a scroll defect, and it is what makes the surrounding follow/flicker behaviour visible in the first place (the user has to re-engage follow by hand).

What changed (motivation → approach → change)

Symptom → root cause. useVirtualChat's prepend trigger (TRIGGER 1) compensates a front growth by anchoring on the topmost visible row whose key survives into the new list. When a wholesale transcript rebuild lands together with the growth — the post-turn refreshSlot replacing a 100-row entry page with the whole transcript while every streamed row is re-identified by its server copy — no visible key survives. captureTopAnchorFrom returns null, the capture stands down, and neither the window nor scrollTop moves while hundreds of rows materialise in front. The old index range, unchanged, is now the start of the transcript.

Reproduced in the existing prepend harness: a 30-row tail behind 1000 new rows, follow released, every tail key changed — the reader landed on row 27 of 1030 with scrollTop untouched.

The identity carry in mergePreservedClientTs is best-effort (it matches streamed rows to their server copies by role + content) and its own doc records the scroll jump that happens when it misses. The virtualizer's fallback for "no key survives" therefore has to hold position rather than give up.

Change. When no visible key survives, the topmost visible row is re-found by position and handed to the same Part 1 re-base and the same Part 2 correction the surviving-key case uses — no new path, no new consumer, no new invalidation key.

The displacement is not the net count growth. The net count equals a row's displacement only for a pure front insert; a rebuild that also grows the tail (a reconnect catching up on missed rows) would overshoot the reader by the tail growth — a shape where a naive positional anchor is worse than the old stand-down. So the displacement is read off the nearest row (by old index) whose key did survive; only when no key survives anywhere does the net count stand in, which keeps the reader's distance from the end. Both paths now re-base by that same measure — the anchored row's own displacement, signed — instead of the net count. Rows coalescing above the reader while the tail grows move them up although the count grew; a net-count re-base carried the window the wrong way past the anchor and left a blank band. Two guards make the signed re-base safe: TRIGGER 2 yields while a re-base is in flight (a negative re-base moves the range up, which TRIGGER 2 reads as a window shift and would otherwise overwrite the pending anchor), and Part 1 clears the slot on every exit from 'awaiting-rebase' so no anchor can be stranded there.

  • website/src/hooks/virtualizer/useVirtualChat.ts — TRIGGER 1: key→index map replaces the survivor set; positional fallback with nearest-survivor displacement (binary search over survivors in old-index order); signed re-base by displacement on both paths; TRIGGER 2 yields during a re-base; Part 1 stage-sentinel + unconditional clear. captureTopAnchorFrom also returns the captured row's index; the slot-switch anchor persists {key, top} only.

Tests

  • useVirtualChat.prependAnchor.test.tsx
    • New holds the reader by POSITION when a front growth retires every visible key: 1000 rows in front, every tail key changed; asserts new index old + 1000, the positional successor key, unchanged screen offset (±1px), scrollTop advanced.
    • New ... when getKey is INDEX-ADDRESSED (ChatPage's rowKeys[i] shape): distinguishes pairing the new row with the current getKey from mispairing it with the previous render's — the latter yields a key no mounted row carries and leaves the viewport in spacer.
    • New moves the positional anchor by the nearest SURVIVOR's displacement, not the net count growth: front +10, tail +5, visible keys retired, rows above the viewport keep theirs; asserts the reader moved by 10.
    • New re-bases by the anchored row's own displacement when a surviving key is found: front +3, tail +50 with a surviving visible key; a net-count re-base would push the window 50 rows past the anchor.
    • New holds the reader when rows ABOVE them coalesce while the tail grows (negative displacement): 3 rows above collapse to 1, tail +10 (count +8, displacement −2); asserts the row's screen offset is held and no blank band.
    • Rewritten shows no blank band when a prepend retires EVERY visible key: previously pinned the stand-down as an accepted loss of position; now pins that the correction runs and a mounted row still covers the viewport top.
  • Mutation-verified, each mutation failing a distinct test: hook change reverted; fallback paired with prependPrev.getKey; inserted - 1; displacement replaced by the net count on the fallback path; displacement replaced by the net count on the surviving-key path; positive-only arming gate restored; TRIGGER 2's in-flight yield removed.
  • Full vitest run: 27889 passed. tsc -b, eslint src/ --max-warnings 597, jscpd clean.

Manual verification

Not reproduced in a live browser in this PR: the harness reproduces the mechanism (front growth + total key retirement with follow released), not the specific rebuild that produced it on the reporters' machines. Reporters are on v0.5.0-insider.9, whose virtualizer predates this and the other recent scroll fixes; a build carrying main is what lets them confirm.

Related Issues

Related: #7045 (bubble vanish / reconciliation), #4394 (same-tick row key aliasing — a key-retirement source this fallback tolerates).

Pattern harvest

Rule candidate: review-prompt
Pattern: a compensation path whose "anchor not found" branch is a silent no-op — on a list whose front grows, doing nothing is never neutral; fall back to a positional anchor or fail loudly.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable) — N/A, no spec describes this trigger's fallback
  • No secrets, credentials, or internal references in the diff

@bolichen97
bolichen97 requested a review from a team September 2, 2026 21:46
@bolichen97
bolichen97 requested a review from a team as a code owner September 2, 2026 21:46
@bolichen97
bolichen97 force-pushed the fix/chat-scroll-flicker-and-history-truncation branch from a4e3890 to b7d26b2 Compare September 2, 2026 22:11
buluoray added a commit that referenced this pull request Sep 2, 2026
…pin bottom

Two fixes for the refresh-then-walk bounce (parked at the bottom while
the archived-history walk lands pages above).

Headless boundary turn keys on its TAIL. The topmost turn of a
partially loaded transcript has no opening prompt (it is in an unloaded
older page), and virtualKeyFor keyed every turn on its LEAD -- so each
walk landing that fed the boundary turn's head renamed the row. React
treats a renamed row as a new element: the giant row unmounted and
remounted once per landing wave, its Pierre surfaces visibly
re-highlighting ("the file-change section keeps reloading") and its
height-cache line orphaned. A headless turn now keys on its tail
(hlt-<id>), which content arriving above cannot touch, so the key holds
across the whole walk and flips to the lead exactly once when the
opening prompt lands (that one flip's height migrates via the departure
rename pass). Scoped tightly: index 0 only (mid-list interim folds keep
the #253 lead-key contract), never the trailing turn (positional guard)
and never an in-flight turn (complete === false, the semantic guard
that also covers refreshing into a single giant running turn).

Bottom re-pin runs regardless of anchor age. The height-sync
compensation effect gated EVERYTHING behind HEIGHT_ANCHOR_MAX_AGE_MS,
including the stick branch -- but that branch reads only live geometry
(bottomTarget), so staleness cannot mis-correct it, while a landing
wave is exactly when the main thread is busiest and the effect most
likely to run late. A late reprice fell through to the post-paint
pinAuto: one visible frame of displacement per wave. The age gate now
guards only the viewport-relative candidate path (its documented
hazard), and the bottom re-pin always runs pre-paint.

Complementary to upstream #8001 (positional fallback when a prepend
retires every visible key): this change keeps identities alive so that
fallback is needed less; no overlap in the code paths touched.
@bolichen97

Copy link
Copy Markdown
Collaborator Author

Audit note — this overlaps #7916 on the same silent stand-down; consolidate rather than land both

This PR and #7916 delete the same stand-down at the same site — TRIGGER 1 in website/src/hooks/virtualizer/useVirtualChat.ts, where captureTopAnchorFrom returns null because no visible row's key survived a front growth. Only one of the two designs can land there; both are real work.

The overlap

Both PRs delete the SAME silent stand-down at the SAME site: TRIGGER 1 in website/src/hooks/virtualizer/useVirtualChat.ts, where captureTopAnchorFrom returns null because no visible row's key survived a front growth, so if (prependAnchor) is skipped, part 1 never rebases the window and scrollTop never moves while N rows materialise in front. On main (blob aefb9aee1f, verified byte-identical to #8001's pre-image) that block is unchanged. Each PR's own prose names the identical mechanism: #8001 "no visible key survives ... captureTopAnchorFrom returns null, the capture stands down"; #7916's in-code comment "there is no surviving key to fall forward to, the capture stands down, and the landing hits the reader uncompensated", and its body "Tree-math fallbacks cover the three anchor-miss holes". Two incompatible designs for one requirement: #8001 re-finds the topmost visible row by POSITION (const j = idx + inserted; items[j]) and feeds it to the existing DOM-measured part-1/part-2 path; #7916 keeps the capture id-based and compensates by ARITHMETIC when it misses. Both then rewrite the SAME test case in website/src/test/useVirtualChat.prependAnchor.test.tsx ("shows no blank band when a prepend retires EVERY visible key") and flip the SAME assertion expect(readScrollTop()).toBe(scrollBefore) in the same direction — #8001 to toBeGreaterThan(scrollBefore) plus expect(after[0].idx).toBe(visible[0].idx + 10), #7916 to toBe(scrollBefore + 10 * 100). In that harness's uniform 100px geometry the two designs produce the SAME number, which is the clearest proof that one user-visible outcome is being implemented twice. Only one design can own that branch: if both landed, #8001's positional capture makes prependAnchor non-null, which renders #7916's !shiftAnchorRef.current arithmetic branch dead and de-fangs its native-anchoring-subtraction test. Coverage is asymmetric: #7916 subsumes #8001; #8001 is a subset of ONE of #7916's dozen-odd fixes, so #7916 is in no way redundant with #8001. Hence CONSOLIDATE on #8001 — its half must be dropped, or must replace #7916's, decided once — rather than CLOSE: #7916 is unlanded, is a 64-file mega-PR, and its base for this very file is one virtualizer commit stale, so retiring the small mutation-verified fix outright would leave the defect live on main for an unbounded interval.

Why #7916 is the one to build on

Completeness on the shared requirement, plus reproduction evidence. On the anchor-miss hole #7916 ships four mechanisms where #8001 ships one: (1) anchor identity moved off display keys onto a new getStableId option (ChatPage wires getStableId: stableAnchorId, the row's TAIL message) so the miss often never happens; (2) prependCountRef.current = itemCount - prependPrev.count hoisted OUT of the if (prependAnchor) guard so a failed capture still reaches part 1; (3) a part-1 // ANCHOR-MISS FALLBACK summing offsetIndex.getHeight(i) over the inserted block and writing insertedPx - max(0, nativeAdj), subtracting Chromium's native CSS scroll anchoring so the write cannot double-compensate; (4) a part-2 // CONSUME-MISS FALLBACK for the anchor row unmounting between capture and consume, which #8001 does not address at all. #7916 also reproduced on a phone rig and a real iPhone with before/after counters (wLost=2); #8001's own body says "Not reproduced in a live browser in this PR". #7916 additionally restructures the very part-2 consumer #8001's anchor feeds (stage !== 'ready', anchorIdOf), so #8001's patch would need re-authoring against a changed protocol anyway. Counter-weights observed and not decisive: #8001 is far more reviewable (2 files, +59/-13 vs 64 files ~+3800) and sits on main's current blob for this file, while #7916's base predates #7811. Neither PR has any review or approval.

What was deliberately discounted

Discounted as proving nothing: (a) the merge conflict in useVirtualChat.prependAnchor.test.tsx would be independent on its own — the finding rests on the two PRs flipping the same ASSERTION about the same scenario, not on co-editing a file; (b) #7916's other 62 files (history_projection.py archive pagination, MeasureFarm.tsx, pierreStaging.ts, staleShellHeal.ts + sw.js, five scroll-rigs, scrollQuiet.ts, chatSlice bounding, height-sync staleness cut, split IntersectionObservers) are wholly disjoint from #8001 and were NOT counted as overlap; #7916 also edits ChatPage.tsx, a listed hub file #8001 never touches, so that earned no credit either way; (c) #7932 also edits useVirtualChat.ts but on the stick/follow path (evaluateAutoPin, lastUserScrollAtRef), never TRIGGER 1 — irrelevant to this pair; (d) authorship of neither PR was considered; (e) #8001's cited refs #7045 and #4394 are ISSUES (no code), cited "Related:" only, so they cannot cover anything; (f) the 5 commits main gained since #8001's merge base touch no virtualizer file, so #8001 is not stale; (g) PR size/reviewability was noted but not allowed to decide the survivor.

If #7916's design is kept, harvest this from here

If #7916's design is kept, harvest from #8001: (1) the observation that a POSITION-derived anchor fed to the existing DOM-measured part-2 path is inherently immune to double-compensation against native CSS scroll anchoring — it measures the row's real post-layout offset — which would let #7916 retire its prependPreScrollTopRef / nativeAdj bookkeeping and its "subtracts the browser's native scroll-anchoring correction" simulation test; (2) #8001's new test holds the reader by POSITION when a front growth retires every visible key, which exercises a 1000-row front growth (vs #7916's 10) and pins the anchor row's screen offset within 1px; (3) #8001's Pattern-harvest rule, which generalises across this whole hook: a compensation path whose anchor-not-found branch is a silent no-op is never neutral on a list whose front grows. Carry into whichever design lands: both share an unstated assumption that inserted = itemCount - prependPrev.count is ALL front growth, but TRIGGER 1 also arms on a commit that prepends older history AND appends a tail row — there #8001's idx + inserted names the wrong row (turning today's no-op into an over-correction) and #7916's height sum over indices 0..inserted-1 prices the wrong block.


From a repository-wide duplicate/overlap audit of every pull request open against main, re-run against the current tree (origin/main 680baf9448dc). This PR is not being closed — the note is informational. If the reading is wrong, please correct the reasoning rather than just the conclusion.

…every key

A front growth that lands together with a re-identification of every visible
row -- the post-turn refresh rebuilding the transcript wholesale while the
reader is scrolled up -- left the prepend trigger with no surviving key to
anchor on. The capture stood down, so neither the window nor scrollTop moved
while hundreds of rows materialised in front: the viewport, still at its old
index range, was showing the START of the transcript. Reported as "the session
history suddenly jumps back to the beginning, then I have to click the down
arrow", and reproduced in the prepend harness (a 30-row tail behind 1000 new
rows: the reader landed on row 27 of 1030).

When no visible key survives, the topmost visible row is re-found by POSITION
and handed to the same re-base and the same correction the surviving-key case
uses. Its displacement is read off the NEAREST row (by old index) whose key did
survive, because the net count growth equals a row's displacement only for a
pure front insert: a rebuild that also grows the tail (a reconnect catching up
on missed rows) would otherwise overshoot the reader by the tail growth. Only
when no key survives anywhere does the net count stand in, which keeps the
reader's distance from the end.

Both paths now re-base by that same measure -- the anchored row's own
displacement, SIGNED -- rather than by the net count. Rows coalescing above
the reader while the tail grows move them up although the count grew; the
old re-base carried the window the wrong way past the anchor and left a blank
band. A negative re-base moves the range up, which trigger 2 reads as a window
shift, so trigger 2 yields while a re-base is in flight, and part 1 clears the
slot on every exit from 'awaiting-rebase' so no anchor can be stranded there.
The slot-switch anchor persists key and top only; the capture's index is the
outgoing commit's.

The test that pinned the stand-down as "acceptably lost" is rewritten to pin
the positional hold; further cases cover a wide growth, the index-addressed
getKey shape, mixed front/tail growth on both paths, and a negative
displacement. Mutation-verified: reverting the hook, mispairing getKey with
the previous items, substituting the net count for the displacement on either
path, restoring the positive-only gate, and dropping trigger 2's yield each
fail a distinct test.
@bolichen97
bolichen97 force-pushed the fix/chat-scroll-flicker-and-history-truncation branch from 4ccdeeb to 9f85aef Compare September 2, 2026 23:14
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

UX-level review of 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

UX-Verdict: PASS

Invisible-by-design virtualizer fix: the reader's scroll position now survives a wholesale transcript rebuild instead of jumping to the top — no new surfaces, strings, or states to review.

[UX-REVIEWED] 9f85aef

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Positional fallback lives in the right layer — the virtualizer must tolerate key retirement it can't prevent — and the naive net-count alternative is explicitly analyzed and rejected.

[DESIGN-REVIEWED] 9f85aef

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence checked: the root-cause claim about mergePreservedClientTs is confirmed at website/src/store/chatSlice.ts:2207-2229 (its doc records the key-flip scroll jump, and pass 2 is best-effort role+content matching), the new index field has 2 real consumers, and I counted the sibling "anchor-not-found → silent stand-down" captures in the hook: TRIGGERS 2/3 resolve keys against live items so retirement can't null them, but the splice capture (TRIGGERS 4/5/6, useVirtualChat.ts:787-809) filters through survivingKeys exactly like the old TRIGGER 1 code and still stands down when every visible key retires.

First-Principles-Verdict: CONCERNS

The fix sits at the right mechanism, but the identical silent stand-down survives in one sibling capture — the splice path — which this change leaves unfixed.

What this change ships

Intent: stop a long transcript from snapping to the session start at turn boundaries — a FIX (two user reports).

  1. Reader no longer jumps to transcript start when a post-turn rebuild retires every visible key — justified (the reported defect).
  2. Fallback moves by the nearest survivor's displacement, not net count — justified (net count overshoots on tail-growth rebuilds).
  3. Surviving-key prepends also re-base by the anchor's own displacement — rides along, justified (declared; net-count re-base strands the anchor, pinned by test).
  4. Rows coalescing above the reader no longer nudge the position (negative displacement compensated) — rides along, justified (falls out of 3's signedness).
  5. TRIGGER 2 yields while a re-base is in flight — derived guard for 3/4.
  6. Anchor slot cleared on every exit from awaiting-rebase — derived guard.
  7. captureTopAnchorFrom returns index; persisted anchor stays {key, top} — declared, 2 consumers.
  8. The test pinning "position acceptably lost" now pins the correction — declared.

Watch

  • Point patch with 1 counted unfixed sibling: grepping captureTopAnchorFrom callers, the splice capture (useVirtualChat.ts:787-809) filters through survivingKeys and stands down when every mounted key retires — a mid-list insert above the reader with total visible re-identification (index 0's key held) displaces the reader by the inserted count, uncompensated. Same root cause the PR's own harvest names ("anchor not found → silent no-op is never neutral"); harm is bounded (no front growth), and the general fix is larger — accepted-and-deferred, not a demand.
  • The deeper cause — best-effort identity carry in mergePreservedClientTs (chatSlice.ts:2207) — is correctly named as out of scope; the description states the level this fix sits at.

[FIRST-PRINCIPLES-REVIEWED] 9f85aef

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 9f85aef

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 9f85aef

Verdict parsed from the review's SHA-scoped output markers for commit 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 9f85aefc5997035e3caf50bdd101ef1c4b3d89f5: <one-sentence reason>

@bolichen97

bolichen97 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

Correct, and out of this PR's scope by design. The splice path has no front growth, so its uncompensated shape displaces the reader by the inserted rows' height — bounded — where TRIGGER 1's sent them to the transcript start. This PR fixes the reported defect at its trigger; widening it to a second capture with its own consumer semantics is a separate change.

Filed as #8033 with the repro shape and the fix shape (same nearest-survivor positional resolution as TRIGGER 1, armed into the existing splice consumer). The deeper cause named by the review — best-effort identity carry in mergePreservedClientTs — is out of scope as the description states; this fix makes the virtualizer tolerate a miss it cannot prevent.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 3, 2026
@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention labels Sep 3, 2026
@iamwhatever
iamwhatever enabled auto-merge (squash) September 3, 2026 02:06

@iamwhatever iamwhatever left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: virtualizer holds the reader by position when a prepend retires every key -- one hook plus its regression test.

@iamwhatever
iamwhatever merged commit 0201f91 into main Sep 3, 2026
103 of 111 checks passed
@iamwhatever
iamwhatever deleted the fix/chat-scroll-flicker-and-history-truncation branch September 3, 2026 02:06
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 3, 2026

@chenmingwei23 chenmingwei23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean (Semgrep + CodeQL), security checklist all-NO, AI reviewers green. Category: fix - hold the virtualizer reader by position when a prepend retires every key (clear root cause).

buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with #8001 (per the audit note): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, #8001's positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with #8001 (per the audit note): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, #8001's positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider; the fix's numbers were
measured without it); mint-pod-url validates its argv and resolves the
binary before the subprocess call; the two flagged scroll rigs share
one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling; test
doubles drop `any` casts and unused imports; PR-body screenshot
evidence lives under temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and pins its suppression to the
finding line; the two flagged scroll rigs share one boot preamble via
rig-lib.mjs; deliberate effect-dependency omissions carry the repo's
standard disable-with-reason spelling (zero warnings under the
burned-to-zero ceiling); test doubles drop `any` casts and unused
imports; PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
buluoray added a commit that referenced this pull request Sep 3, 2026
Squashed rebase of the chat-history-polish series onto main after
upstream #8001 landed in the same TRIGGER 1 site.

Archived history: full-chain pagination (backend page endpoints, walk
rigs, scrollQuiet landing buffer), reader-initiated loading only (the
top sentinel, walk poll and idle prefetch all authorize through
shouldAutoFillOlder: real input this session, or a transcript too short
to scroll), and MeasureFarm measured-only landings.

Scroll stability: height-anchor age gate scoped to the viewport-
relative path only (the bottom re-pin reads live geometry and now runs
regardless of anchor age); native-anchoring remainder writes; commit-
phase prepend mirror (concurrent-render tear); 'ready'-only anchor
consumption; display-key rename migration in the height cache; headless
boundary-turn tail keying; WarmSwap frozen-height remounts; sticky
shell activity line (the per-tool collapse ease was the tool-rhythm
bounce); latched running flag for the display layer.

TRIGGER 1 consolidation with the landed #8001 (0201f91): anchor
identity prefers getStableId (the row's tail, which survives regroups,
so the miss rarely happens); on a miss, the landed positional
re-identification (nearest-survivor displacement, net count when none)
re-anchors and feeds the same DOM-measured part-1/part-2 path -- which
is inherently immune to double compensation against native scroll
anchoring; only when even that finds nothing does the arithmetic
fallback compensate (insertedPx minus the native adjustment). Part 1
re-bases by the anchored row's DISPLACEMENT (signed, clamped) rather
than the net count, and carries the net separately for the fallbacks.
The equal-count swap trigger's render-phase bump is latched per commit:
its termination argument assumed a render-phase mirror advance, and the
mirror now advances at commit. The legacy status-line collapse test is
replaced by the freeze contract the sticky rework establishes.

The wholesale-retirement tests pin the positional path's real
invariants: the visual anchor (successor row at the viewport top) and
the no-double-compensation upper bound -- not an exact scrollTop, which
estimate-vs-real spacer pricing legitimately skews until rows
re-measure.

Review-lane fixes folded in: the unconsumed estimateItem/estimateAt
per-row pricing option is removed (dead rider); mint-pod-url validates
its argv, resolves the binary first, and suppresses the taint rule by
its full doubled id on the finding line; the two flagged scroll rigs
share one boot preamble via rig-lib.mjs; deliberate effect-dependency
omissions carry the repo's standard disable-with-reason spelling (zero
warnings under the burned-to-zero ceiling); test doubles drop `any`
casts and unused imports; non-copy literals this branch adds are
shape-excluded with measured rationale (DOMException protocol names,
useStagedMount remount cache keys, the heightScopeKey partition key);
PR-body screenshot evidence lives under
temp-screenshots/chat-history-polish/.
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.

4 participants