Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
61c391d
Fix starship rendering regressions and add PTY debug runbook
RtlZeroMemory Feb 27, 2026
e029210
Fix starship template theme to use packed rgb colors
RtlZeroMemory Feb 27, 2026
56868eb
Address PR feedback and fix lint/native diff telemetry audit
RtlZeroMemory Feb 27, 2026
6cf158f
Fix native vendor integrity check for uninitialized submodules
RtlZeroMemory Feb 27, 2026
9cebb4f
Fix CI color-style tests and native payload short-read audit
RtlZeroMemory Feb 27, 2026
9f73007
fix(ink-compat): optimize translation and layout hot paths
RtlZeroMemory Feb 27, 2026
b44f973
fix(ink-compat): handle packed rgb and stabilize review regressions
RtlZeroMemory Feb 27, 2026
fe4533e
chore(lint): resolve biome violations in ci files
RtlZeroMemory Feb 27, 2026
8257f3d
fix(core): satisfy strict index-signature access in hashTextProps
RtlZeroMemory Feb 27, 2026
2e4c8a4
docs(changelog): add missing merged PR entries through #227
RtlZeroMemory Feb 27, 2026
13d93b5
feat(bench): add ink-compat benchmark harness
RtlZeroMemory Feb 27, 2026
045b95d
feat(ink-compat): add bench phase hook
RtlZeroMemory Feb 27, 2026
a0fcb87
fix(ink-compat): match Ink soft-wrap whitespace
RtlZeroMemory Feb 27, 2026
ebe4477
merge(main): merge origin/main
RtlZeroMemory Feb 27, 2026
4beed4f
bench: improve determinism and resize handling
RtlZeroMemory Feb 27, 2026
d235c13
feat(bench): add validity doc and reporting
RtlZeroMemory Feb 27, 2026
b698f20
perf(ink-compat): coalesce renders and reduce churn
RtlZeroMemory Feb 27, 2026
1b3e608
chore(bench): add active cpuprofile hotspot evidence
RtlZeroMemory Feb 27, 2026
9a9c5ea
perf(ink-compat): reduce runtime renderer overhead
RtlZeroMemory Feb 27, 2026
529b512
perf(ink-compat): reduce dashboard-grid tail latency
RtlZeroMemory Feb 27, 2026
709ee5a
refactor(core): reduce hot-path allocation churn
RtlZeroMemory Feb 27, 2026
e248a55
chore(ink-compat): safety checkpoint before perf refactors
RtlZeroMemory Feb 27, 2026
6e21c72
Merge origin/main into perf/hotpath-cache-pooling-head
RtlZeroMemory Feb 27, 2026
e3f5d67
fix(layout): guard forced dimension cache keys
RtlZeroMemory Feb 27, 2026
1068458
perf(ink-compat): apply claude optimization batch
RtlZeroMemory Feb 28, 2026
470ca52
Merge branch 'pr-228' into feat/ink-compat-bench-harness
RtlZeroMemory Feb 28, 2026
3ff9494
chore(bench): safety checkpoint before long-run harness refactor
RtlZeroMemory Feb 28, 2026
7f43413
fix(ink-compat): harden ansi transform rendering and bench wiring
RtlZeroMemory Feb 28, 2026
3d17344
docs(ink-compat): overhaul migration and discovery docs
RtlZeroMemory Feb 28, 2026
5ee0c54
fix(pr): address review feedback on harness and ink-compat
RtlZeroMemory Feb 28, 2026
a820802
docs(ink-compat): align debug links and parity verification note
RtlZeroMemory Feb 28, 2026
ff58d10
fix(lint): resolve ci biome diagnostics
RtlZeroMemory Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,8 @@ CODEX_*.md
.last_perf_pack_quick
*.cpuprofile
packages/bench/bubbletea-bench/.bin/

# Ink compat benchmark artifacts (runner output)
results/ink-bench_*/
results/verify_*/
results/verify_*.json
212 changes: 212 additions & 0 deletions BENCHMARK_VALIDITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Ink vs Ink-Compat Benchmark Validity (FAIR)

This benchmark suite compares:

- **`real-ink`**: `@jrichman/ink`
- **`ink-compat`**: `@rezi-ui/ink-compat` (Rezi-backed Ink replacement)

The **exact same benchmark app code** runs in both modes (`packages/bench-app`). The only difference is **module resolution for `ink`** at runtime (a symlink in `packages/bench-app/node_modules/ink`).

If equivalence fails, the benchmark is **invalid** until fixed.

## Renderer Selection (Same App Code)

`bench-app` imports from `ink` normally:

- `import { render, Box, Text } from "ink"`

`bench-runner` switches renderers by linking:

- `real-ink`: `packages/bench-app/node_modules/ink -> node_modules/@jrichman/ink`
- `ink-compat`: `packages/bench-app/node_modules/ink -> packages/ink-compat`

React is resolved from the workspace root, keeping a singleton React version across both runs.

## Determinism & Terminal Fairness

### PTY + fixed terminal

All runs execute inside a PTY (`node-pty`) with fixed dimensions:

- `BENCH_COLS` (default `80`)
- `BENCH_ROWS` (default `24`)

The harness forces a consistent terminal identity:

- `TERM=xterm-256color`
- `COLUMNS`, `LINES` set to `BENCH_COLS/BENCH_ROWS`
- `FORCE_COLOR=1`

### Rendering mode

The benchmark app uses consistent Ink render options for both renderers:

- `alternateBuffer: false`
- `incrementalRendering: true`
- `maxFps: BENCH_MAX_FPS` (default `60`)
- `patchConsole: false`

### Offline, scripted inputs

Scenarios are driven via a control socket (JSON lines) plus optional scripted keypresses/resizes:

- streaming tokens / ticks are deterministic
- large-list-scroll uses scripted `↓` inputs
- no network access

## Output Equivalence (Correctness Gate)

For every verify run:

1. The harness captures **raw PTY output bytes** (`pty-output.bin`).
2. Output is applied to a headless terminal (`@xterm/headless`) to reconstruct the **final screen buffer** (`screen-final.txt`).
3. `npm run verify` compares `real-ink` vs `ink-compat` final screens.

Rules:

- If **final screen differs**, the scenario comparison is **invalid**.
- If intermediate frames differ but the final screen matches, we allow it and report only final equivalence (intermediate equivalence is currently **UNPROVEN**).

Known limitation:

- `resize-storm` currently fails final-screen equivalence and is excluded from valid comparisons.

## Settle Detection (Time To Stable)

The harness tracks a rolling screen hash and marks the run **stable** when:

- screen hash is unchanged for `stableWindowMs` (default `250ms`)

Reported:

- `timeToStableMs`: time from start until the first moment stability is satisfied
- `meanWallS`: end-to-end wall time to settle (`timeToStableMs/1000`, falling back to total duration if stability isn’t reached)

## Meaningful Paint Signal

The benchmark app always renders a deterministic marker:

- `BENCH_READY ...`

The harness reports:

- `timeToFirstMeaningfulPaintMs`: first time any screen line contains `BENCH_READY`

## Metrics: What’s Measured (Per Frame)

Per-frame metrics are written as JSONL:

- `packages/bench-app/dist/entry.js` writes `frames.jsonl` on exit.

Each frame corresponds to one `onRender()` callback invocation.

### Shared metrics

- `renderTimeMs`
- from renderer `onRender({renderTime})`
- **excludes** any time spent waiting for throttling/scheduling
- `layoutTimeMs`
- **real-ink only**: Yoga layout wall time measured via preload instrumentation (see below)
- `renderTotalMs`
- `renderTimeMs + (layoutTimeMs ?? 0)`
- primary “render CPU work” accumulator for comparisons (still see UNPROVEN caveats)
- `scheduleWaitMs`
- time from “first update requested” until “frame start”
- reported separately; **excluded** from `renderTotalMs`
- `stdoutWriteMs`, `stdoutBytes`, `stdoutWrites`
- measured by wrapping `process.stdout.write()` inside the app
- **caveat**: this measures JS time spent in `write()` calls, not kernel flush completion
- `updatesRequestedDelta`
- number of app updates requested since prior frame
- used to compute coalescing stats (`updatesRequested`, `updatesPerFrameMean`, etc.)

### Ink-compat-only phase breakdown (when enabled)

When `BENCH_INK_COMPAT_PHASES=1`, `ink-compat` emits phase timings into the app’s frame record:

- `translationMs`
- `percentResolveMs`
- `coreRenderMs`
- `assignLayoutsMs`
- `rectScanMs`
- `ansiMs`
- plus node/op counts

High-cardinality counters (translation cache hits/misses, etc.) are gated by:

- `BENCH_DETAIL=1`

## Metrics: What’s Measured (Per Run)

Per-run summaries are written to `run-summary.json` and `batch-summary.json`:

Primary KPIs:

- `meanWallS`
- `totalCpuTimeS`
- derived from `/proc/<pid>/stat` sampling (user+system ticks), converted using `getconf CLK_TCK`
- `meanRenderTotalMs` (sum of per-frame `renderTotalMs`)
- `timeToFirstMeaningfulPaintMs`
- `timeToStableMs`

Secondary KPIs:

- render latency distribution: `renderTotalP50Ms`, `renderTotalP95Ms`, `renderTotalP99Ms`, `renderTotalMaxMs`
- scheduling distribution: `scheduleWaitP50Ms`, `scheduleWaitP95Ms`, `scheduleWaitP99Ms`, `scheduleWaitMaxMs`
- coalescing stats: `updatesRequested`, `updatesPerFrameMean`, `framesWithCoalescedUpdates`, `maxUpdatesInFrame`
- I/O stats: `writes`, `bytes`, `renderMsPerKB`
- memory: `peakRssBytes` (from `/proc` samples)

## What `renderTime` Includes / Excludes (Renderer-Specific)

### `real-ink` (`@jrichman/ink`)

`renderTimeMs` comes from Ink’s `onRender` callback.

- **Includes**: Ink’s JS-side render pipeline inside `Ink.onRender()` (output generation + stdout writes).
- **Excludes**: Yoga layout time, because Yoga layout runs via `rootNode.onComputeLayout()` during React commit (`resetAfterCommit`).

To make comparisons fair, we instrument Yoga layout:

- A preload script patches each Ink instance’s `rootNode.onComputeLayout` to time Yoga layout and attaches `layoutTimeMs` to the `onRender` metrics.
- The benchmark uses `renderTotalMs = renderTimeMs + layoutTimeMs`.

### `ink-compat` (`@rezi-ui/ink-compat`)

`renderTimeMs` is measured around `renderFrame()`:

- **Includes** (when phases enabled): translation, percent resolve, Rezi core render/layout, ANSI serialization, stdout write.
- **Excludes**: time spent waiting on throttle / scheduling.

## UNPROVEN / Known Gaps (and how to prove)

### React reconcile/commit time breakdown

We do **not** currently provide a proven, apples-to-apples split of:

- React reconcile time
- React commit time

for both renderers.

Minimum instrumentation to prove:

- Add an optional preload (both modes) that wraps `react-reconciler` scheduler entrypoints (e.g. `performWorkOnRootViaSchedulerTask`) and accumulates commit/reconcile durations per frame.
- Alternatively, instrument renderer-specific “commit complete” hooks and wall-clock around them, with care to exclude throttle waits.

### Intermediate frame equivalence

We only gate on **final screen** equivalence.

Minimum instrumentation to prove:

- During verify, snapshot and hash the reconstructed screen buffer on every frame (or every N ms) and diff sequences.

### Stdout write latency

`stdoutWriteMs` is JS time inside `write()`. It does not include terminal emulator processing time.

Minimum instrumentation to prove:

- backpressure-aware measurements (bytes accepted vs drained), plus optional `strace`/`perf` outside this suite.

51 changes: 51 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,57 @@ The format is based on Keep a Changelog and the project follows Semantic Version

## [Unreleased]

## [0.1.0-alpha.48] - 2026-02-27

### Features

- **ink-compat**: Improved Ink compatibility fidelity, diagnostics, and documentation coverage.
- **drawlist/backend**: Added builder `buildInto(dst)` and backend zero-copy `beginFrame` SAB path.
- **renderer/perf**: Shipped packed-style pipeline, frame text arena, retained sub-display-lists, BLIT_RECT plumbing, and logs scrolling optimizations.
- **runtime/perf**: Added layout stability signatures and content-keyed render packets with additional hot-path optimizations.

### Bug Fixes

- **release/publish**: Fixed npm publish flow for `@rezi-ui/ink-compat` and shim packages.
- **native**: Fixed MSVC `ZR_ARRAYLEN` compatibility and bumped vendored Zireael revisions.
- **node/backend**: Prevented reclaiming READY SAB slots during `beginFrame`.
- **starship/template**: Fixed rendering regressions and added PTY debugging runbook coverage.
- **ink-compat**: Fixed translation/layout hot paths and regression fallout from the optimization pass.

### Developer Experience

- **docs/dev**: Added code-standards enforcement references.
- **ci**: Optimized PR pipeline concurrency and fast-gate behavior.
- **renderer/refactor**: Replaced WeakMap theme propagation with stack-based propagation.
- **release**: Added release prep updates leading into alpha.40+ publishing flow.

### Merged Pull Requests

- [#201](https://github.com/RtlZeroMemory/Rezi/pull/201) docs(dev): add Rezi code standards and enforcement references
- [#202](https://github.com/RtlZeroMemory/Rezi/pull/202) chore(release): bump Zireael vendor and prepare alpha.40
- [#203](https://github.com/RtlZeroMemory/Rezi/pull/203) feat(ink-compat): improve fidelity, diagnostics, and docs
- [#204](https://github.com/RtlZeroMemory/Rezi/pull/204) fix(release): publish ink-compat and shim packages
- [#205](https://github.com/RtlZeroMemory/Rezi/pull/205) fix(native): make ZR_ARRAYLEN MSVC-compatible
- [#206](https://github.com/RtlZeroMemory/Rezi/pull/206) fix(release): publish ink-compat by path
- [#207](https://github.com/RtlZeroMemory/Rezi/pull/207) docs: add comprehensive Ink-compat guide and README feature callout
- [#208](https://github.com/RtlZeroMemory/Rezi/pull/208) chore(release): publish scoped ink shim packages
- [#210](https://github.com/RtlZeroMemory/Rezi/pull/210) fix(native): bump Zireael vendor to v1.3.9
- [#211](https://github.com/RtlZeroMemory/Rezi/pull/211) refactor(renderer): replace WeakMap theme propagation with stack
- [#212](https://github.com/RtlZeroMemory/Rezi/pull/212) feat(core): add drawlist builder buildInto(dst) for v2/v3
- [#213](https://github.com/RtlZeroMemory/Rezi/pull/213) feat: add backend beginFrame zero-copy SAB frame path
- [#214](https://github.com/RtlZeroMemory/Rezi/pull/214) fix(node): do not reclaim READY SAB slots in beginFrame
- [#215](https://github.com/RtlZeroMemory/Rezi/pull/215) ci: optimize PR pipeline — concurrency, fast gate, reduced matrix
- [#216](https://github.com/RtlZeroMemory/Rezi/pull/216) drawlist: make v1 the only protocol and persistent builder
- [#217](https://github.com/RtlZeroMemory/Rezi/pull/217) EPIC 6: packed style pipeline + Zireael vendor bump
- [#218](https://github.com/RtlZeroMemory/Rezi/pull/218) EPIC 8: frame text arena + slice-referenced text ops
- [#219](https://github.com/RtlZeroMemory/Rezi/pull/219) chore(native): bump vendored Zireael to v1.3.11
- [#220](https://github.com/RtlZeroMemory/Rezi/pull/220) EPIC 7: retained sub-display-lists via per-instance render packets
- [#221](https://github.com/RtlZeroMemory/Rezi/pull/221) EPIC 9B: plumb BLIT_RECT and optimize logs scroll rendering
- [#223](https://github.com/RtlZeroMemory/Rezi/pull/223) Fix post-refactor regressions and bump native vendor to Zireael #103
- [#225](https://github.com/RtlZeroMemory/Rezi/pull/225) Fix starship rendering regressions with clean diff and PTY debug runbook
- [#226](https://github.com/RtlZeroMemory/Rezi/pull/226) perf: layout stability signatures, content-keyed packets, hot-path fixes
- [#227](https://github.com/RtlZeroMemory/Rezi/pull/227) fix(ink-compat): optimize translation and layout hot paths

## [0.1.0-alpha.40] - 2026-02-25

### Bug Fixes
Expand Down
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Rezi is a high-performance terminal UI framework for TypeScript. You write decla
- **Inline image rendering** — display PNG, JPEG, and raw RGBA buffers using Kitty, Sixel, or iTerm2 graphics protocols, with automatic blitter fallback
- **Terminal auto-detection** — identifies Kitty, WezTerm, iTerm2, Ghostty, Windows Terminal, and tmux; enables the best graphics protocol automatically, with env-var overrides for any capability
- **Performance-focused architecture** — binary drawlists + native C framebuffer diffing; benchmark details and caveats are documented in the Benchmarks section
- **Ink compatibility layer** — run existing Ink CLIs on Rezi with minimal changes (import swap or dependency aliasing), plus deterministic parity diagnostics; details: [Ink Compat docs](docs/architecture/ink-compat.md)
- **Ink compatibility layer** — run existing Ink CLIs on Rezi with minimal changes (import swap or dependency aliasing), plus deterministic parity diagnostics; details: [Porting guide](docs/migration/ink-to-ink-compat.md) · [Ink Compat architecture](docs/architecture/ink-compat.md)
- **JSX without React** — optional `@rezi-ui/jsx` maps JSX directly to Rezi VNodes with zero React runtime overhead
- **Deterministic rendering** — same state + same events = same frames; versioned binary protocol, pinned Unicode tables
- **Hot state-preserving reload** — swap widget views or route tables in-process during development without losing app state or focus context
Expand Down Expand Up @@ -115,6 +115,44 @@ Numbers are from a single-replicate PTY-mode run on WSL. They are directional, n

---

## Ink-Compat Bench (Ink vs Ink-Compat)

This repo includes a fairness-focused benchmark + profiling suite that runs the **same TUI app code** against:

- `real-ink`: `@jrichman/ink`
- `ink-compat`: `@rezi-ui/ink-compat`

Key commands:

```bash
# build bench packages
npm run prebench

# (optional) set up module resolution for bench-app explicitly
npm run prepare:real-ink
npm run prepare:ink-compat

# run a scenario (3 replicates)
npm run -s bench -- --scenario streaming-chat --renderer real-ink --runs 3 --out results/
npm run -s bench -- --scenario streaming-chat --renderer ink-compat --runs 3 --out results/

# CPU profiling (writes .cpuprofile under results/.../run_XX/cpu-prof/)
npm run -s bench -- --scenario dashboard-grid --renderer ink-compat --runs 1 --cpu-prof --out results/

# final-screen equivalence gate
npm run -s verify -- --scenario streaming-chat --compare real-ink,ink-compat --out results/
```

Docs + reports:

- Methodology + metric definitions: `BENCHMARK_VALIDITY.md`
- Latest report: `results/report_2026-02-27.md`
- Bottlenecks + fixes: `results/bottlenecks.md`
- Porting and architecture docs:
- `docs/migration/ink-to-ink-compat.md`
- `docs/architecture/ink-compat.md`
- `docs/dev/ink-compat-debugging.md`

## Quick Start

Get running in under a minute:
Expand Down
22 changes: 22 additions & 0 deletions docs/architecture/ink-compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

It is designed for practical compatibility: keep React + Ink component/hook semantics, but replace Ink's renderer backend with Rezi's deterministic layout and draw pipeline.

If you are actively migrating an app, start with [Ink to Ink-Compat Migration](../migration/ink-to-ink-compat.md) and use this page as the runtime/internals reference.

## What this gives you

- Reuse existing Ink app code with minimal migration.
Expand Down Expand Up @@ -81,6 +83,26 @@ You can also import shim implementations from `@rezi-ui/ink-compat` directly:
- `@rezi-ui/ink-compat/shims/ink-gradient`
- `@rezi-ui/ink-compat/shims/ink-spinner`

## Wiring verification (recommended in CI)

To ensure you are not silently running real Ink:

1. Verify resolved package identity:

```bash
node -e "const p=require('ink/package.json'); if(p.name!=='@rezi-ui/ink-compat') throw new Error('ink resolves to '+p.name); console.log('ink-compat active:', p.version);"
```

2. Verify resolved module path:

```bash
node -e "const fs=require('node:fs'); const path=require('node:path'); const pkg=require.resolve('ink/package.json'); console.log(fs.realpathSync(path.dirname(pkg)));"
```

3. For bundled CLIs, rebuild the bundle after aliasing and validate expected compat-only markers in generated output.

4. For rendering/layout/theme parity checks, run a live PTY with `REZI_FRAME_AUDIT=1` and generate evidence with `node scripts/frame-audit-report.mjs`.

## Public compatibility surface

### Components
Expand Down
5 changes: 4 additions & 1 deletion docs/getting-started/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Rezi is designed specifically for terminal UIs, not as a React port.

### I'm using Ink. How do I migrate?

Use the [Ink to Rezi migration guide](../migration/ink-to-rezi.md) for a direct mental-model map and practical migration recipes.
Choose the migration path that matches your goal:

- For minimal app-code churn, use [Ink to Ink-Compat Migration](../migration/ink-to-ink-compat.md).
- For a full Rezi-native rewrite (`createNodeApp`, `ui.*`), use [Ink to Rezi migration guide](../migration/ink-to-rezi.md).

### What platforms does Rezi support?

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ User feedback: `spinner`, `skeleton`, `callout`, `errorDisplay`, `empty`
## Learn More

- [Concepts](guide/concepts.md) - Understanding Rezi's architecture
- [Ink to Ink-Compat Migration](migration/ink-to-ink-compat.md) - Port existing Ink apps with minimal code churn
- [Beautiful Defaults migration](migration/beautiful-defaults.md) - Design system styling defaults and manual overrides
- [Ink to Rezi Migration](migration/ink-to-rezi.md) - Mental model mapping and migration recipes
- [Lifecycle & Updates](guide/lifecycle-and-updates.md) - State management patterns
Expand Down
Loading
Loading