fix(mac): AppKit timeline P0s — revision adoption, disclosure height, scroll contract, SwiftUI seam - #444
Merged
Merged
Conversation
A selectionOrStatus/environment refresh rebuilt semantic rows from the previous timeline but stamped the presentation snapshot with the whole canonical revision read from the bridge — including timeline lanes it never applied. When one 33ms batch flush carried a metadata publish plus a timeline write, the controller's metadata fetch already saw the newer timeline revision, adopted it, and the buffered timeline mutation was then discarded as a duplicate: the appended row or streamed text never appeared until an unrelated structural rebuild. The refresh now stamps (held timeline lanes, applied metadata/environment lanes), so the buffered mutation applies normally. Also: a tool row's expandability now counts its parsed detail, not just its output — the expanded body shows both. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scroll contract (docs/architecture/appkit-renderer.md): - ScrollAnchor repins on a user scroll that lands within 48pt of the tail (the doc's repin rule; previously only the dock action could repin, so a user who scrolled back down never re-followed the stream). - Scroller-knob drags and assistive scrolls are classified as user scrolling (unattributed clip scrolls in TimelineTrackingScrollView), so they finally unpin instead of being yanked back on the next delta. - scrollToBottom/jumpToTurn account for the bottom content inset; pinned streams no longer sit 10pt short of the actual tail. - The Fenwick height index rebuilds from the same measured-height source the table delegate uses, so anchor restores and turn-rail jumps stop landing off by the accumulated measured-vs-estimated delta. - The live streaming Markdown row is re-measured after every in-place update instead of keeping a byte-count estimate for the whole stream. Disclosure (P0): toggling any tool/reasoning/group/subagent disclosure now invalidates the row's cached height through a controller-installed store callback, re-queries the row height under anchor capture, and re-measures the expanded cell; the height-cache key also carries the disclosure bit so a cached collapsed height can never answer for the expanded state. Previously expanded output was clipped to the collapsed frame forever. Chevrons render only when a row has expandable content. SwiftUI seam: - ChatTimelineHost gains a Coordinator that defers pin/activity callbacks past the current SwiftUI update (mirrors SidebarListHost), removing modify-during-update hazards that dropped writes. - ChatScreen no longer stomps controller-owned pin state and activity on thread switch: returning to a thread keeps its restored viewport and the activity dock stays visible mid-run. - The pinned activity dock's measured height is reserved as bottom content inset so it stops occluding the transcript tail; the material slab behind the composer fades with a keyed animation instead of popping. - updateExternalPinState re-attaches only on a genuine transition, ending a programmatic scroll per body evaluation while pinned. Rows/chrome: the turn rail forwards wheel events to the transcript and hit-tests only its notch buttons (it swallowed scrolling over a 22pt strip); subagent command elapsed labels respect the settled-thread guard so idle threads own no ticking clock; row action failures are routed to the app's error surface instead of a DEBUG-only field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 tasks
15 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The AppKit timeline batch from the 2026-08-02 bug hunt — both P0s plus the scroll-contract and seam defects.
P0 — chat stops updating: a metadata-only presentation refresh (status flip, context-window tick, plan progress) stamped the store with the whole canonical revision including timeline lanes it never applied; the timeline mutation buffered behind it in the same 33 ms flush was then discarded as a duplicate — appended rows/streamed text silently never appeared until an unrelated rebuild. The refresh now claims only the lanes it applied (regression test included).
P0 — clipped disclosures: expanding any tool/reasoning/group/subagent row never invalidated the cached row height, so 40 lines of output rendered inside a 44 pt collapsed frame, permanently (the cache key ignored disclosure, so scrolling away and back replayed the stale height). Toggles now route through a controller-installed store callback that drops the cache entry, re-measures under anchor capture; the cache key carries the disclosure bit. Chevrons render only for rows that actually have expandable content.
Scroll contract (per
docs/architecture/appkit-renderer.md):SwiftUI seam:
ChatTimelineHostgains a Coordinator deferring pin/activity callbacks past the current update (mirrorsSidebarListHost) — removes modify-during-update undefined behavior.Also: subagent command elapsed labels respect the settled-thread guard (no immortal 1 Hz clock on idle threads); row action failures (approve/deny/retry/stop) surface in the app's error UI instead of a DEBUG-only field.
Area
apps/mac— native macOS appapps/windowsapps/mobileapps/serverRelease size
size:XSsize:Ssize:Msize:Lsize:XLVerification
pnpm run verify --allafter merging current main: 5/5 steps pass, exit 0🤖 Generated with Claude Code