Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
151 commits
Select commit Hold shift + click to select a range
4108768
fix: repair model picker interactions
SivanCola Jun 1, 2026
dd6e2ff
feat(desktop): unify session experience and stabilize transcript
SivanCola Sep 4, 2026
877f5e3
docs(desktop): document session experience modes
SivanCola Sep 4, 2026
83a05ec
feat(desktop): make session experience authoritative
SivanCola Sep 4, 2026
8506b4c
refactor(transcript): project stable turn blocks
SivanCola Sep 4, 2026
834a46d
feat(transcript): add generation-aware scroll kernel
SivanCola Sep 4, 2026
8d38b33
refactor(transcript): replace Virtuoso with kernel viewport
SivanCola Sep 4, 2026
10dbda8
test(transcript): replay kernel viewport contracts
SivanCola Sep 4, 2026
ab4f706
docs(transcript): ratchet kernel architecture contracts
SivanCola Sep 4, 2026
06e3e47
Merge main-v2 into feature/session-experience-transcript-stability
SivanCola Sep 4, 2026
b173e45
chore(frontend): preserve repolint budget after main sync
SivanCola Sep 4, 2026
d1cb68a
fix(transcript): fence reader corrections to settled geometry
SivanCola Sep 4, 2026
1b13d45
test(transcript): scale native tail traversal to block geometry
SivanCola Sep 4, 2026
2123644
fix(transcript): preserve native reader ownership
SivanCola Sep 4, 2026
e21f8a5
fix(transcript): commit only viewport-covering ranges
SivanCola Sep 4, 2026
18695a7
Merge main-v2 into feature/session-experience-transcript-stability
SivanCola Sep 4, 2026
6b30309
Merge main-v2 into feature/session-experience-transcript-stability
SivanCola Sep 4, 2026
bf845c7
fix(transcript): serialize native measurement commits
SivanCola Sep 4, 2026
c47a171
fix(desktop): publish transcript measurements atomically
SivanCola Sep 4, 2026
0569cf3
fix(desktop): preserve extent during resident transfer
SivanCola Sep 4, 2026
8349bb7
fix(desktop): publish transcript sizes by anchor
SivanCola Sep 4, 2026
34525d4
fix(desktop): fence native viewport range commits
SivanCola Sep 4, 2026
9e584ff
fix(desktop): keep transcript range in layout
SivanCola Sep 4, 2026
60d5c46
test(desktop): target visible transcript actions
SivanCola Sep 4, 2026
63a9e37
test(desktop): await native question landing
SivanCola Sep 4, 2026
80927eb
fix(desktop): preserve native scroll provenance
SivanCola Sep 4, 2026
fc2638c
fix(desktop): gate history loads by scroll direction
SivanCola Sep 4, 2026
e67f54d
fix(desktop): commit transcript geometry atomically
SivanCola Sep 4, 2026
6924ce5
fix(desktop): bind measurements to painted anchor
SivanCola Sep 4, 2026
ab1e834
fix(desktop): fence reader measurement prefixes
SivanCola Sep 4, 2026
1150e88
fix(desktop): reconcile transcript anchor sources
SivanCola Sep 4, 2026
8302ce3
fix(desktop): freeze reader viewport geometry
SivanCola Sep 4, 2026
a67e677
fix(desktop): isolate transcript size publication
SivanCola Sep 4, 2026
835a282
fix(desktop): stage sizes during native ownership
SivanCola Sep 4, 2026
ca6a52e
fix(desktop): budget native scroll runway
SivanCola Sep 4, 2026
222884a
Merge main-v2 into feature/session-experience-transcript-stability
SivanCola Sep 4, 2026
d84e6f0
test(desktop): expose native window gaps
SivanCola Sep 4, 2026
b64ef30
fix(desktop): calibrate native scroll runway
SivanCola Sep 4, 2026
19e8790
Merge origin/main-v2 into feature/session-experience-transcript-stabi…
SivanCola Sep 4, 2026
b450729
test(desktop): terminate WebView2 replay by geometry
SivanCola Sep 4, 2026
4afc649
test(desktop): retain reader settlement evidence
SivanCola Sep 4, 2026
2371c17
test(desktop): isolate reader geometry transactions
SivanCola Sep 4, 2026
fe56d66
fix(desktop): normalize WebKit mouse ownership
SivanCola Sep 4, 2026
306180d
fix(desktop): make native measurement ownership authoritative
SivanCola Sep 4, 2026
de7d599
Merge origin/main-v2 into feature/session-experience-transcript-stabi…
SivanCola Sep 4, 2026
4a6a79c
fix(desktop): fence measurement publication at commit
SivanCola Sep 4, 2026
00cb9ac
test(control): join reconciliation autosave owner
SivanCola Sep 4, 2026
70d411b
test(desktop): settle native reader baselines
SivanCola Sep 4, 2026
5c1a201
test(desktop): finish GTK reader smoke by geometry
SivanCola Sep 4, 2026
0a47331
Merge origin/main-v2 into feature/session-experience-transcript-stabi…
SivanCola Sep 4, 2026
70a4b62
chore(repo): reconcile ratchet after main merge
SivanCola Sep 4, 2026
9d4bd63
test(desktop): target outer reader point on GTK
SivanCola Sep 4, 2026
de1295d
Merge origin/main-v2 into feature/session-experience-transcript-stabi…
SivanCola Sep 4, 2026
9cc1f4e
fix(transcript): own asynchronous navigation through viewport settlement
SivanCola Sep 5, 2026
ff55942
fix(transcript): commit covered geometry through one presentation lif…
SivanCola Sep 5, 2026
5da8657
docs(transcript): close review scope and record acceptance limits
SivanCola Sep 5, 2026
6a04e1a
Merge main-v2 into the transcript stability branch
SivanCola Sep 5, 2026
86c53c8
Unify committed App command ownership
SivanCola Sep 5, 2026
2076ad1
Split the App shell into lifecycle-owned regions
SivanCola Sep 5, 2026
a2a4895
Merge main-v2 before App lifecycle completion
SivanCola Sep 5, 2026
bb1fddc
Fence committed commands and make App lifecycle evidence explicit
SivanCola Sep 5, 2026
5cb605a
Merge main-v2 before final App ownership migration
SivanCola Sep 5, 2026
2b9aba0
refactor(desktop): separate resource commands and navigation lifecycle
SivanCola Sep 5, 2026
eb4908b
Merge main-v2 and integrate management page lifecycle
SivanCola Sep 5, 2026
3b94670
refactor(desktop): isolate topicbar presentation and input contracts
SivanCola Sep 5, 2026
371ca03
test(desktop): verify worktree identity through topicbar behavior
SivanCola Sep 5, 2026
39b05ec
refactor(desktop): unify source-bound controller profile restoration
SivanCola Sep 5, 2026
984e214
refactor(desktop): own source-bound submissions and plan revisions
SivanCola Sep 5, 2026
c81814a
refactor(desktop): move App module-level code into owned modules
SivanCola Sep 5, 2026
d7863f9
docs(desktop): record the App layering migration runbook
SivanCola Sep 5, 2026
cccdb37
refactor(desktop): own window chrome state and lifecycle
SivanCola Sep 5, 2026
17918d1
refactor(desktop): own shell geometry commands and projections
SivanCola Sep 5, 2026
3584bc3
refactor(desktop): own the session status banner stack
SivanCola Sep 5, 2026
cf9b990
refactor(desktop): own session export commands and theme scene
SivanCola Sep 5, 2026
b9bd9a1
docs(desktop): mark banner and export slices complete in the runbook
SivanCola Sep 5, 2026
4569b30
refactor(desktop): own the undo and rewind lifecycle
SivanCola Sep 5, 2026
bb42951
refactor(desktop): own the extension form surface
SivanCola Sep 6, 2026
9a2b609
test(desktop): replace rewind source assertion with owner behavior test
SivanCola Sep 6, 2026
57a0a92
refactor(desktop): own the tab bar commands and runtime reveals
SivanCola Sep 6, 2026
a4bec99
refactor(desktop): own the runtime event surface
SivanCola Sep 6, 2026
ad1a2d7
refactor(desktop): own the command palette
SivanCola Sep 6, 2026
cf92140
refactor(desktop): own the composer command router
SivanCola Sep 6, 2026
63e9c74
refactor(desktop): own the trash and history commands
SivanCola Sep 6, 2026
e9ce39d
refactor(desktop): own remote workspace connection commands
SivanCola Sep 6, 2026
e26dbe2
docs(desktop): record the assembly architectural boundary and slice s…
SivanCola Sep 6, 2026
403b84a
refactor(desktop): bind delivery and remote commands to committed tar…
SivanCola Sep 6, 2026
0bb8441
refactor(desktop): bind clear-session commands to source ownership
SivanCola Sep 6, 2026
3d44110
test(desktop): cover source-bound clear-session ownership
SivanCola Sep 6, 2026
7c284b1
refactor(desktop): keep todo continuation source-bound
SivanCola Sep 6, 2026
8a917de
refactor(desktop): own topic summary lifecycle
SivanCola Sep 6, 2026
ce5d5a0
refactor(desktop): bind runtime job cancellation to session ownership
SivanCola Sep 6, 2026
265aad3
refactor(desktop): bind terminal output insertion to session ownership
SivanCola Sep 6, 2026
f049918
refactor(desktop): bind todo dismissal to session ownership
SivanCola Sep 6, 2026
ad47a7e
refactor(desktop): isolate remaining App bridge ports
SivanCola Sep 6, 2026
d09c692
perf(desktop): keep context ring out of the context panel chunk
SivanCola Sep 6, 2026
585e822
fix(desktop): stabilize topic summary hook dependencies
SivanCola Sep 6, 2026
ebc30bd
test(desktop): follow palette ownership after App extraction
SivanCola Sep 6, 2026
2f590e1
test(desktop): verify trash availability at its command owner
SivanCola Sep 6, 2026
863bdd8
test(desktop): follow reveal guards after tab command extraction
SivanCola Sep 6, 2026
a7d7586
refactor(desktop): own topic shortcut lifecycle
SivanCola Sep 6, 2026
a8fbf12
refactor(desktop): own worktree merge lifecycle
SivanCola Sep 6, 2026
fc22452
refactor(desktop): own footer geometry lifecycle
SivanCola Sep 6, 2026
2b8b4dc
refactor(desktop): own native settings overlay event
SivanCola Sep 6, 2026
f34bbbb
refactor(desktop): own tab projection lifecycle
SivanCola Sep 6, 2026
183de7a
fix(desktop): stabilize extracted lifecycle dependencies
SivanCola Sep 6, 2026
2792918
refactor(desktop): own diagnostics and overlay validity effects
SivanCola Sep 6, 2026
daf9f54
refactor(desktop): own local overlay and session UI lifecycles
SivanCola Sep 6, 2026
ce6d48a
fix(desktop): stabilize local effect host dependencies
SivanCola Sep 6, 2026
45cb66e
refactor(desktop): remove final App effect
SivanCola Sep 6, 2026
84f6925
test(desktop): follow topic shortcut owner after effect extraction
SivanCola Sep 6, 2026
60e6b8c
test(desktop): follow session undo ports after owner extraction
SivanCola Sep 6, 2026
6f6914a
test(desktop): follow palette focus owner after App extraction
SivanCola Sep 6, 2026
25e384c
test(desktop): remove obsolete App source fixture
SivanCola Sep 6, 2026
56e09ff
test(desktop): follow theme lifecycle owners after extraction
SivanCola Sep 6, 2026
10df8dd
test(desktop): remove obsolete rewind App fixture
SivanCola Sep 6, 2026
2dc6ca2
refactor(desktop): make App a pure composition entry
SivanCola Sep 6, 2026
4f0c695
test(desktop): follow AppRuntime source after entry split
SivanCola Sep 6, 2026
295e679
test(desktop): follow AppRuntime for automation layout contracts
SivanCola Sep 6, 2026
a21617c
test(desktop): follow AppRuntime footer wiring
SivanCola Sep 6, 2026
13d11ad
test(desktop): follow goal shortcut wiring into AppRuntime
SivanCola Sep 6, 2026
540a3e0
test(desktop): follow history retry wiring into AppRuntime
SivanCola Sep 6, 2026
c0a8a9f
test(desktop): follow MCP decision wiring into AppRuntime
SivanCola Sep 6, 2026
d791152
test(desktop): follow navigation surface wiring into AppRuntime
SivanCola Sep 6, 2026
ac4ba04
test(desktop): follow composer readiness wiring into AppRuntime
SivanCola Sep 6, 2026
e2bdab7
test(desktop): follow theme background wiring into AppRuntime
SivanCola Sep 6, 2026
b2c9e7a
test(desktop): follow AppRuntime source contracts
SivanCola Sep 6, 2026
d47a3d1
test(desktop): follow remote palette and export owners
SivanCola Sep 6, 2026
851673c
ci(desktop): gate App lifecycle and memory qualification
SivanCola Sep 6, 2026
8188303
test(desktop): attribute stable App memory soak cohorts
SivanCola Sep 6, 2026
52c723c
fix(desktop): ignore isolated memory soak counter blips
SivanCola Sep 6, 2026
2863e59
docs(desktop): record App soak attribution evidence
SivanCola Sep 6, 2026
eaa8925
Merge main-v2 before final App qualification
SivanCola Sep 6, 2026
e459ced
perf(desktop): lazy-load session runtime continuations
SivanCola Sep 6, 2026
f3cc323
chore(desktop): remove obsolete App size baseline
SivanCola Sep 6, 2026
bff510c
chore(desktop): sync repolint baseline with main-v2
SivanCola Sep 6, 2026
c1f017d
chore(desktop): keep pure App entry out of repolint baseline
SivanCola Sep 6, 2026
5ab73da
test(desktop): point owner tests at sessionRuntimeOwner
SivanCola Sep 6, 2026
ba849d2
fix(desktop): keep the App memory gate honest about attribution limits
SivanCola Sep 6, 2026
5ae3f5d
refactor(desktop): complete AppRuntime composition split and owner mi…
SivanCola Sep 6, 2026
474f395
chore(desktop): ratchet bundle budgets with recorded attribution
SivanCola Sep 6, 2026
2c07386
Merge main-v2 exact-prompt protocol into the completed App layering
SivanCola Sep 6, 2026
a963b36
docs(desktop): record completed App layering and merged qualification…
SivanCola Sep 6, 2026
67e91c5
fix(desktop): normalize relative PLAYWRIGHT_BROWSERS_PATH in App benches
SivanCola Sep 6, 2026
c58dc03
fix(desktop): import Playwright after browsers-path normalization in …
SivanCola Sep 6, 2026
cb87d66
fix(desktop): distinguish transient counter excursions from persisten…
SivanCola Sep 6, 2026
2f8f6a9
test(desktop): retarget remote export/todo source contracts to their …
SivanCola Sep 7, 2026
3bc7bcb
fix(desktop): measure the memory soak tail at rest with a settled sample
SivanCola Sep 7, 2026
d0286f8
fix(desktop): reconcile cold transcript geometry and cancel backgroun…
SivanCola Sep 7, 2026
89b83a5
fix(desktop): commit transcript measurements without native scroll debt
SivanCola Sep 7, 2026
8f35cf3
Merge main-v2 and reconcile the measured transcript bundle budget
SivanCola Sep 7, 2026
a823eb5
ci: request validation after resolving the latest-base conflict
SivanCola Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ jobs:
pnpm --dir frontend test:motion-browser
pnpm --dir frontend test:composer
pnpm --dir frontend test:todo-visibility
pnpm --dir frontend test:app-lifecycle
PLAYWRIGHT_BROWSERS_PATH=.pw-browsers pnpm --dir frontend test:app-browser
PLAYWRIGHT_BROWSERS_PATH=.pw-browsers pnpm --dir frontend test:app-memory
pnpm --dir frontend test:all
pnpm --dir frontend test:transcript
xvfb-run -a env REASONIX_TRANSCRIPT_NATIVE_THUMB=1 pnpm --dir frontend test:transcript-browser
PLAYWRIGHT_BROWSERS_PATH=.pw-browsers pnpm --dir frontend test:transcript-reader-browser
Expand Down
154 changes: 118 additions & 36 deletions desktop/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,124 @@

## Transcript scroll discipline

The transcript (`frontend/src/components/Transcript.tsx`, react-virtuoso) has
one structural rule set, earned across #8657/#8688 and the follow-up refactors.
Keep to it when touching anything that can move the transcript viewport.
The transcript (`frontend/src/components/Transcript.tsx`) is governed by
`TimelineProjection` and the generation-aware `TranscriptKernel`. Keep these
contracts when touching anything that can move the transcript viewport.

- **Single writer**: only the scroll arbiter — `frontend/src/lib/useTranscriptScrollArbiter.ts`
and its extracted controllers (`transcriptTailSettle.ts`,
`transcriptAnchorCompensation.ts`) — may call
`virtuosoRef.current.scrollTo/scrollBy/scrollToIndex`, and raw
`scroller.scrollTop` assignments on transcript surfaces are equally
off-limits (route through the arbiter's `SCROLL_TO_OFFSET` channel, e.g.
owner `"anchor-compensation"` / `"block-window-prepend"`). Everything else
(jumps, tail-follow, selection edge scrolls, layout recovery) submits
requests to the arbiter. `frontend/scripts/check-single-scroll-writer.mjs`
enforces it statically; `lib/transcriptScrollProbe.ts` observes it at
runtime. Never add a second writer — extend the arbiter's reducer
(`lib/transcriptScrollArbiter.ts`) with an explicit transition instead.
- **Preemption is explicit**: user intent (wheel/touch/key/pointer), selection,
and programmatic writers preempt an in-flight recovery through reducer
events that end it in a terminal state (done / cancelled / expired), each
reported to `noteTranscriptRecoveryTerminal`. No silent exits.
- **Native geometry is authoritative**: Virtuoso's `atBottomStateChange` is a
delivery signal, not the bottom truth. Derive bottom ownership from the live
scroller's `scrollHeight - scrollTop - clientHeight`. Browser clamps may not
leave manual reading; only a delivered scroll with explicit reader intent
may re-enter tail-follow. Tail-follow persists across later measurements and
layout growth until explicit user intent releases it.
- **No keyed remounts on content patches**: patches flow through `data` only;
Virtuoso re-measures mounted rows itself. Remounts happen only on surface
switches and blank-watchdog rebuilds, and restore from the measured-size
cache (`lib/transcriptMeasuredSizes.ts`) plus a state snapshot
(`lib/transcriptStateSnapshot.ts`) instead of static estimates.
- **Stable identity**: one complete turn is the projection, anchor, and
virtualization unit. Block keys come from backend entry/user identity, never
array position. Prepend and content patches must not rename mounted blocks.
- **Stable viewport actions**: controls that can be activated while range or
geometry state changes keep the same DOM identity. Visibility is state on a
mounted action host; do not conditionally remount it across viewport commits.
- **Generation fence**: session/surface replacement increments the kernel
generation. Every delayed measurement, timer, animation-frame callback, and
write request carries that generation; stale work performs zero writes.
Async paging owns a source-session request identity; question navigation owns
generation plus interaction revision from request through positioned terminal
state. Native takeover cancels navigation, not a valid source data load. Old
completion/finally callbacks may release only their identical request.
- **Single writer**: only `frontend/src/lib/transcriptViewportWriter.ts` may
mutate the transcript's native scroll position. Full-DOM, TanStack window,
Markdown, selection, question navigation, prepend, composer resize, and tail
follow all submit transactions to `TranscriptKernel`. The static gate in
`frontend/scripts/check-single-scroll-writer.mjs` must reject any bypass.
- **Scroll provenance**: a physical writer offset remains pending until its
matching native `scroll` event is consumed or a different offset proves
user movement. Starting a gesture must not relabel a delayed writer event as
native input, and top-edge pagination reacts only to native-owned upward
movement, never a writer event or a reader moving away from the boundary.
- **Explicit terminal state**: every transaction ends committed, cancelled, or
expired. User input and selection preempt lower-priority work; question jumps
outrank display/prepend/restore/resize, which outrank tail follow.
- **Native geometry is authoritative**: bottom means
`scrollHeight - scrollTop - clientHeight <= 4`. TanStack computes prefix
sizes and mounted ranges only; its measurement compensation is disabled and
its scroll callback must never bypass the writer.
- **Covered-range commit**: the Window Adapter may paint a TanStack candidate
only when it covers the current native viewport. Retain the last covering
geometry snapshot when a candidate is stale: mounted items, complete prefix, total extent,
and scroll margin are one atomic value and must never come from different
measurement generations. If a native jump invalidates both, rebuild once
from the prefix-size ledger while preserving every protected block.
Spend the bounded mount budget directionally: keep a reverse cushion, then
allocate the remaining runway ahead of current native motion so asynchronous
WebView scrolling cannot outrun the mounted range. Never exceed the completed
block mount cap to hide coverage races. The cap belongs to the whole adapter,
not only cold history: resident and protected blocks consume the same budget.
Retire measured resident prefixes and trim optional overscan before declaring
budget failure. Materialize third-party lazy cache views with `Array.from`;
a Proxy over mutable typed-array sizes is not an immutable prefix snapshot.
If no candidate, retained snapshot, or ledger reconstruction covers the
viewport, fail closed through the shared full-DOM safety renderer before
paint; never commit an uncovered range and detect the blank afterward.
Measurement-only notifications cannot replace the painted range while
native input owns an unchanged viewport. Native viewport geometry is an
external store: range renders must use its immutable snapshot so React
cannot commit a range calculated before a newer compositor scroll offset.
Window items use absolute layout `top`, not transforms that can put range
position and native scroll state into independently committed compositor
transactions.
- **Anchor-safe measurement commit**: DOM measurements enter a block-keyed
staging ledger before they can change TanStack's prefix sizes. While native
input owns reader intent, the entire painted viewport is immutable: both the pre-measurement
prefix range and mounted DOM must place a block after the viewport before it
becomes a publish boundary. The logical Kernel anchor may only move that
boundary later. This prevents stale listeners, underestimated ranges, or
lazy blocks from reflowing any content the reader can see. TanStack's
`scrollMargin` is measured in the native scroller's coordinate space,
including Transcript padding and any prefix. Earlier and visible sizes remain
staged during native ownership; only post-viewport overscan may publish.
After ownership ends, publish staged DOM sizes under a Kernel logical-anchor
restore transaction. Prefix layout and anchor correction must complete in
one before-paint commit, cancelling any queued older geometry work.
Preserve the first reading anchor, while allowing later
blocks to move with actual content growth; freezing every old top would
overlap expanded content. Observe mounted absolute blocks as well as the
projection root, since local folds do not change the root extent. Tail intent
does not refine invisible cold history; its exact geometry belongs to resident DOM. During
bounded wheel input, the lazy measurement ledger owns a publish boundary at
least the unconsumed pixel-mode native steps plus one viewport ahead
of the painted viewport in both prefix and DOM geometry. Retire travel only
after physical viewport progress is observed; counting already-consumed
travel indefinitely freezes future measurements and builds a release-time
geometry debt. Keep the one-viewport reserve throughout the gesture lease. Touch, selection, keyboard jumps, nested handoff
without a bounded delta, and native thumb drag are unbounded: every cold
measurement remains staged until ownership ends.
Publish one immutable Reasonix snapshot, then transfer that exact published
batch into TanStack's keyed size cache in the same browser task. Never call
TanStack `measure()` for a measurement publish: it clears the keyed cache and
rebuilds the protected prefix. Never base correctness on an idle timeout,
enable TanStack-owned ResizeObserver publication, or add platform-specific
scroll compensation.
- **Resident active tail**: the active turn and at least the two newest
completed turns stay in ordinary DOM. A resident block may enter windowed
history only after it is a viewport away and owns no anchor, focus, or
selection endpoint. Measure every contiguous leaving prefix into one ledger
snapshot before changing the resident boundary; estimated-size migration is
forbidden. Stream growth in reader intent performs zero writes.
- **Bounded safe mode**: two blank/invalid/correction anomalies without an
intervening healthy frame in one generation switch that session to full DOM
until the next surface generation. Do not add a second rendering stack or a
persistent user flag. Normal full, windowed, and safety use one presentation
and observer lifecycle. Immediate safety mounts all currently paged blocks
with the last trusted cold prefix coordinates, retaining native keyed hosts,
focus, selection, and reader offset without a structural write. It stops
window eviction; it does not force offscreen history or large bodies to load.
- **One geometry scheduler**: full/windowed layout, ResizeObserver, surface
paint, and auto-fill use cancellable Kernel frames. Observers bind generation
at registration and reject queued notifications even after disconnection.
Coalesce health validation and correction at one geometry entry; only one
clock-owned re-observation may precede the second-fault safety latch.
- **Systemic fixes**: repair ownership, projection, measurement, or presentation
invariants in their common layer. Do not accumulate scenario-specific retry
branches, platform compensation, or progressively relaxed acceptance gates.
- **Deterministic clocks**: new scroll logic must go through the same
injectable patterns as the existing code (global `requestAnimationFrame`,
`Date.now`, `window.setTimeout`) so the fake-clock harness can drive it —
no `performance.now`-only budgets or ad-hoc timers.
injectable clock used by `TranscriptKernel` (`requestAnimationFrame`,
`Date.now`, timer functions). No real sleeps or hidden retry clocks.
- **No redundant physical writes**: a transaction whose requested offset has
already landed may commit as a no-op, but must not assign `scrollTop` again.
- **Race tests are mandatory**: any scroll-behavior change ships with a
deterministic case in `frontend/src/__tests__/transcript-recovery-race.test.tsx`
(JSDOM + fake rAF/clock harness, stubbed `VirtuosoHandle`). Run
`pnpm test:transcript` before committing transcript changes.
deterministic event sequence in `frontend/src/__tests__/transcript-kernel.test.ts`
and, when relevant, a viewport/projection case. Run `pnpm test:transcript`
before committing transcript changes.
13 changes: 9 additions & 4 deletions desktop/cmd/transcript-native-smoke/host_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <math.h>
#include <unistd.h>

