D249: the cursor blinks while you can type (compositor layer) #182

Closed
opened 2026-10-12 05:31:49 +00:00 by kayg · 4 comments
Owner

Implements D249 (the cursor blinks while you can type) on cursor-blink, from release-prod-3 (1f77a449).

The live cursor becomes its own layer over the terminal's cursor cell, blinked by a compositor-only Web Animations opacity animation. The Ghostty renderer stays parked at idle (D81, D112); it skips its own cursor draw while the layer is shown. Measurements, test output and captures follow in comments.

Implements D249 (the cursor blinks while you can type) on `cursor-blink`, from `release-prod-3` (1f77a449). The live cursor becomes its own layer over the terminal's cursor cell, blinked by a compositor-only Web Animations opacity animation. The Ghostty renderer stays parked at idle (D81, D112); it skips its own cursor draw while the layer is shown. Measurements, test output and captures follow in comments.
Author
Owner

Implemented on branch cursor-blink. Measurements (Chromium headless, 1440x900, shared host load ~35-55, so absolute numbers are noisy):

  1. Main thread while blinking idle for 29 s (Chromium trace): 0 FunctionCall, 0 TimerFire, 0 FireAnimationFrame, 0 Layout, 0 Paint, 0 renderer passes, ScriptDuration 0. Not literally zero: one style recalc of the cursor element at each blink flip (68 recalcs in 29 s, LayoutCount 0); steady has 0. Both runs show the same ~2 Hz BeginMainFrame from the page itself (59 vs 61).
  2. CPU, 29 s each, no tracing, percent of one core, two rounds: blinking renderer 3.31/2.48, GPU 2.38/1.86; steady renderer 0.93/0.93, GPU 0.41/0.48. Blinking costs about +1.5-2.4 pp renderer and +1.4-2.0 pp GPU here (software compositing in headless inflates GPU). iPhone battery check is for the owner.
  3. Alignment: the spec compares the layer box with the renderer's own cursor pixels (layer off) at the end of an 85-char line and after wrapping, DPR 1 and 2: pass.
    The server idle canary (scripts/test-idle-canary.py) measures the host daemon, untouched by this change.

Tests: cursor-blink.spec 6/6; font-metrics, cell-metrics, terminal-cell-position pass (some font-metrics runs hit 'socket hang up' on the first /__mock/reset and pass on rerun); bun run check, bun test src (150 pass), build + transfer-budget exit 0, no budget changed. Under current host load these fail identically on unmodified release-prod-3: predictive-echo 'production viewports', terminal-theme 'every curated theme', terminal 'first run' and 'access screens', predictive-backspace budget tests.
Captures: apps/tracer/captures/cursor-blink/ (blink-on/off, unfocused-underline, unfocused-steady-bar, long-line-wrapped; 1440x900 and 393x852, ink and paper).

Implemented on branch cursor-blink. Measurements (Chromium headless, 1440x900, shared host load ~35-55, so absolute numbers are noisy): 1. Main thread while blinking idle for 29 s (Chromium trace): 0 FunctionCall, 0 TimerFire, 0 FireAnimationFrame, 0 Layout, 0 Paint, 0 renderer passes, ScriptDuration 0. Not literally zero: one style recalc of the cursor element at each blink flip (68 recalcs in 29 s, LayoutCount 0); steady has 0. Both runs show the same ~2 Hz BeginMainFrame from the page itself (59 vs 61). 2. CPU, 29 s each, no tracing, percent of one core, two rounds: blinking renderer 3.31/2.48, GPU 2.38/1.86; steady renderer 0.93/0.93, GPU 0.41/0.48. Blinking costs about +1.5-2.4 pp renderer and +1.4-2.0 pp GPU here (software compositing in headless inflates GPU). iPhone battery check is for the owner. 3. Alignment: the spec compares the layer box with the renderer's own cursor pixels (layer off) at the end of an 85-char line and after wrapping, DPR 1 and 2: pass. The server idle canary (scripts/test-idle-canary.py) measures the host daemon, untouched by this change. Tests: cursor-blink.spec 6/6; font-metrics, cell-metrics, terminal-cell-position pass (some font-metrics runs hit 'socket hang up' on the first /__mock/reset and pass on rerun); bun run check, bun test src (150 pass), build + transfer-budget exit 0, no budget changed. Under current host load these fail identically on unmodified release-prod-3: predictive-echo 'production viewports', terminal-theme 'every curated theme', terminal 'first run' and 'access screens', predictive-backspace budget tests. Captures: apps/tracer/captures/cursor-blink/ (blink-on/off, unfocused-underline, unfocused-steady-bar, long-line-wrapped; 1440x900 and 393x852, ink and paper).
Author
Owner

