Files
Lawrence Chen bd6b6e9f2a agent-chat: virtualization scroll anchoring, measured-height disclosure, picker polish (#7650)
* Scroll anchoring for virtualized transcript; picker row gap; smoother disclosure

Re-measurements above the viewport compensate scrollTop in the same
frame (adaptive row estimates shrink initial error); rows suppress
re-measure while their disclosure animates and measure once at the end;
activity rows get a capped-stagger entry animation; model picker rows
gain a 1px seam. Adds resize-anim-lab.html: ten instrumented height
animation approaches for side-by-side feel testing.

* Remove per-row entry fade/translate from disclosure open

The height reveal alone carries the animation; the staggered child
fade felt wrong in dogfood. The lab's entry toggle now defaults off.

* Disclosure animates measured height via WAAPI

Replaces the grid-template-rows track animation after side-by-side
testing in the resize-anim lab (user picked measured height). Open
measures scrollHeight and animates 0->H committing height:auto on
finish; close animates back and unmounts after; a mid-flight toggle
cancels and continues from the current computed height. Remounts of
already-open rows stay instant and reduced motion stays instant.

* Live ghostty re-theme, no composer shadow, menu opt-out flip, disclosure races

The sidecar watches the ghostty config and resolved theme sources,
re-resolves through +show-config, and broadcasts a theme event that
clients apply as CSS variables live (shiki colors are variable-driven
so code re-themes too). The composer card loses its shadow. The plus
menu appends the built-in Agent chat item even with a custom
contextMenu (explicit newWorkspaceMenu:false or explicit inclusion are
the only overrides). Disclosure finish remeasures through an unguarded
event path so cached row heights can't go stale, and interrupt cleanup
pins the mid-flight height before cancel so reopen continues instead of
snapping (fixes the 110px->149px jump seen in CDP).

* Anchor scroll compensation on the first visible index at measure time

virtualFirstVisibleIndex derives the anchor from current scrollTop and
offsets, so compensation covers exactly the rows above the viewport top:
overscan rows above compensate, visible rows do not, and scroll-up
mounts keep compensating against estimates.

* Anchor strictly past exact row-bottom scroll positions

When scrollTop equals a row's cumulative bottom edge that row is fully
above the viewport; anchoring on it exempted its own resizes from
compensation.

* cmux pushes its resolved theme to the agent-chat sidecar

AgentChatThemeSync builds the theme payload from cmux's own resolution
(GhosttyApp runtime colors + full ANSI palette) and POSTs it to the
sidecar's /api/theme when the action runs and on config-reload and
background-change notifications, debounced and gated on recent health.
The sidecar treats the pushed theme as authoritative (last writer
wins), broadcasts it live, and its file fallback now also applies the
managed override from cmux's app-support config with appearance-based
light/dark selection — closing the +show-config divergence. The theme
watcher poll now only stats mtimes and re-arms watchers only when the
source list changes; recurring resolves use async spawn.

* AgentChatThemeSync: import CmuxFoundation for GhosttyBackgroundBlur

* Theme validator treats omitted nullable keys as null

Swift's synthesized Codable drops nil optionals from the JSON; the
exact-key check was 400ing every push from configs with an empty
font-family. Unexpected keys are still rejected.

* Allow opacity 0 in theme payloads (legal ghostty background-opacity)

* Theme sync survives restarts and pushes unconditionally

Observers install at app startup (not only on the create action), each
notification resolves the current agentChat.url from the config store
and POSTs with the short timeout (no health-freshness gate — a refused
loopback connection is cheap), nullable payload fields encode explicit
JSON nulls, and non-2xx responses are logged.

* Theme sync cleanups: clock debounce, os Logger, nonisolated payload; opacity-0 and sparse-payload server test

* Root-anchor the theme endpoint like healthURL

A path in agentChat.url passed health checks but 404'd every theme
POST because themeURL preserved the path prefix while the sidecar
serves /api/theme at the origin root.

* Push the theme once at launch

Observers alone never fire after a relaunch with an unchanged config,
so a running sidecar kept its file-derived theme until the next change;
applicationDidFinishLaunching schedules the debounced push (init-time
start() is too early for resolved config state).

* Move the launch-time theme push into AgentChatThemeSync

The 4-line AppDelegate addition tripped the frozen file's hard-cap
budget; the sync now observes NSApplication.didFinishLaunching itself
(or schedules immediately when already running), which also puts the
behavior in the file that owns it.

* Hue-aware accent selection; tooltips stack above popovers

Accent candidates (ANSI 4,12,6,14,5,13,2,10) are filtered to blue/cyan/
violet hues so themes like Monokai that remap blue to orange fall
through to their cyan; a neutral foreground mix is the last resort and
an explicit accent in the cmux payload overrides. Tooltip positioners
get a dedicated layer above menus/popovers.

* Document agent-chat-accent and live theme updates in the README

* Drop self-removal from the one-shot launch observer

didFinishLaunching posts once per process; the mutable capture tripped
the sendable-closure warning budget.

* cmux theme pushes inherit the file-configured accent override

The Swift payload has no accent field, so every push stored accent:null
and silently disabled the documented agent-chat-accent override
whenever cmux is running (always, since the launch push).
2026-07-08 20:36:27 -07:00
..