static const int32_t ReasonixFinishWheelDelta = -1440;

@interface ReasonixTranscriptSmokeHost : NSObject <WKNavigationDelegate, WKScriptMessageHandler>
@property(nonatomic, strong) WKWebView *webView;
@property(nonatomic, strong) NSWindow *window;
Expand Down Expand Up @@ -159,7 +161,7 @@ - (void)userContentController:(WKUserContentController *)controller didReceiveSc
self.transcriptPoint = NSMakePoint(x, self.webView.isFlipped ? y : NSHeight(self.webView.bounds) - y);
[self.window makeFirstResponder:self.webView];
[self ensureInteractionFocus];
// The contract waits for settled Virtuoso geometry before posting ready.
// The contract waits for settled block-window geometry before posting ready.
// Re-establish native focus at that boundary instead of relying on the
// workflow's earlier best-effort app activation.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 200 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
Expand Down Expand Up @@ -277,7 +279,10 @@ - (void)finishNativeWheelTail {
- (void)finishWheelBurst:(NSInteger)remaining {
if (self.done) return;
if (remaining > 0) {
[self dispatchWheelDelta:-120];
// The block ledger is taller than the retired row surface. Keep the same
// native event/time bound while giving the finishing phase enough physical
// wheel travel to reach the measured tail without a JavaScript scroll.
[self dispatchWheelDelta:ReasonixFinishWheelDelta];
self.finishWheelEvents += 1;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
[self finishWheelBurst:remaining - 1];
Expand All @@ -289,7 +294,7 @@ - (void)finishWheelBurst:(NSInteger)remaining {

- (void)sendWheel:(NSTimer *)timer {
// Sustain small high-resolution trackpad deltas without outrunning
// Virtuoso's mount window on a loaded hosted WebView.
// the block mount window on a loaded hosted WebView.
const NSInteger totalTicks = self.localMicro ? 40 : 1200;
if (self.wheelTick >= totalTicks) {
[timer invalidate];
Expand All @@ -300,7 +305,7 @@ - (void)sendWheel:(NSTimer *)timer {
});
return;
}
// Hosted WebKit can coalesce events while Virtuoso commits its last range.
// Hosted WebKit can coalesce events while the block window commits its last range.
// After an idle boundary, keep sending bounded ordinary wheel events until
// the native extent is stably at the tail. This remains native input; the
// JavaScript probe only decides when the platform-specific fixture stops.
Expand Down
Loading
Loading