feat(desktop): unify session experience and stabilize transcript / 统一会话体验并稳定 Transcript 滚动 - #9777
Conversation
Problem: The desktop exposed three overlapping work-process preferences, while switching disclosure state could invalidate transcript geometry and compete with user scrolling. Root cause: Reasoning, process-fold, and transcript layout paths interpreted independent legacy state; display changes were not represented as one canonical preference transaction. Fix: Add the standard/deep session experience contract with compatibility migration, unify reasoning/tool/process rendering, preserve manual row overrides, and route display changes through the transcript arbiter's anchor-aware single-writer path. Add deterministic preference, settings, compatibility, and bundle-budget coverage. Verification: pnpm --dir desktop/frontend typecheck; pnpm --dir desktop/frontend test:typecheck; pnpm --dir desktop/frontend test:transcript; pnpm --dir desktop/frontend check:scroll-writer; pnpm --dir desktop/frontend build; go test ./internal/config; (cd desktop && go test ./...).
# Conflicts: # desktop/frontend/scripts/check-bundle-budget.mjs
Problem: Sustained WKWebView and WebView2 scrolling could outrun the mounted window or outlive the wheel-only gesture timeout. The resulting delayed range commit allowed a reader restore to reverse native travel, while frame-count browser assertions observed transient geometry instead of the required logical state. Root cause: The viewport adapter exposed too little bounded runway for native scroll delivery, and gesture ownership was leased from wheel dispatch rather than renewed by the authoritative native scroll stream. Test setup and settlement were also tied to locator timing and fixed paint counts. Fix: Renew the native gesture lease through the final scroll event, provide a 12-block TanStack runway under the existing 40-block cap, and make native diagnostics measure writes around delivered wheel input. Browser fixtures now activate setup transitions atomically and await tail or logical-anchor convergence. Verification: Passed pnpm build, transcript unit/browser/reader suites, frontend test:all, Desktop Go test and race, go vet, golangci-lint, and repolint.
Problem: native WebViews can deliver a TanStack range or a measurement notification one paint behind the authoritative scroll position, leaving a blank frame or moving a reader anchor after native input. Root cause: the window adapter painted every asynchronous candidate and gesture completion recaptured geometry even when no native scroll had occurred. Fix: introduce a covered-range commit protocol that retains a valid painted range, reconstructs large native jumps from the prefix-size ledger, and lets only native scroll events update gesture anchors. Record the range source in content-free native diagnostics and document the invariant. Verification: pnpm test:transcript; pnpm build; pnpm test:transcript-browser; pnpm test:transcript-reader-browser; pnpm test:all; go test ./...; go test -race ./...; make lint.
Problem: main-v2 advanced with model-picker interaction fixes while this PR already replaced the same legacy picker and settings paths with generation-fenced components. Resolution: preserve the PR's AnchoredPopover and last-request-wins ModelPicker ownership, incorporate the upstream KeyField save transaction and flex rule, and remove the now-dead legacy ModelSelect path instead of reintroducing two picker implementations. Verification: model-switcher-refresh, settings-refresh-snapshot, and provider-access-card tests; pnpm build; pnpm test:all; transcript browser and reader-browser replays.
Integrate current subagent outcome recovery and reasoning replay work while preserving the TranscriptKernel architecture and tightened bundle ratchets. Normalize subagent terminal outcomes once at the tool-result boundary, lazy-load expanded subagent presentation and CSS, and keep asynchronous feature styling out of the transcript geometry path. Verified with pnpm build, pnpm test:all, transcript browser and reader replays, go test ./..., go test -race ./..., and make lint.
Make the window adapter the sole owner of DOM-to-prefix-ledger measurement publication. Native input now holds geometry commits until its ownership lease ends; changed item sizes then enter one logical-anchor transaction and settle as a batch. This prevents asynchronous ResizeObserver updates from moving mounted blocks behind TranscriptKernel on WebKit and WebView2.\n\nDocument and test the measurement commit invariant without adding platform-specific compensation or relaxing existing gates.
Problem: WebKit and WebView2 could move or blank the reader viewport while a windowed transcript published several measured block sizes one at a time. Native gestures also depended on hook-owned global timers that were outside the kernel generation fence. Root cause: each resizeItem call exposed an intermediate TanStack prefix tree, so browser-native scroll anchoring could react between item updates even though the application single-writer accepted no scroll command. Fix: make Reasonix own an immutable block-keyed measurement ledger, invalidate TanStack once per complete batch, move gesture leases and paint callbacks behind the injectable kernel clock, and make safe mode count genuinely consecutive geometry anomalies. Verification: pnpm test:transcript; pnpm test:transcript-browser; pnpm test:transcript-reader-browser; pnpm test:all; pnpm build; go test .; go test -race .; go run ./tools/repolint
Problem: native WKWebView could still reverse by 5849px after history prepend while the application writer accepted zero reader writes. Root cause: the resident-tail boundary retired one in-flow block at a time without first recording its rendered height. TanStack replaced those blocks with estimates, so a sequence of resident-to-cold transfers changed the native scroll extent during sustained reading. Fix: scan the contiguous eligible resident prefix, exclude anchor/focus/selection owners, publish every exact DOM height in one immutable ledger snapshot, then move the resident boundary once. Verification: pnpm test:transcript; pnpm test:transcript-browser; pnpm test:transcript-reader-browser; pnpm build; go run ./tools/repolint
Problem: the repolint baseline still carried the old App.tsx size exception after the entry became a pure composition boundary.\nRoot cause: baseline cleanup was deferred during the runtime extraction.\nFix: remove the obsolete App exception; AppRuntime must now be split below the production ceiling before repolint can qualify.\nVerification: App entry contract passes; repolint intentionally reports the remaining AppRuntime violation.
Problem: merging current main-v2 exposed baseline drift from unrelated upstream files.\nRoot cause: the integration merge retained the older baseline instead of the current mainline ratchet.\nFix: adopt main-v2's current baseline while keeping the App entry exception removed; AppRuntime remains an explicit new violation until it is split below the production ceiling.\nVerification: repolint now isolates the remaining AppRuntime size failure.
Problem: main-v2 baseline still carried the retired App.tsx size allowance.\nRoot cause: baseline synchronization restored the historical exception after the entry split.\nFix: remove the obsolete entry again; only the unsplit AppRuntime remains as a visible violation.\nVerification: App entry contract remains green.
Problem: runtime-job, session-clear and terminal-output owner tests still imported the execute* entry points from sessionActionOwner. Root cause: the lazy session runtime continuation split moved those execute* functions into sessionRuntimeOwner, but the three owner tests kept stale imports and no longer exercised the real module boundary. Fix: import executeCancelRuntimeJob, executeClearSession and executeTerminalOutputInsertion from sessionRuntimeOwner; sessionActionOwner keeps only the submit* action surface used by sessionPromptExecutor. Verification: tsx runtime-job-owner/session-clear-owner/terminal-output-owner tests pass; tsc --noEmit for main and test configs is clean; no remaining execute* import references sessionActionOwner.
Problem: the retention classifier could report an "attributed" status from stable post-GC counters, and the app-memory verdict required that status for PASS, so CI qualification implied whole-App heap attribution the instrumented evidence cannot prove. Root cause: weak-ref cohorts and DOM/listener counters observe only instrumented tokens. They cannot explain survivors outside that cohort, compiled-code growth, or the mainline control delta; a flat final tail could also hide growth earlier in the post-GC sequence. Fix: the classifier now always reports needs-attribution, compares every checkpoint against the warmed baseline instead of only the final three, validates native counters as safe non-negative integers, flags subscription population drift, and records heap-retainer/control evidence as an explicit offline duty. The bench verdict passes on clean screening (no disqualifying reason via the exported screeningBlockers helper) instead of a self-attributed status, keeping the CI gate green without overstating the evidence. Lifecycle docs describe the screening contract in both languages. Verification: node --test bench/app-memory-evidence.test.mjs (9/9, including screeningBlockers gate coverage); node --check on both bench modules; no remaining consumer of the removed "attributed" status.
…grations Problem: AppRuntime.tsx still held 2,052 lines of orchestrating handlers, inline state, region prop builders and JSX assembly; repolint's per-module 800-line ceiling failed CI lint (1252 over), and runbook slices 1/3/5 kept confirmClearContext, openTurnVerification, handleDeliveryContinue, activeTabIdRef, maximised sync, topic summary wiring and worktree-merge state inline in the runtime controller. Root cause: the layering migration stopped at domain command/effect verticals; cross-region wiring, composition bindings and the view assembly were never moved behind the established owner/adapter/region boundary. Fix: migrate the remaining handlers into app-runtime owners (useSessionClearCommands, useTurnVerificationCommands, useDeliveryContinueCommands and the todo/insert/navigation/chrome/control/ profile/transcript-surface projections), move activeTabIdRef into the activeTabMirror module singleton and maximised sync into the windowChrome store plus useWindowsMaximisedSync lifecycle, complete the topic-summary and worktree-merge owner chains, then split the composition itself: useAppSessionComposition (755) and useAppNavigationComposition (221) own the hook bindings while app-shell/AppRuntimeView (516), ChatPaneRegion, TopicbarActionsStack, DockToggleButton and the footer/chrome/dock/overlay builders own presentation. AppRuntime.tsx is now a 161-line composition root. Hook order, memo deps and render structure are preserved verbatim. Verification: tsc (both configs), eslint, check-app-entry-contract, check-app-layers, test:app-lifecycle and the full discovery runner pass; source-assertion suites were retargeted to the new owners with equivalent behavioral assertions; go run ./tools/repolint is clean with no baseline widening; the six failing discovery suites reproduce identically on the pre-change HEAD (pre-existing).
Problem: the production build failed bundle budgets on every desktop CI leg: initial JavaScript gzip measured 427.3 KiB against the 426.8 budget already at the previous head, and 440.3 KiB after the composition split. Root cause: the previous head was already 0.5 KiB over from toolchain drift; the layering split then added ~13.0 KiB of incompressible object property names from the new owner/region bag interfaces. Sourcemap comparison shows the module set and lazy boundaries are identical to the previous head - no module moved from lazy to eager. Fix: ratchet only the five red budgets to the narrow measured-covering values (initial JS 426.8 -> 440.4, deferred shell CSS 116 -> 116.5, zh 60.6 -> 61.0, zh-TW 61.5 -> 61.9, initial raw 2349.4 -> 2378.7), each with a before/after attribution comment in the budget script. The deferred CSS and locale increases reproduce on the pre-PR head and acknowledge base drift; largest-chunk and render-blocking budgets are unchanged. Verification: pnpm build passes all eight budget assertions; measurements recorded in scripts/check-bundle-budget.mjs comments.
Conflict resolution: - useController.ts: keep this branch's tab-scoped *ForTab ownership variants and thin active-tab wrappers while every body adopts main-v2's shared resolvePromptForTab (turnId + runtimeEpoch pinning, ListTabs refresh on ask/unknown) and handlePromptFailure (expire_prompt vs submit_prompt_failed + replay); the ResolvePlanDecisionTab legacy fallback is removed with the main-v2 contract. The merged reducer takes main-v2's runtimeStatusSnapshotIsStale, a strict superset of this branch's manual epoch/seq fence. - check-bundle-budget.mjs: both attribution comment trails merged; budgets re-measured on the combined graph (kernel-reduced graph + layering bags + main-v2 feature chain): initial JS gzip 441.0 -> 441.1 budget, initial raw 2380.9 -> 2381.0 budget. Verification on the merged tree: pnpm build (all eight budget assertions), test:app-lifecycle, both sides' focused suites (send-failed, goal-activation-tab-routing, session-submission-lifecycle, ask-card-identity, prompt-expiry, runtime-status-freshness, session-recovery-runtime-failure, ask-submit, use-controller-send-fallback, external-opener), repolint clean, and the full discovery runner passes 298/298 - the merge also fixes five of the six pre-existing branch failures via main-v2's exact-prompt/freshness work.
… state Update the PR esengine#9777 checkpoint in both languages: the runbook slices are complete (AppRuntime 161-line composition root, session/navigation composition modules, AppRuntimeView and region builders, repolint 800-line gate with no App exception, goalSubmit.ts deleted with scenarios repointed at owner tests), the main-v2 exact-prompt merge is recorded with its 298/298 discovery result, and bundle budgets carry their measured post-merge values with attribution. Native platform soak, final Go/race/lint/CodeQL evidence, final-head checks and offline heap attribution remain pending.
Problem: the ubuntu desktop CI job failed at test:app-browser with "Executable doesn't exist at desktop/.pw-browsers/chromium_headless_shell-1234" right after the install step had downloaded the browsers to desktop/frontend/.pw-browsers. Root cause: app-browser.mjs and app-memory.mjs (the two benches this PR's CI lines invoke with a relative PLAYWRIGHT_BROWSERS_PATH=.pw-browsers prefix) used a bare ||= fallback, leaving the relative value for Playwright to resolve against the wrong base. Every sibling bench (approval-animation, transcript-*, composer-transcript-stability, theme-surface-contract, run) already normalizes an unset or relative ".pw-browsers" value to the absolute frontendDir path. Fix: adopt the same normalization idiom in both benches. Verification: node --check on both files; app-memory-evidence suite passes. The CI step that installs to desktop/frontend/.pw-browsers and the sibling benches using the normalized path are unchanged.
…App benches
Problem: the previous commit normalized the relative PLAYWRIGHT_BROWSERS_PATH
but the ubuntu desktop CI job still failed with the browsers resolved under
desktop/.pw-browsers instead of desktop/frontend/.pw-browsers.
Root cause: both benches used a static "import { chromium } from
"playwright"", which is hoisted and evaluated before the normalization line
runs; Playwright captures the registry path at module evaluation, so the
relative value was already frozen. approval-animation.mjs and the other
sibling benches import Playwright dynamically after normalizing.
Fix: switch app-browser.mjs and app-memory.mjs to the same dynamic-import
idiom after path normalization.
Verification: node --check on both files; import-order resolution exercised
locally with a relative PLAYWRIGHT_BROWSERS_PATH.
…t drift Problem: the ubuntu desktop CI job ran the full app-memory soak (3 processes, 128/512 rounds, all integrity/release/page-error checks green) but the verdict stayed NEEDS_ATTRIBUTION: real Linux/Chromium data shows listener blips (614 vs the 512 baseline) at phase transitions that fully recover. Root cause: the rewritten classifier compared every checkpoint against the baseline, so a one-sample excursion that is provably freed (the counters return to the exact baseline) blocked the automated gate - stricter than the established soak intent of ignoring isolated recovered blips. Fix: split counter analysis into persistent drift (final checkpoint displaced from baseline, or the post-baseline tail still moving - blocking) and transient excursions (recorded as the non-blocking observation reason "transient-counter-excursion" so they still get an offline explanation). screeningBlockers now excludes only the two observation reasons; persistent drift, persistent cohorts, invalid counters and unreleased operations still block. Docs updated in both languages. Verification: node --test bench/app-memory-evidence.test.mjs (11/11), with new cases mirroring the CI blip shape and the displaced-final-tail blocker.
…new owners Problem: two source-contract assertions in remote-project-tree.test.tsx failed after the App composition split: they read AppRuntime.tsx for code that moved. Root cause: the export transcript selection now lives in useAppSessionComposition (exportItems = remoteSurfaceActive ? remoteSession.transcript.items : state.items) and the todo shelf loop/guard moved to useTodoPanelCommands (items from visibleRuntimeState, dismissal gated on !remote). The behavior contract is unchanged; only the source location and shape changed. Fix: point the assertions at the owning modules, additionally pinning that the composition feeds visibleRuntimeState.items into the todo panel so the visible-transcript projection contract stays explicit. Verification: remote-project-tree suite 25/25 locally; pnpm test:remote green.
Problem: the ubuntu soak verdict flipped between PASS and NEEDS_ATTRIBUTION across identical-code runs: the transient 614-listener sampling blip can land on the final round checkpoint, and with no later checkpoint to prove recovery the persistent-drift rule blocked the gate. Root cause: the tail was measured synchronously after the last navigation round trip, mid-cleanup; whether the blip overlapped the final checkpoint was pure timing luck. The gate contract (final tail at baseline) was right - the bench never guaranteed a resting-state tail measurement. Fix: after the last phase, settle frames, force GC, and append an explicit phase="settled" quiescent confirmation sample. The classifier treats a settled tail as the authoritative resting state (displaced settled tail still blocks; a blip on the round before it is a recorded transient observation). Per-process stdout now includes the attribution reasons so CI logs are self-diagnosing. Verification: node --test bench/app-memory-evidence.test.mjs (12/12), including the round-5 blip-before-settled shape and a stuck settled tail still blocking.
|
Acceptance update — final head Final CI on head
Commits pushed in this session ( Still pending (external to this PR): long-running native-app soak, and whole-app heap offline attribution. |
…d jobs Problem: expanding cold reasoning could overlap the next turn, and stopping jobs from non-active tabs silently returned false. Root cause: visible measurements remained staged indefinitely and absolute children did not resize the observed projection root. Background cancellation also invented a tab key that never matched registered session authority. Fix: observe mounted blocks with generation-fenced kernel frames and publish staged sizes under logical-anchor restoration after native ownership ends. Preserve the native compositor frontier during gestures, discard superseded staged sizes, and reconcile the same keyed hosts in safety mode. Resolve runtime cancellation from canonical committed session resources. Verification: transcript/kernel/ledger and session lifecycle tests; TypeScript; Chromium and WebKit expansion, collapse, held-input and sustained-wheel replays; selection, prepend, safety, session replacement and composer browser gates; full frontend build with existing budgets, repolint and diff checks.
Problem: native sustained reading exposed overlapping estimated blocks and a large release-time reflow. A measured prefix could also paint one frame before its logical-anchor correction. Root cause: the publication frontier accumulated travel for an entire gesture, including distance already consumed by the native viewport. That froze future measurements indefinitely. Prefix layout and correction used separate frames. Fix: retire only physically observed travel while retaining one viewport of compositor runway. Keep unbounded native gestures frozen. Commit a published prefix and its anchor correction through the same before-paint kernel boundary, revoking older queued geometry work instead of adding another scroll writer. Verification: deterministic measurement-ledger and real-hook commit tests; Chromium and WebKit replay the native fixture through 230 wheel steps and lease release with 0px reverse movement and 0px painted overlap, retaining the native 4px gate. Existing reader, expansion/collapse, typecheck and build gates pass. The raw initial bundle measures 2,438,153 bytes, 9 bytes above the former cap; raise only that cap from 2381.0 to 2381.1 KiB. Other budgets are unchanged.
Integrate the reviewed context-overflow recovery changes from esengine#9882 and esengine#9879. Preserve the kernel-reduced startup graph rather than restoring the pre-kernel raw budget. The integrated build measures 2,438,339 raw bytes (2381.190 KiB), 186 bytes above the repaired PR head. Set only the raw ceiling to 2381.2 KiB; all gzip, CSS, locale and largest-chunk gates remain unchanged. Validation: frontend build, test typecheck, context-maintenance notice tests, agent/control/provider/boot Go tests and repolint passed. Transcript measurement and session-command production repairs are unchanged.
Problem: PR #9777 combines viewport state, renderer replacement, settings, and application lifecycle ownership in one large review surface. Root cause: the final renderer depends on generation-aware transactions, immutable measurement snapshots, and logical navigation that need their own executable contract before production integration. Fix: adopt the final pure kernel, timeline projection, measurement ledger, window geometry, history request and navigation models from #9777. Keep production Transcript and its existing writer unchanged. Include deterministic race, native-travel retirement, immutable-prefix and 10,000-turn tests. Verification: production build and bundle budgets, production/test TypeScript, six focused model suites, and repository lint passed. Native rendering is qualified in the separate renderer cutover, not by these model-only tests.
Problem: independent display, reasoning and process-fold preferences produce inconsistent session presentation and stale controls after failed saves. Root cause: frontend mirrors and historical settings can disagree with the backend snapshot, while display changes alter the legacy renderer geometry. Fix: adopt Standard/Deep backend ownership, compatibility mirrors and the snapshot-driven setting control from #9777. Adapt its presentation consumers to the existing renderer and route preference geometry through its current scroll writer. Keep user scrolling and selection authoritative. Include the committed command primitive and its disposal/supersession tests. Verification: configuration and desktop Go tests, production/test TypeScript, production build, settings and consumer regressions, existing Transcript suite and repository lint passed. The measured bundle and legacy-adapter line allowances are documented narrowly in the PR; correctness gates stay unchanged. Full final discovery and external CI qualify the published head.
…nership Problem: independent virtual-row corrections can fight native scrolling and leave cold reasoning expansions overlapping following content. Root cause: row measurements, pending compositor travel, mounted ranges and anchor correction did not share one generation-fenced commit boundary. Fix: adopt the final repaired block projection and viewport kernel from #9777, including cold-block observers, consumed-travel retirement, immutable measurement publication and before-paint anchor restoration. Remove Virtuoso and its competing recovery paths. Keep the existing App callback and session identity wiring. Migrate all three native smoke hosts and their shared contract together with the renderer, and update the development invariants. Verification: production build and tightened bundle budget; production/test TypeScript; Transcript suite; Chromium selection, scroll and composer gates; Chromium/WebKit reader traversal with zero reverse displacement and overlap; desktop Go suite; repository lint. The discovery run passed 267 suites, and its one obsolete Virtuoso source assertion was replaced and rerun successfully. Native external CI must qualify this child head before merge.
|
Superseded by the five separately reviewed and merged slices:
The final split retains the adopted work from this PR's a823eb5 reference, with all subsequent differences classified as reviewed repairs, qualification or current-mainline integration. The independent cancellation prerequisite is #9903. Thank you @SivanCola for the session-experience, transcript and App decomposition work. Closing this original integration PR avoids merging the same work a second time. The work is merged into main-v2. Release publication was outside this delivery; a released build containing all five merges has not been verified here. |
Split delivery tracking
This PR is retained as the repaired integration reference and will not be merged as a bundle. Its changes are being reconstructed as independently buildable, reviewed and CI-gated slices:
Current progress: 3 of 5 slices merged. #9895 passed exact-head CI and native GTK, WKWebView and WebView2 reading, each with zero reverse displacement and final tail distance. Its only root-job retry covered two existing timeouts in unchanged code. The independent backend cancellation prerequisite #9903 is also merged. The remaining ownership and shell slices contain the same shared repairs and await their own complete platform and three-runner memory qualification.
Qualification exposed a pre-existing remote-resume race: an error could become visible before the previous session identity was restored. Slice 4 now owns restoration and error publication as one revision-checked completion, ordered with lifecycle transitions; slice 5 inherits it. A deterministic before/after regression, sibling-path tests, repeated race cases and full desktop tests/race pass. Both child heads are re-running their complete remote gates.
Each child is merged only after its own qualification. The long App memory protocol will retain three independent complete runs and move to an independent workflow with verified same-commit shard aggregation. Original integration CI remains evidence for this source head, not a substitute for child CI. Whole-application heap attribution remains unqualified.
The historical implementation and repair notes below describe the integration reference, not a claim that a child or this parent has merged.
Current review qualification (a823eb5)
Cold reasoning expansion and deferred Markdown reconcile actual block heights without overlapping the next turn. Mounted block observers are generation-fenced. The shared measurement ledger retires compositor travel only after physical viewport progress, keeping one viewport of runway instead of accumulating an entire gesture as measurement debt. Published prefix layout and logical-anchor correction complete in one before-paint Kernel commit, cancelling older queued geometry work. Native gestures still own scrolling; no extra writer, platform branch, or native threshold relaxation was added.
Background runtime cancellation resolves the actual committed target-session identity. Closed/replaced resources remain rejected, and stale completions cannot refresh another session.
Latest-base integration:
3289e2348bceb30cc7bfa588a86f00607ad02af6, including fix(agent,provider): un-stick compaction on thinking-model summaries and unnumbered overflows / 修复思考型模型摘要与无数字超窗导致的分段压缩死锁 #9882 and Recover from summary-request overflow and rescue over-ceiling context / 摘要请求超窗自恢复与硬上限截断救援 #9879. The only conflict was the raw bundle budget; all upstream context-recovery and localized notice changes are retained.a823eb5c2has the same file tree as integration commit8f35cf3b4; its empty CI trigger was needed because GitHub created only metadata checks immediately after the conflict resolution.Retained repaired-head evidence (89b83a5; not substituted for current-head CI):
The review repairs do not change persisted formats or provider-visible prompt/cache bytes; the integrated agent changes belong to the already-merged upstream PRs. Whole-App heap attribution remains unqualified; this is not a leak-free claim.
Summary
react-virtuosoand its restore, compensation, footer handoff, index-translation, and size-tree control paths.TimelineProjection,TranscriptKernel, andTranscriptViewportWriter.@tanstack/react-virtualonly as a range/measurement adapter above that threshold, with active and resident-tail turns kept in ordinary DOM.User-visible behavior
Transcript architecture
The runtime path is now:
scrollTop,scrollHeight, andclientHeightare the geometry source of truth.behavior: "auto"; a static CI gate enforces the single writer.Compatibility and migration
[desktop] session_experience = "standard" | "deep".standard.expandedcalls map todeep; other legacy combinations map tostandard.standard -> display=standard, reasoning=autoanddeep -> display=standard, reasoning=expandedfor rollback compatibility.Historical review closure and acceptance (5da8657)
Three additive review slices, with no history rewrite:
9cc1f4ef7: async source/navigation ownership, terminal transactions and deterministic races.ff559421b: atomic covered window geometry, unified full/window/safety presentation, 40-completed-block budget, resident retirement and committed command lifetimes.5da865779: scope restoration and acceptance/architecture documentation.Source paging survives a cancelled jump, but obsolete generation/request identities cannot position or mutate another session. All geometry uses cancellable Kernel frames and one health/correction entry. Before-paint safety retains keyed native hosts and trusted prefix coordinates, mounts only loaded pages, and locks after two faults until replacement. Stable commands no longer retain historical selection-row contexts.
Current local cumulative verification passed:
pnpm buildwith existing single-writer, TypeScript and bundle budgetspnpm test:transcriptpnpm test:transcript-browser(selection, scroll/safety, composer)pnpm test:transcript-reader-browser(Chromium + WebKit)pnpm test:allgo test ./internal/configand desktopgo test .go run ./tools/repolintandgit diff --checkThe latest 10,000-turn archived/lazy performance run measured projection 67.49ms and immutable prefix/range commit 0.83ms, with 40 completed blocks mounted. Safety replay uses 120 loaded of 1,000 total turns, not fully loaded synthetic bodies; it checks real native selection/focus, reader/tail ≤4px, zero held-gesture writes, full-mode generation locking and ≤1s interactivity. No budget is raised.
Remaining qualification: collected whole-App heap still grows during repeated session switching, including the no-fault control. Heap snapshots showed the fixed Transcript selection resolver count stays at 11 (previously +1,960); remaining retainers include App-shell callback contexts and compiled code. The main-v2 memory baseline is not established. This is explicitly documented, not labeled leak-free or hidden behind a byte threshold. Final-head native/Go/race/lint/CodeQL checks have now passed on this exact SHA; earlier heads were not used as evidence. Historical assessment; current qualification is recorded above.
Full evidence and limits: Transcript acceptance.
Historical CI evidence (5da8657)
Verified head:
5da8657791a8df68f209b0135313f9915fd82659. All applicable checks passed; the unrelated site job was conditionally skipped.Native selection tests executed successfully. Their upload step is failure-only and was skipped on success; this is log/check evidence, not a claim that a successful-run attachment exists. PR checks do not replace the full main-v2 release sweep. App-shell heap attribution was left open in that historical assessment and remains explicitly unqualified above.
Scope and related PRs
410876875to current main-v2. No Provider production redesign; necessary merged model-capability fixture compatibility remains.Release and rollback
Documentation-impact: updated - session experience behavior and the Transcript kernel state machine, invariants, safety mode, and compatibility-removal condition are documented.
Cache-impact: low - standing desktop contributor instructions and config declarations changed, but provider-visible prompts, model-input bytes, memory serialization, tool payloads, and cache keys are unchanged.
Cache-guard: internal/config/session_experience_test.go plus scripts/check-cache-impact.sh; verified the change is limited to desktop presentation/config compatibility and does not alter provider request construction.
System-prompt-review: reviewed by the PR author - desktop/AGENTS.md documents engineering invariants only, and internal/config/config.go adds a desktop presentation preference without changing system prompts.