Skip to content

feat(desktop)!: drop the transcript range boundary notice - #5173

Closed
Astro-Han wants to merge 4 commits into
mainfrom
refactor/transcript-drop-gap-notice
Closed

feat(desktop)!: drop the transcript range boundary notice#5173
Astro-Han wants to merge 4 commits into
mainfrom
refactor/transcript-drop-gap-notice

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Removes the transcript range boundary notice — the 上方还有未加载的较早消息 / 下方还有未加载的较新消息 rows and their Load earlier / Load newer buttons.

The notice told the reader that the window they are reading does not hold the whole transcript, and gave them a button to extend it. Neither is theirs to care about. The window is a memory budget the Renderer manages, the band already fills the edge a reader approaches, and every arrival and departure of the notice is a height change above or below the reader with no content behind it.

It arrived in #4560 without a design decision — the merge's own before/after images do not show it — and #5147 has since had to exclude it from browser scroll anchoring, because a row that is not content was becoming the anchor the reader's position is measured from. Users have reported the transcript feeling stalled around these boundaries.

Before / after

PartialHistoryNotice story, before and after

Light only: the storybook harness here does not switch themes from the CLI, and every rule removed is theme-agnostic — the gap row's width, margin and padding, plus its overflow-anchor: none exclusion.

What goes with it

  • the .maka-transcript-gap-row exclusion from overflow-anchor, no longer needed;
  • transcript-row-projection, whose only job was placing the two rows among the Turns;
  • transcriptGap copy in all three locales;
  • the explicit history-load path the notice was the only entry point to — loadHistory('earlier' | 'later'), historyLoadPending and the pending plumbing behind it. The one navigation a reader still makes is returning to the tail, which is now returnToLatest().

A bug the notice was hiding

Returning to the tail cancelled an outstanding bookmark frame only because the notice's pending state forced a render. Without that render the queued frame scrolled the reader back to the bookmark they had just left. An explicit tail pin now outranks a queued restore, which is what the return-to-latest button consumes a pending bookmark frame always claimed to check — it fails without the fix.

Verification

  • 2425 apps/desktop tests, 401 @maka/ui tests
  • all four apps/desktop tsconfigs, Biome format and lint, build-storybook
  • partial-history-notice.spec.ts (rewritten around what survives: a bounded range reaches its whole history with nothing to click) and transcript-scroll-cost.spec.ts, 2 repeats each, plus streaming-remount.spec.ts and quote-window-boundary.spec.ts

Base

Stacked on #5170, which is in review; this is a user-visible removal with its own acceptance, so it is not folded into that one.

Main owned the presentation window: a resident range with reading
anchors, navigation versions, overlay settlement across pages and
eviction bookkeeping, while the Renderer kept its own scroll state on
top. Two owners of one window meant every reader gesture met Main's
range accounting, and the reading position existed in six
representations that had to agree.

Main now keeps a tail cache and answers page requests pass-through.
`loadBefore` / `loadAfter` return a page without touching the cache,
`loadAround` and `loadLatest` return a reset snapshot, and catch-up
evicts whole Turns from the oldest edge while always keeping the newest.
The Renderer owns the window: it extends it by pixel bandwidth, trims it
with `retain`, and re-opens the edge it trimmed as a gap. `hasOlder` /
`hasNewer` are Host page cursors on an answer; the Renderer combines them
with its own coverage, so paging reaching an end never means nothing
exists outside the window. `navigationVersion` invalidates navigation
only — Session id, replica generation and Host epoch stay independent
identity checks.

Overlay settlement follows the same rule. Main used to broadcast
`completedOverlayMessageIds`, so a window retired an overlay whether or
not it installed the durable row that replaced it. A window off the tail
declines tail growth, so it deleted the overlay and dropped the body in
the same batch and the Turn vanished from the reader's view. The catch-up
broadcast also filtered its rows through Main's own tail residency, so a
row installed and immediately evicted under budget never reached any
window at all. Retirement is now the window's own inference — installing
a durable row retires the overlay it settles — and the broadcast carries
every row the catch-up read.