Implemented on branch cursor-blink. Measurements (Chromium headless, 1440x900, shared host load ~35-55, so absolute numbers are noisy):

  1. Main thread while blinking idle for 29 s (Chromium trace): 0 FunctionCall, 0 TimerFire, 0 FireAnimationFrame, 0 Layout, 0 Paint, 0 renderer passes, ScriptDuration 0. Not literally zero: one style recalc of the cursor element at each blink flip (68 recalcs in 29 s, LayoutCount 0); steady has 0. Both runs show the same ~2 Hz BeginMainFrame from the page itself (59 vs 61).
  2. CPU, 29 s each, no tracing, percent of one core, two rounds: blinking renderer 3.31/2.48, GPU 2.38/1.86; steady renderer 0.93/0.93, GPU 0.41/0.48. Blinking costs about +1.5-2.4 pp renderer and +1.4-2.0 pp GPU here (software compositing in headless inflates GPU). iPhone battery check is for the owner.
  3. Alignment: the spec compares the layer box with the renderer's own cursor pixels (layer off) at the end of an 85-char line and after wrapping, DPR 1 and 2: pass.
    The server idle canary (scripts/test-idle-canary.py) measures the host daemon, untouched by this change.

Tests: cursor-blink.spec 6/6; font-metrics, cell-metrics, terminal-cell-position pass (some font-metrics runs hit 'socket hang up' on the first /__mock/reset and pass on rerun); bun run check, bun test src (150 pass), build + transfer-budget exit 0, no budget changed. Under current host load these fail identically on unmodified release-prod-3: predictive-echo 'production viewports', terminal-theme 'every curated theme', terminal 'first run' and 'access screens', predictive-backspace budget tests.
Captures: apps/tracer/captures/cursor-blink/ (blink-on/off, unfocused-underline, unfocused-steady-bar, long-line-wrapped; 1440x900 and 393x852, ink and paper).

Implemented on branch cursor-blink. Measurements (Chromium headless, 1440x900, shared host load ~35-55, so absolute numbers are noisy): 1. Main thread while blinking idle for 29 s (Chromium trace): 0 FunctionCall, 0 TimerFire, 0 FireAnimationFrame, 0 Layout, 0 Paint, 0 renderer passes, ScriptDuration 0. Not literally zero: one style recalc of the cursor element at each blink flip (68 recalcs in 29 s, LayoutCount 0); steady has 0. Both runs show the same ~2 Hz BeginMainFrame from the page itself (59 vs 61). 2. CPU, 29 s each, no tracing, percent of one core, two rounds: blinking renderer 3.31/2.48, GPU 2.38/1.86; steady renderer 0.93/0.93, GPU 0.41/0.48. Blinking costs about +1.5-2.4 pp renderer and +1.4-2.0 pp GPU here (software compositing in headless inflates GPU). iPhone battery check is for the owner. 3. Alignment: the spec compares the layer box with the renderer's own cursor pixels (layer off) at the end of an 85-char line and after wrapping, DPR 1 and 2: pass. The server idle canary (scripts/test-idle-canary.py) measures the host daemon, untouched by this change. Tests: cursor-blink.spec 6/6; font-metrics, cell-metrics, terminal-cell-position pass (some font-metrics runs hit 'socket hang up' on the first /__mock/reset and pass on rerun); bun run check, bun test src (150 pass), build + transfer-budget exit 0, no budget changed. Under current host load these fail identically on unmodified release-prod-3: predictive-echo 'production viewports', terminal-theme 'every curated theme', terminal 'first run' and 'access screens', predictive-backspace budget tests. Captures: apps/tracer/captures/cursor-blink/ (blink-on/off, unfocused-underline, unfocused-steady-bar, long-line-wrapped; 1440x900 and 393x852, ink and paper).
Author
Owner

Orchestrator: DEPLOYED to production as a web-only change (no server restart; ActiveEnterTimestamp unchanged 03:17:48 UTC, CA stays unlocked) at release-prod-3w 4855e033 = 1f77a449 + signout-clear (D245) + phone-width (TASTE full width / edge to edge, lone window full width) + cursor-blink (D249). Gates on the merged head: tsc clean, bun test src 154 pass, transfer budget pass, sign-out + phone-window-width + phone-polish + cursor-blink specs 34/35 (DPR-2 alignment timed out at 29.2 s under load 61, passed on rerun in 37 s). Owner to test on iPhone.

Orchestrator: DEPLOYED to production as a web-only change (no server restart; ActiveEnterTimestamp unchanged 03:17:48 UTC, CA stays unlocked) at release-prod-3w 4855e033 = 1f77a449 + signout-clear (D245) + phone-width (TASTE full width / edge to edge, lone window full width) + cursor-blink (D249). Gates on the merged head: tsc clean, bun test src 154 pass, transfer budget pass, sign-out + phone-window-width + phone-polish + cursor-blink specs 34/35 (DPR-2 alignment timed out at 29.2 s under load 61, passed on rerun in 37 s). Owner to test on iPhone.
Author
Owner

Closed by the orchestrator during the 2026-10-28 handover (#254), on the owner's request to close finished issues. Evidence: Cursor-blink (D249) deployed in 4855e033, which is in dev and release-prod-live. Reopen if anything is still missing.

Closed by the orchestrator during the 2026-10-28 handover (#254), on the owner's request to close finished issues. Evidence: Cursor-blink (D249) deployed in 4855e033, which is in dev and release-prod-live. Reopen if anything is still missing.
kayg closed this issue 2026-10-28 17:53:05 +00:00
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#182
No description provided.