···11+# `@clayterm/virtualizer` Current-State Specification
22+33+**Status:** Current-state specification for the implementation on `feat/virtualizer-v1`.
44+55+This document was imported from the draft virtualizer spec in `~/Downloads` and updated to match the current implementation in this repository. It is intended to describe the implemented package surface and behavior as it exists in-tree today. Where the implementation is known to be more provisional or implementation-shaped than the original draft contract, that is called out explicitly.
66+77+## 1. Scope
88+99+`@clayterm/virtualizer` is a TypeScript package that provides viewport virtualization over large terminal text output. It owns:
1010+1111+- ring-buffer storage of logical lines
1212+- per-line cached display width
1313+- per-line wrap-point caching at the current column width
1414+- anchor-based scrolling
1515+- viewport resolution
1616+- approximate scrollbar-estimation fields
1717+1818+It does not own:
1919+2020+- ANSI style parsing beyond CSI/OSC skipping for width and wrapping
2121+- rendering, layout, or terminal output
2222+- input normalization
2323+- input parsing
2424+- search, selection, filtering, or disk-backed scrollback
2525+2626+## 2. Package Boundary
2727+2828+The package lives in [`virtualizer/`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer) and exports:
2929+3030+- [`Virtualizer`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/virtualizer.ts)
3131+- [`VirtualizerOptions`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/types.ts)
3232+- [`ViewportEntry`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/types.ts)
3333+- [`ResolvedViewport`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/types.ts)
3434+3535+The package has no runtime import of `@clayterm/clayterm`. Width measurement is injected by the caller.
3636+3737+The intended width provider from the renderer package is `createDisplayWidth()` from root [`width.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/width.ts), which is an async factory returning a synchronous `(text: string) => number` function.
3838+3939+## 3. Public API
4040+4141+The current implemented API is:
4242+4343+```ts
4444+class Virtualizer {
4545+ constructor(options: VirtualizerOptions);
4646+4747+ readonly lineCount: number;
4848+ readonly baseIndex: number;
4949+ readonly columns: number;
5050+ readonly rows: number;
5151+ readonly totalEstimatedVisualRows: number;
5252+ readonly currentEstimatedVisualRow: number;
5353+ readonly isAtBottom: boolean;
5454+ readonly anchorLineIndex: number;
5555+ readonly anchorSubRow: number;
5656+5757+ appendLine(text: string): number;
5858+ resize(columns: number, rows: number): void;
5959+ scrollBy(deltaVisualRows: number): void;
6060+ scrollToFraction(fraction: number): void;
6161+ resolveViewport(): ResolvedViewport;
6262+6363+ getLineDisplayWidth(lineIndex: number): number | undefined;
6464+}
6565+6666+interface VirtualizerOptions {
6767+ measureWidth: (text: string) => number;
6868+ maxLines?: number;
6969+ columns: number;
7070+ rows: number;
7171+}
7272+7373+interface ViewportEntry {
7474+ lineIndex: number;
7575+ text: string;
7676+ wrapPoints: number[];
7777+ totalSubRows: number;
7878+ firstSubRow: number;
7979+ visibleSubRows: number;
8080+}
8181+8282+interface ResolvedViewport {
8383+ entries: ViewportEntry[];
8484+ totalEstimatedVisualRows: number;
8585+ currentEstimatedVisualRow: number;
8686+ isAtBottom: boolean;
8787+}
8888+```
8989+9090+Notes:
9191+9292+- `appendLine()` returns the assigned monotonic `lineIndex`.
9393+- `resolveViewport()` reads `columns` and `rows` from internal state and takes no parameters.
9494+- `getLineDisplayWidth()` is a current implemented API for exposing cached per-line display width. It returns `undefined` for evicted or never-assigned indices.
9595+9696+## 4. Width Model And ANSI Handling
9797+9898+The current implementation expects `measureWidth(text)` to be:
9999+100100+- synchronous
101101+- ANSI-agnostic
102102+- additive across concatenation
103103+- based on per-codepoint `wcwidth` semantics
104104+105105+The virtualizer owns ANSI CSI/OSC skipping internally.
106106+107107+Recognized sequences:
108108+109109+- CSI: `ESC [` ... final byte in `0x40..0x7E`
110110+- OSC: `ESC ]` ... terminated by BEL or `ESC \`
111111+112112+Current behavior:
113113+114114+- recognized CSI/OSC bytes are skipped for both cached display width and wrap computation
115115+- `measureWidth` is called only on visible codepoints
116116+- unrecognized escape families are not skipped
117117+- no ANSI style state is carried between lines
118118+119119+Implementation note:
120120+121121+- unterminated CSI/OSC sequences are currently consumed to the end of the string by the scanner in [`virtualizer/ansi-scanner.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/ansi-scanner.ts)
122122+123123+## 5. Data Model
124124+125125+The virtualizer maintains:
126126+127127+- a ring buffer of `{ text, displayWidth, lineIndex }`
128128+- `baseIndex`
129129+- a monotonic next-line counter
130130+- current `columns` and `rows`
131131+- anchor state: `(anchorLineIndex, anchorSubRow, isAtBottom)`
132132+- `totalEstimatedVisualRows`
133133+- `currentEstimatedVisualRow`
134134+- a wrap cache `Map<lineIndex, wrapPoints>`
135135+136136+Current storage implementation lives in [`virtualizer/ring-buffer.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/ring-buffer.ts).
137137+138138+Current empty-buffer behavior:
139139+140140+- `lineCount === 0`
141141+- `totalEstimatedVisualRows === 0`
142142+- `currentEstimatedVisualRow === 0`
143143+- `isAtBottom === true`
144144+- `resolveViewport().entries` is empty
145145+- `scrollBy()` and `scrollToFraction()` are no-ops
146146+147147+## 6. Operations
148148+149149+### 6.1 `appendLine(text)`
150150+151151+Current implementation flow:
152152+153153+1. Compute `displayWidth` with ANSI skipping.
154154+2. If the buffer is at capacity, evict the oldest line before insertion.
155155+3. Delete the evicted line’s wrap-cache entry.
156156+4. Decrement `totalEstimatedVisualRows` by the evicted line’s estimated rows.
157157+5. Adjust anchor and current estimate if eviction occurred before the anchor.
158158+6. Store the new line with the next monotonic `lineIndex`.
159159+7. Increment `totalEstimatedVisualRows` by `max(1, ceil(displayWidth / columns))`.
160160+8. If `isAtBottom`, advance anchor to the new line at sub-row 0.
161161+162162+### 6.2 `scrollBy(deltaVisualRows)`
163163+164164+Current implementation:
165165+166166+- walks exact sub-rows using cached or freshly computed wrap points
167167+- clamps at top and bottom
168168+- clears `isAtBottom` when scrolling up from bottom
169169+- sets `isAtBottom` when clamped to the last exact sub-row of the last line
170170+- recomputes `currentEstimatedVisualRow` from estimated line prefixes plus the anchor sub-row
171171+172172+### 6.3 `scrollToFraction(fraction)`
173173+174174+Current implementation:
175175+176176+- maps the fraction into an estimated target row
177177+- walks from `baseIndex` using estimated rows per line
178178+- lands on an estimated sub-row within the selected line
179179+- recomputes `currentEstimatedVisualRow` after updating the anchor
180180+181181+### 6.4 `resize(columns, rows)`
182182+183183+Current implementation:
184184+185185+- clears the wrap cache on column change
186186+- recomputes `totalEstimatedVisualRows` by summing `ceil(displayWidth / columns)`
187187+- updates `columns`
188188+- recomputes `currentEstimatedVisualRow`
189189+- updates `rows`
190190+191191+Current implementation detail:
192192+193193+- anchor sub-row clamping is currently based on the estimated row count for the anchor line at the new width, not an exact re-wrap of that line
194194+195195+### 6.5 `resolveViewport()`
196196+197197+Current implementation:
198198+199199+1. Walks forward from the anchor, filling as many rows as possible.
200200+2. If the forward walk does not fill the viewport, it backfills upward:
201201+ - first by expanding the anchor line upward when `anchorSubRow > 0`
202202+ - then by including preceding lines
203203+3. Returns ordered `ViewportEntry` records plus estimation metadata.
204204+205205+Each `ViewportEntry` contains the original `text`, all wrap points for that line, and a visible sub-row window.
206206+207207+## 7. Output Invariants The Implementation Intends To Satisfy
208208+209209+The current test suite checks the following output invariants through public API behavior:
210210+211211+- `entries` ordered by ascending `lineIndex`
212212+- `text` preserved exactly
213213+- `wrapPoints` strictly increasing
214214+- no wrap point inside surrogate pairs
215215+- no wrap point inside recognized CSI/OSC
216216+- `totalSubRows === wrapPoints.length + 1`
217217+- `firstSubRow` and `visibleSubRows` stay within bounds
218218+- total visible sub-rows do not exceed the viewport row budget
219219+- every visible slice fits within `columns`
220220+- output can be reconstructed from `text + wrapPoints`
221221+222222+## 8. Current Implementation Notes And Known Gaps
223223+224224+These notes document the current branch shape rather than defining desired future contract.
225225+226226+- The renderer-side width provider is exposed as `createDisplayWidth(): Promise<(text: string) => number>`, not a directly callable synchronous top-level export.
227227+- `getLineDisplayWidth()` is currently public to support direct verification of cached display width.
228228+- The wrap golden fixture for `abc文d` is currently exercised at `columns = 4` in [`virtualizer/test/wrap-golden.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/wrap-golden.test.ts), reflecting the corrected boundary case used by the branch’s tests rather than the older draft wording from Downloads.
229229+- The implementation and tests should be treated as the source of truth for current behavior where they diverge from the older draft.
230230+231231+## 9. Source Alignment
232232+233233+This file replaces the earlier draft-only location in `~/Downloads` with an in-repo copy under [`specs/`](/Users/tarasmankovski/Repositories/frontside/clayterm/specs). It is intended to track the implemented branch and should be updated together with public API or behavioral changes in [`virtualizer/`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer).
+216
specs/clayterm-virtualizer-test-plan.md
···11+# `@clayterm/virtualizer` Current-State Test Plan
22+33+**Status:** Current-state test plan for the implementation in this repository.
44+55+This document was imported from the draft virtualizer test plan in `~/Downloads` and updated to reflect the test suites, filenames, and assertion strategy that exist on `feat/virtualizer-v1`.
66+77+## 1. Scope
88+99+This plan covers:
1010+1111+- root renderer width-provider tests for `createDisplayWidth()`
1212+- virtualizer conformance-style tests in [`virtualizer/test/`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test)
1313+- golden fixtures
1414+- real-world ANSI fixtures
1515+- property-style randomized tests
1616+- informational benchmark gates
1717+1818+Current suite count:
1919+2020+- root renderer width tests: 7
2121+- virtualizer tests: 113
2222+2323+These counts reflect the current branch contents.
2424+2525+## 2. Test Inventory By File
2626+2727+Root package:
2828+2929+- [`test/width.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/test/width.test.ts): renderer width-provider tests for `createDisplayWidth()`
3030+3131+Virtualizer package:
3232+3333+- [`virtualizer/test/invariants.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/invariants.test.ts)
3434+- [`virtualizer/test/width.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/width.test.ts)
3535+- [`virtualizer/test/ansi.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/ansi.test.ts)
3636+- [`virtualizer/test/append.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/append.test.ts)
3737+- [`virtualizer/test/empty.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/empty.test.ts)
3838+- [`virtualizer/test/viewport.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/viewport.test.ts)
3939+- [`virtualizer/test/wrap-golden.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/wrap-golden.test.ts)
4040+- [`virtualizer/test/ansi-golden.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/ansi-golden.test.ts)
4141+- [`virtualizer/test/real-world-ansi.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/real-world-ansi.test.ts)
4242+- [`virtualizer/test/scroll.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/scroll.test.ts)
4343+- [`virtualizer/test/fraction.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/fraction.test.ts)
4444+- [`virtualizer/test/eviction.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/eviction.test.ts)
4545+- [`virtualizer/test/resize.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/resize.test.ts)
4646+- [`virtualizer/test/exactness.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/exactness.test.ts)
4747+- [`virtualizer/test/property.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/property.test.ts)
4848+- [`virtualizer/test/bench.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/bench.test.ts)
4949+5050+## 3. Assertion Strategy
5151+5252+The current branch uses three broad test styles:
5353+5454+- deterministic conformance-style tests
5555+- reference-behavior tests for exact formula/provider expectations
5656+- informational benchmarks
5757+5858+Current observability surfaces used by tests:
5959+6060+- `appendLine()` return value
6161+- read-only `Virtualizer` state getters
6262+- `resolveViewport()` output
6363+- `getLineDisplayWidth(lineIndex)`
6464+- instrumented `measureWidth` mocks
6565+6666+The addition of `getLineDisplayWidth()` is important for current coverage because several width assertions now verify cached width directly rather than inferring it from row counts.
6767+6868+## 4. Traceability To Current Suites
6969+7070+### 4.1 Renderer width-provider
7171+7272+Covered in [`test/width.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/test/width.test.ts):
7373+7474+- `R.WIDTH.ascii`
7575+- `R.WIDTH.cjk`
7676+- `R.WIDTH.combining`
7777+- `R.WIDTH.zwj-emoji`
7878+- `R.WIDTH.additivity`
7979+- `R.WIDTH.empty-string`
8080+- `R.WIDTH.zero-width`
8181+8282+These validate `createDisplayWidth()` as the current reference provider.
8383+8484+### 4.2 Core identity and append behavior
8585+8686+Covered in:
8787+8888+- [`virtualizer/test/invariants.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/invariants.test.ts)
8989+- [`virtualizer/test/append.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/append.test.ts)
9090+- [`virtualizer/test/empty.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/empty.test.ts)
9191+9292+Current assertions include:
9393+9494+- monotonic line indices
9595+- identity survival across eviction
9696+- identity non-reuse
9797+- empty-buffer semantics
9898+- bottom-follow behavior
9999+- append behavior while scrolled up
100100+- wrap-cache stability across append
101101+102102+### 4.3 Width and ANSI handling
103103+104104+Covered in:
105105+106106+- [`virtualizer/test/width.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/width.test.ts)
107107+- [`virtualizer/test/ansi.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/ansi.test.ts)
108108+- [`virtualizer/test/ansi-golden.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/ansi-golden.test.ts)
109109+- [`virtualizer/test/real-world-ansi.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/real-world-ansi.test.ts)
110110+111111+Current assertions include:
112112+113113+- `measureWidth` never sees recognized ANSI bytes
114114+- cached display width excludes recognized CSI/OSC
115115+- unrecognized escape forms are not skipped
116116+- wrap points never land inside recognized sequences
117117+- no ANSI state leaks across lines
118118+- captured ANSI fixtures behave consistently at multiple widths
119119+120120+### 4.4 Viewport invariants and wrapping fixtures
121121+122122+Covered in:
123123+124124+- [`virtualizer/test/viewport.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/viewport.test.ts)
125125+- [`virtualizer/test/wrap-golden.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/wrap-golden.test.ts)
126126+127127+Current assertions include:
128128+129129+- ordering and text preservation
130130+- wrap-point monotonicity and bounds
131131+- surrogate-pair safety
132132+- visible row budget
133133+- slice width within columns
134134+- self-contained reconstruction
135135+136136+Current fixture note:
137137+138138+- the branch’s `G.WRAP.cjk-boundary` fixture uses `abc文d` at `columns = 4`, matching the corrected test expectation in code
139139+140140+### 4.5 Scroll, fraction, eviction, and resize
141141+142142+Covered in:
143143+144144+- [`virtualizer/test/scroll.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/scroll.test.ts)
145145+- [`virtualizer/test/fraction.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/fraction.test.ts)
146146+- [`virtualizer/test/eviction.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/eviction.test.ts)
147147+- [`virtualizer/test/resize.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/resize.test.ts)
148148+149149+Current assertions include:
150150+151151+- top and bottom clamping
152152+- `isAtBottom` transitions
153153+- fractional jumps to top and bottom
154154+- eviction anchor recovery
155155+- viewport stability when eviction removes older lines
156156+- cache invalidation across resize
157157+- `displayWidth` stability across resize via `getLineDisplayWidth()`
158158+159159+## 5. Property And Benchmark Coverage
160160+161161+Property-style randomized coverage lives in [`virtualizer/test/property.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/property.test.ts):
162162+163163+- identity monotonicity and non-reuse
164164+- eviction stability
165165+- append independence from scroll position
166166+- viewport invariants after random operations
167167+- estimation-field constraints
168168+- resize invariant preservation
169169+170170+Informational benchmark coverage lives in [`virtualizer/test/bench.test.ts`](/Users/tarasmankovski/Repositories/frontside/clayterm/virtualizer/test/bench.test.ts):
171171+172172+- Gate 1: width-provider overhead
173173+- Gate 2: extended ANSI corpus
174174+- Gate 3: estimate vs exact wrap-count match rate
175175+- Gate 4: `scrollToFraction` performance
176176+- Gate 5: skip-optimization comparison
177177+- Gate 6: ANSI-heavy viewport resolution ratio
178178+- Gate 7: resize performance
179179+180180+These are informational measurements, not conformance gates.
181181+182182+## 6. Commands
183183+184184+Run root width-provider tests:
185185+186186+```sh
187187+deno test --allow-read test/width.test.ts
188188+```
189189+190190+Run virtualizer tests:
191191+192192+```sh
193193+cd virtualizer && deno test
194194+```
195195+196196+## 7. Current-State Notes
197197+198198+This plan is intentionally aligned to the branch implementation, not the older Downloads draft verbatim.
199199+200200+Notable updates from the older draft:
201201+202202+- width assertions now use `getLineDisplayWidth()` directly where the branch exposes it
203203+- the renderer width-provider surface is `createDisplayWidth()` rather than a direct synchronous export
204204+- the current suite count is lower than the draft’s projected count because the implementation consolidates multiple properties into fewer files and parameterized tests
205205+- the `wrap-golden` boundary fixture has been corrected to the current checked-in test case
206206+207207+## 8. Maintenance
208208+209209+This file should be updated when:
210210+211211+- the `Virtualizer` public API changes
212212+- test files are renamed or reorganized
213213+- current assertion strategy changes
214214+- additional real-world fixtures or benchmark gates are added
215215+216216+The in-repo `specs/` directory is now the canonical location for these virtualizer docs.