mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 06:59:42 +02:00
Recorder (scripts/dev/record-codex-frames.mjs) captures real codex 0.147 TUI output through the production pipeline (tmux status-off + the codex-mode full strip from session.ts) into JSONL fixtures with keystroke injection points; analyzer replays them through @xterm/headless for the measurements in docs/predictive-echo-plan.md. Composer signature /^> /-style (U+203A), modal and wrapped rows correctly rejected, echo is unstyled default-fg, tmux delivers echo as minimal in-place deltas. Package: types.ts gains optional cursorX/cursorY, getCell, onWriteParsed, onResize (all additive); prediction-renderer.ts renders per-glyph spans keyed by prediction seq; @xterm/headless@^6.0.0 devDep for replay tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
/**
|
|
* Incremental DOM renderer for PredictiveEchoAddon.
|
|
*
|
|
* Unlike overlay-renderer.ts (which paints whole lines with an opaque
|
|
* background out to totalCols), prediction spans cover ONLY the predicted
|
|
* glyph's own cells: anything wider would blank real echo arriving around
|
|
* a prediction. Spans are keyed by prediction seq for O(1) removal.
|
|
*/
|
|
import type { CellDimensions, FontStyle } from './types.js';
|
|
|
|
export interface PredictionSpanParams {
|
|
seq: number;
|
|
/** Viewport-relative row (0-based). */
|
|
row: number;
|
|
/** Column (0-based). */
|
|
col: number;
|
|
char: string;
|
|
/** Cell width of the glyph (1 or 2). */
|
|
width: 1 | 2;
|
|
dims: CellDimensions;
|
|
font: FontStyle;
|
|
underline: boolean;
|
|
}
|
|
|
|
export function addPredictionSpan(
|
|
container: HTMLElement,
|
|
map: Map<number, HTMLSpanElement>,
|
|
p: PredictionSpanParams
|
|
): void {
|
|
const span = document.createElement('span');
|
|
// cellH+1 height: covers the sub-pixel seam between rows (same trick the
|
|
// buffer overlay renderer ships with). Background covers only this glyph's
|
|
// cells, never a full row.
|
|
span.style.cssText =
|
|
`position:absolute;left:${p.col * p.dims.width}px;top:${p.row * p.dims.height}px;` +
|
|
`width:${p.width * p.dims.width}px;height:${p.dims.height + 1}px;line-height:${p.dims.height}px;` +
|
|
`text-align:center;pointer-events:none;` +
|
|
`font-family:${p.font.fontFamily};font-size:${p.font.fontSize};font-weight:${p.font.fontWeight};` +
|
|
(p.font.letterSpacing ? `letter-spacing:${p.font.letterSpacing};` : '') +
|
|
`color:${p.font.color};background-color:${p.font.backgroundColor};` +
|
|
`font-feature-settings:'liga' 0,'calt' 0;` +
|
|
(p.underline ? 'text-decoration:underline;' : '');
|
|
span.textContent = p.char;
|
|
map.set(p.seq, span);
|
|
container.appendChild(span);
|
|
}
|
|
|
|
export function removePredictionSpan(map: Map<number, HTMLSpanElement>, seq: number): void {
|
|
const span = map.get(seq);
|
|
if (span) {
|
|
span.remove();
|
|
map.delete(seq);
|
|
}
|
|
}
|
|
|
|
export function clearAllSpans(map: Map<number, HTMLSpanElement>): void {
|
|
for (const span of map.values()) span.remove();
|
|
map.clear();
|
|
}
|