The reading position is one representation: the authority publishes the
Turn crossing the top of the scrollport, the prompt rail derives its tick
from that instead of running its own observers, and the controller keeps
a bookmark to re-anchor after a replica generation change.

Behavior changes: opening a Session prefetches history until two
viewports sit above the reader; tail growth marks read while any consumer
is open, where it previously waited for the reader to have nothing newer.

Supersedes #5147, which patched these symptoms at the old window
authority.

Closes #5163

Generated-by: Claude Code
The suite asserted that paging works and stays bounded, and sampled the
mounted count only once the range had settled. Neither says where the
reader ended up while a page was installing, which is the whole of what
#5163 reports. This probe reads every frame, and at each change of the
mounted range compares a Turn present on both sides: with no input
between two frames its document position must not move, so
`Delta top + Delta scrollTop` is zero unless the boundary displaced the
reader.

Measured settled-to-settled rather than across the changing frames: the
range passes through an intermediate commit that mounts far more Turns
than it keeps, and scroll anchoring corrects after layout, so a reading
taken inside the change reports a correction that never reached the
screen.

It fails on this branch. Pure trims land at 18px; every page install
displaces the reader by about 1800px.

Also stops asserting `data-search-highlight` after waiting for the jumped
Turn to mount. That highlight clears itself 2.2s after the command lands,
so the assertion fails whenever loading the page around the Turn takes
longer than the flash - a 3s pass turning into an 18s timeout under load,
reproduced 2 of 6 runs. The jump's landing place is read from the reading
position instead, which does not expire.

Restores the band-check guard removed in the previous commit. A
controlled comparison over 6 runs each puts the flake at 2 of 6 without
it and 3 of 6 with it, so the ablation that removed it rested on a single
passing run and the guard is not what that flake was about.

Generated-by: Claude Code
…assumed

The window has five writers — command answers, replica replacements, tail
broadcasts, band trims and automatic fills — but only command answers were
invalidatable, and the automatic fill borrowed the reader's own navigation
channel. Five reported defects fall out of those two gaps.

A read is answerable only while what it assumed still holds, and the two kinds
of read assume different things. An extension splices rows onto one edge, so
any replacement of that edge — navigating away, or the band trimming it out —
makes its answer unable to reach what is left; installing it would open a hole
the contiguous-window model cannot express and no edge cursor can name. A
replacement discards the edges, so only a newer navigation makes it stale, and
a replica replacement that answers nothing this window asked for is admitted
whole rather than misfiltered as a superseded command.

Filling an edge is not navigating to it: it gets its own channel, so it no
longer consumes the reader's outstanding jump, and it answers whether the
window moved so a read that changed nothing is not reissued in its own
callback, forever.

Generated-by: Claude Code
The notice told the reader that the window they are reading does not hold the
whole transcript, and gave them a button to extend it. Neither is theirs to
care about: the window is a memory budget, the band already fills the edge a
reader approaches, and every arrival and departure of the notice is a height
change above or below them with no content behind it.

It arrived in #4560 without a design decision — the merge's own before/after
images do not show it — and #5147 has since had to exclude it from browser
scroll anchoring, because a row that is not content was becoming the anchor the
reader's position is measured from. Removing it retires that exclusion, the
row-projection module whose only job was placing it, its copy in three locales,
and the whole explicit history-load path it was the only entry point to: the
one navigation a reader still makes is returning to the tail.

Removing it also exposed a bug it had been hiding. Returning to the tail
cancelled an outstanding bookmark frame only because the notice's pending state
forced a render; without that render the queued frame scrolled the reader back
to the bookmark they had just left. An explicit pin now outranks a queued
restore, which is what the existing regression always claimed to check.

Generated-by: Claude Code
@github-actions github-actions Bot added the effort/XXL Over 2500 readable lines label Sep 11, 2026
@Astro-Han

Copy link
Copy Markdown
Contributor Author

Folding this into #5170 — the boundary notice is part of the same transcript window, and splitting it only made the reviewer re-read the same code twice.

@Astro-Han Astro-Han closed this Sep 11, 2026
@Astro-Han
Astro-Han deleted the refactor/transcript-drop-gap-notice branch September 11, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/XXL Over 2500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant