Skip to content

fix(mac): transcript visual restoration — reuse corruption, card chrome, hover meta - #462

Merged
SergeSerb2 merged 2 commits into
mainfrom
surgecode/timeline-visual-restoration
Aug 3, 2026
Merged

fix(mac): transcript visual restoration — reuse corruption, card chrome, hover meta#462
SergeSerb2 merged 2 commits into
mainfrom
surgecode/timeline-visual-restoration

Conversation

@SergeSerb2

Copy link
Copy Markdown
Owner

Problem

The AppKit-ported chat log looked broken and cluttered:

  1. Row-reuse corruption (the "jank")clearContent() used removeArrangedSubview, which only unmanages a view; it stays in subviews, drawn at its stale frame. Every reconfigure stranded previous content on screen: stray bars, ghost rows, decision options drawn on top of each other. Separately, recycled NativeWrappingTextFields kept the previous row's preferredMaxLayoutWidth, so reused fields wrapped at ~70px — approvals, checkpoints, and commands rendered one word per line.
  2. Chrome loss from the port — tool/approval/checkpoint/plan/reasoning/decision rows lost their card backing entirely (bare text over the scenery photo), the user bubble lost its hairline stroke, the transcript fade veil under scrolled-away tails was deleted, and hover-revealed timestamps/actions became permanent lines under every message.

Fix

Reuse correctness

  • Strands removed explicitly (removeFromSuperview on recycle, recursive for stacks).
  • Wrap width reset on field configure + deterministic single-line intrinsic before a width is assigned — kills both the narrow-wrap collapse and the stale-height overlap.

Chrome restoration (matching the retired SwiftUI renderer's TranscriptStyle vocabulary)

  • Card backing for non-prose rows: radius 12, state-tinted fill (red 8% failed, accent 8% running/decision, quiet 5% settled), 0.5pt hairline, 2.5pt accent rail on decisions and failures.
  • User bubble regains its accent-tinted hairline stroke.
  • Disclosure headers get the same accent icon tint as plain headers.
  • ChatTranscriptFadeEdge (gradient-masked material) restored under scrolled-away tails.
  • Content column 800 → 840pt (old renderer's width).

Declutter

  • Timestamp + Copy/Edit/Retry/Copy Group fold into one hover-revealed meta line per row; assistant "Responding · time / Copy Response" chrome hover-reveals once settled (stays visible while streaming). Buttons remain enabled — keyboard, VoiceOver, and programmatic activation unaffected; hidden chrome ignores mouse hits via HoverChromeStackView.hitTest so invisible buttons can't swallow clicks.
  • Card-backed rows include their insets in the height estimate, so first paint no longer clips card content until the 50ms height commit.

Verification

  • Full mac suite: 1107 + 296 + 36 tests, all pass (pnpm run test:mac).
  • pnpm run verify --all: 5 steps pass.
  • Default UIProbe sweep: passes with only the three activity-dock mount-check failures that already fail on main; before/after captures show cards complete on first paint, full-width wrapping, no ghost content, hover chrome hidden at rest.

Follow-ups (not this PR)

  • The three pre-existing activity-dock probe mount-check failures on main.
  • Code-block bottom-edge clipping in one mock scene (pre-existing).
  • Estimate calibration for multi-line approvals (first-paint pop is reduced, not eliminated).

🤖 Generated with Claude Code

…ption

Two reuse bugs made the AppKit transcript degrade as rows recycled:
clearContent used removeArrangedSubview, which only stops managing a view —
it stays mounted at its last frame, so every reconfigure stranded the
previous row's content on screen (stray bars, overlapped decision options),
and recycled wrapping text fields kept their previous row's
preferredMaxLayoutWidth, so reused fields re-wrapped at whatever narrow
column they last had (one-word-per-line approvals, checkpoints, commands).
Remove strands explicitly, reset the wrap width on configure, and give
un-assigned fields a deterministic single-line intrinsic to compress from.

Restore the visual chrome the port dropped: tool, approval, checkpoint,
plan, reasoning, decision, usage-limit, and session-exit rows get their
rounded card backing (state-tinted fill, hairline border, accent rail on
decisions/failures) matching the SwiftUI islands; the user bubble regains
its accent hairline; disclosure headers tint like plain headers; the
transcript fade veil returns under scrolled-away tails; and the content
column returns to 840pt.

Permanent timestamp lines and Copy/Edit/Retry button rows collapse into one
hover-revealed meta line per row. Buttons stay enabled — keyboard,
accessibility, and programmatic activation are unaffected — while hidden
chrome ignores mouse hits so an invisible button can't swallow a click.
Card-backed rows carry their extra insets in the height estimate so first
paint no longer clips until the measured height commits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SergeSerb2 SergeSerb2 added the size:M Normal feature or meaningful behavior change label Aug 3, 2026
…ayout, calibrate estimates

The activity-dock probe surfaces (pinned and detached) were registered by the
retired SwiftUI scroll view and died with it — the dock rendered but the
default sweep's three mount checks failed on every run since the port.
Register both surfaces on the ChatScreen overlay that hosts the dock now.

Code blocks measured their height with per-line font math that undershoots
TextKit's typographic line height; measure the laid-out text instead (code
never wraps, so the height is width-independent and bounded by
maximumCodeCharacters). The markdown fragment measure path also still
clamped content to the pre-restoration 800pt column.

Approval, decision, and checkpoint rows render caption/choice/control lines
that never appear in their estimate text; count them so first paint stops
clipping until the measured height commits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SergeSerb2

Copy link
Copy Markdown
Owner Author

Follow-ups from the PR description are now fixed in this branch:

  • Activity-dock probe failures: the three mount checks (activity-dock-not-mounted, tool-promotion-not-mounted, activity-dock-thinking-not-mounted) failed on every sweep since Gate F because the probeSurface registrations lived on the retired SwiftUI scroll view. Both dock surfaces (pinned + detached) now register from the ChatScreen overlay. Default sweep now passes clean — UIProbe: done with zero failures.
  • Code-block measurement: replaced per-line font math (undershoots TextKit's typographic line height) with the laid-out text's actual usage bounds; fragment measure path un-clamped from the stale 800pt. The one remaining "clipped" mock capture turned out to be an honest mid-stream render (message cancelled before the closing fence streamed), not a measure bug.
  • Estimate calibration: approval/decision/checkpoint rows now count their caption, choice, and control lines in the height estimate, so first paint no longer clips card content.

Full mac suite green (1107+296+36); the one preferredQuickChatProvider failure seen in an intermediate run is a pre-existing async-seed flake — passes on rerun with no diff change.

@SergeSerb2
SergeSerb2 merged commit 815a972 into main Aug 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M Normal feature or meaningful behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant