# PR #21641 evidence matrix — terminal cursor blink-off fringe

Before = merge-base e64766c56a (main renderer.ts). After = a1c2f1e5f3 (PR head + main).
Emulation: CDP `Emulation.setDeviceMetricsOverride {width,height,deviceScaleFactor,mobile:false}` with the SAME CSS width/height for every row. Why: it changes only `devicePixelRatio`, so the CSS layout (mount clientHeight -> grid rows, bottom-anchor slack, originY) is identical across DPRs and across before/after; browser zoom changes the CSS viewport, reflows the drawer and moves row origins, so captures stop being comparable. The surface re-fits on DPR change via its `(resolution: Ndppx)` media query; wait 2 rAFs after the override. 2.2 = desktop Zoom-In analogue (2x display at 110%) and is one of the two DPRs the PR's browser test pins.

Fixture setup (all rows): integrated terminal drawer, shell `bash --norc --noprofile` (rc prompts/vi-mode can rewrite DECSCUSR), terminal focused (blink only runs while focused; unfocused = static hollow box), `prefers-reduced-motion: no-preference` (reduce disables blink), output idle (any write forces cursor on). Font: set Settings > Typography terminal font size to **11** (row height round(11*1.35)=15 CSS px). At the default 12 (16 px rows, padding 4) row tops are (4+16k) CSS px = whole device px at 1.25 and 1.5, so main is already clean there unless the view is bottom-anchored with odd slack — the bug would not show. Precheck per DPR: capture a "before" off-frame; it must show non-background pixels in the cursor row, else press Enter to move the cursor one row and retry.

Cursor styles the app can show: no setting exists. Default = blinking block (core option 23). Others only via DECSCUSR from the program: `printf '\e[5 q'` blinking bar, `\e[3 q` blinking underline, `\e[1 q`/`\e[0 q` back to blinking block. Hollow block = unfocused only (does not blink). Blink period: 500 ms on / 500 ms off (CURSOR_BLINK_INTERVAL_MS).
Themes: light/dark from the app `dark` class; cursor rgb(38,56,78) on #fff (light) / rgb(180,203,255) on rgb(14,18,24) (dark) unless --terminal-* vars override. Theme change via MutationObserver -> setTheme -> full repaint.

| id | DPR | emulation | theme | cursor | phase | type | expected before | expected after | notes |
|---|---|---|---|---|---|---|---|---|---|
| R1 | 1 | CDP dsf=1 | dark | block | blink cycles | recording | clean | clean, identical | control |
| R2 | 1.25 | CDP dsf=1.25 | dark | block | blink cycles | recording | faint lines at top/bottom edge of cell while off | no lines | primary repro |
| R3 | 1.5 | CDP dsf=1.5 | dark | block | blink cycles | recording | lines on rows with fractional top (odd rows at 15px) | none | |
| R4 | 2 | CDP dsf=2 | dark | block | blink cycles | recording | clean | clean, identical | control |
| R5 | 2.2 | CDP dsf=2.2 | dark | block | blink cycles | recording | lines (every row fractional) | none | Zoom-In analogue |
| R6 | 1.25 | CDP | light | block | blink cycles | recording | lines (dark fringe on white, most visible) | none | |
| R7 | 2.2 | CDP | light | block | blink cycles | recording | lines | none | |
| R8 | 1.25 | CDP | dark | bar `\e[5 q` | blink cycles | recording | short fringe ticks above/below the 2px bar | none | |
| R9 | 2.2 | CDP | light | bar | blink cycles | recording | ticks | none | |
| R10 | 1.25 | CDP | dark | underline `\e[3 q` | blink cycles | recording | possible bottom-edge fringe | none | optional |
| S1-S5 | 1,1.25,1.5,2,2.2 | CDP | dark | block | off | still | 1,2: clean; others: fringe in cursor cell | all clean; DPR1/2 pixel-identical to before | zoomed cursor-cell crop + whole panel, zero-threshold amplified diff |
| S6-S7 | 1.25, 2.2 | CDP | light | block | off | still | fringe | clean | |
| S8-S9 | 1.25, 2.2 | CDP | dark | bar | off | still | fringe ticks | clean | |
| S10-S11 | 1.25, 2.2 | CDP | dark/light | block | on | still | solid block | solid block, edges may move <=1 device px | not a regression |
| P1 | 1.25 | CDP | dark | block on U+E0B0 | blink cycles | recording | fringe around the inverted triangle cell when off | none | `printf '\ue0b0\e[D'` (cursor sits on separator; renderer draws it geometrically, no font needed) |
| P2 | 2.2 | CDP | dark | n/a (separator erased) | after erase | still | faint triangle-edge lines remain in row | clean | `printf '\ue0b0'; sleep 1; printf '\r\e[K'` |
| U1 | 1 vs 1.25 | CDP | unfocused | hollow box | static | still | box | box (row-snapped height) | unfocused never blinks |

Making phases deterministic: don't screenshot on a timer. In-page, read the terminal `<canvas>` directly with `getImageData` over the cursor cell (+1 device px margin above/below), rAF-poll until it is (a) fully cursor-colored = ON or (b) no cursor-colored pixels = OFF, then grab `toDataURL()` of the canvas in the same task. Recordings: >=6 s (6 cycles) per DPR, before and after, same CSS viewport and font. Non-comparability risks: web font not loaded (await `document.fonts.ready`, metrics are measured once), cursor focus lost to the recorder, shell output after the prompt resetting the phase, different scrollback/anchor slack between builds (use a fresh terminal + `clear`), theme vars differing between builds.
