Skip to content

Commit d4840c6

Browse files
RtlZeroMemoryclaude
andcommitted
docs(guide,backend,readme): document inline screen mode + example app
- new centralized guide: docs/guide/screen-modes.md (usage, sizing, behavior, capability fallback, naming distinction vs executionMode) - mkdocs nav, backend/node.md screen section, protocol/abi.md 1.3.0 pin, README feature visibility, CLAUDE.md examples/map updates - examples/inline-status: runnable inline-mode app (spinner + progress + quit keys), verified end-to-end in a real terminal (tmux): scrollback preserved, live repaints, mid-run resize reflow, final frame left in scrollback with prompt restored below Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d50b5af commit d4840c6

13 files changed

Lines changed: 238 additions & 22 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Current public templates:
2020

2121
Repository examples:
2222
- `examples/hello-counter`
23+
- `examples/inline-status` (inline screen mode: scrollback-preserving region)
2324
- `examples/raw-draw-demo`
2425
- `examples/gallery`
2526
- `examples/regression-dashboard` (validation surface for layout/render/input regressions)
@@ -158,6 +159,7 @@ Practical defaults:
158159
- Do not hand-edit generated drawlist writers.
159160
- Prefer semantic widgets and recipe-driven styling over manual status rendering.
160161
- Prefer `ui.virtualList` for large collections.
162+
- Screen modes: `config.screen = { mode: "alt" | "inline", inlineRows }` selects alt-screen vs inline presentation (docs/guide/screen-modes.md). Distinct from `executionMode: "inline"` (engine-on-main-thread).
161163

162164
## Skills
163165

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ Individual widgets carry explicit [stability tiers](docs/widgets/stability.md) (
2020

2121
Dashboards, control planes, internal tools, log viewers, agent consoles, and developer workflows that need more than line-oriented output: multi-panel layouts, routed screens, focusable controls, forms, tables, overlays, and predictable behavior under keyboard and mouse input.
2222

23+
Apps choose their presentation: full-screen on the alternate buffer, or **inline screen mode** — a bounded region on the primary screen with terminal scrollback preserved above it and the final frame left in scrollback on exit (the presentation style of agent CLIs, progress UIs, and REPLs). See the [Screen Modes guide](docs/guide/screen-modes.md).
24+
2325
## Why Rezi
2426

2527
- A structured app model instead of a rendering loop you assemble yourself
2628
- A declarative widget API that does not require React
2729
- Native framebuffer diffing and terminal output through a fuzz-tested C engine
2830
- First-party widgets for forms, tables, trees, overlays, charts, file pickers, and command palettes
31+
- Full-screen and inline screen modes from the same widget API (`screen: { mode: "inline", inlineRows: 9 }`)
2932
- Reproducible rendering and input contracts, so application behavior is testable
3033

3134
## Example

docs/backend/node.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ if (app.isNoColor) {
6565
}
6666
```
6767

68+
Screen mode (alt vs inline):
69+
70+
- `screen.mode` selects the terminal presentation: `"alt"` (default,
71+
full-screen on the alternate buffer) or `"inline"` (a bounded region of
72+
`screen.inlineRows` rows on the primary screen with scrollback preserved
73+
above and the final frame left in scrollback on exit).
74+
- `screen.inlineRows` (1..1024) is required with `"inline"` and rejected
75+
otherwise; the effective viewport is clamped to the live terminal height.
76+
- Unrelated to `executionMode: "inline"` (engine-on-main-thread); the two
77+
options compose freely.
78+
- See the [Screen Modes guide](../guide/screen-modes.md) and
79+
`examples/inline-status` for a runnable app.
80+
81+
```ts
82+
const app = createNodeApp({
83+
initialState: {},
84+
config: {
85+
screen: { mode: "inline", inlineRows: 9 },
86+
},
87+
});
88+
```
89+
6890
Emoji width policy:
6991

7092
- `emojiWidthPolicy` keeps core text measurement and native rendering aligned.

docs/guide/screen-modes.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Screen Modes
2+
3+
Rezi apps can present in two terminal screen modes, selected at app creation:
4+
5+
| Mode | Surface | Fits |
6+
| --- | --- | --- |
7+
| `"alt"` (default) | Full-screen on the alternate screen buffer | Dashboards, editors, full-screen TUIs |
8+
| `"inline"` | A bounded region on the primary screen | Status UIs, progress displays, REPLs, agent-style CLIs |
9+
10+
In **alt mode** the app owns the whole terminal and the prior screen is
11+
restored on exit — the classic full-screen TUI experience.
12+
13+
In **inline mode** the app renders a region of `inlineRows` rows at the
14+
current scroll position, the way tools like Ink-based CLIs present:
15+
16+
- Terminal **scrollback stays visible above the region** while the app runs.
17+
- The region scrolls naturally with the session as it claims rows.
18+
- On exit, the **final frame remains in scrollback** and the shell prompt is
19+
restored on a fresh line below it.
20+
- Repaints use relative cursor motion only — the engine never clears the
21+
screen or addresses absolute rows, so content above the app is never
22+
touched.
23+
24+
## Enabling inline mode
25+
26+
```ts
27+
import { createNodeApp } from "@rezi-ui/node";
28+
29+
const app = createNodeApp({
30+
initialState: {},
31+
config: {
32+
screen: { mode: "inline", inlineRows: 9 },
33+
},
34+
});
35+
```
36+
37+
`screen.inlineRows` (1..1024) is required in inline mode and rejected in alt
38+
mode. The effective viewport is clamped to the live terminal height and
39+
re-clamped on terminal resize.
40+
41+
See [`examples/inline-status`](https://github.com/RtlZeroMemory/Rezi/tree/main/examples/inline-status)
42+
for a complete runnable app (spinner + progress bar + quit keys).
43+
44+
## Layout and sizing
45+
46+
The app's layout viewport in inline mode is `terminal columns x inlineRows`;
47+
resize events report that viewport, so layout, wrapping, and responsive
48+
breakpoints work unchanged.
49+
50+
Size `inlineRows` to your content's height. Remember that container chrome
51+
consumes rows: `ui.panel(...)` adds two border rows plus padding, and stack
52+
gaps add one row per gap. The `examples/inline-status` panel (one status row,
53+
a progress bar, and a caption) needs 9 rows.
54+
55+
## Behavior notes
56+
57+
- **Capability fallback**: protocol images (kitty / sixel / iTerm2) require
58+
absolute screen coordinates, so inline mode renders `ui.image(...)` through
59+
the sub-cell blitter path instead. `ui.canvas(...)` blitters are unaffected.
60+
Backend capability snapshots reflect this (`supportsScrollRegion` and image
61+
protocol support read as unavailable).
62+
- **Terminal resize**: the engine re-anchors and repaints the region. As with
63+
every inline-style CLI, the terminal's own reflow of *pre-resize scrollback*
64+
can leave artifacts above the region; the app's region itself repaints
65+
cleanly.
66+
- **Exiting**: prefer the repo's standard pattern of `await app.run()`
67+
followed by `process.exit(0)` when the app holds timers or other live
68+
handles (see `examples/gallery`).
69+
70+
## Naming note: `screen.mode` vs `executionMode`
71+
72+
These are independent options that compose freely:
73+
74+
- `screen.mode: "alt" | "inline"` — what the app looks like in the terminal
75+
(this page).
76+
- `executionMode: "auto" | "worker" | "inline"` — where the native engine
77+
runs (worker thread vs main thread); see
78+
[Worker model](../backend/worker-model.md).
79+
80+
An inline-screen app can run on the worker path, and a full-screen app can
81+
run on the inline execution path.
82+
83+
## Under the hood
84+
85+
Inline mode is implemented by the Zireael engine (v1.4.0+, engine ABI 1.3.0)
86+
as a first-class screen mode: relative-motion emission (CR/CUU/CUD/CHA), LF
87+
row claims that scroll the region as one unit, and a scrollback-safe erase
88+
baseline. The Node backend forwards `screen` as native config keys
89+
(`plat.screenMode`, `inlineRows`); raw `nativeConfig` passthrough of those
90+
keys is also accepted, with the high-level `screen` option taking precedence.

docs/protocol/abi.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ engine pins.
88
| Constant | Value | Notes |
99
|---|---:|---|
1010
| `ZR_ENGINE_ABI_MAJOR` | `1` | Engine ABI major |
11-
| `ZR_ENGINE_ABI_MINOR` | `2` | Engine ABI minor |
11+
| `ZR_ENGINE_ABI_MINOR` | `3` | Engine ABI minor |
1212
| `ZR_ENGINE_ABI_PATCH` | `0` | Engine ABI patch |
1313
| `ZR_DRAWLIST_VERSION_V1` | `1` | ZRDL v1 |
1414
| `ZR_EVENT_BATCH_VERSION_V1` | `1` | ZREV v1 |
1515
| `ZR_UNICODE_VERSION_MAJOR` | `15` | Unicode major |
1616
| `ZR_UNICODE_VERSION_MINOR` | `1` | Unicode minor |
1717
| `ZR_UNICODE_VERSION_PATCH` | `0` | Unicode patch |
1818

19+
Engine ABI `1.3.0` (Zireael v1.4.0) added inline screen mode: the
20+
`plat.screenMode` selector and the `inlineRows` field on the engine create
21+
and runtime configs. See [Screen Modes](../guide/screen-modes.md).
22+
1923
## Magic Values
2024

2125
| Constant | Value | Meaning |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "inline-status",
3+
"private": true,
4+
"type": "module",
5+
"dependencies": {
6+
"@rezi-ui/core": "0.1.0-beta.1",
7+
"@rezi-ui/node": "0.1.0-beta.1"
8+
}
9+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Inline screen mode example.
3+
*
4+
* The app renders a 9-row region on the primary screen instead of the
5+
* alternate screen: terminal scrollback stays visible above the region while
6+
* the app runs, and the final frame remains in scrollback after exit with
7+
* the shell prompt restored below it.
8+
*
9+
* Run: npx tsx examples/inline-status/src/index.ts
10+
*/
11+
import { defineWidget, ui, useInterval } from "@rezi-ui/core";
12+
import { createNodeApp } from "@rezi-ui/node";
13+
14+
const SPINNER = ["|", "/", "-", "\\"] as const;
15+
const TICK_MS = 80;
16+
17+
const app = createNodeApp<Record<string, never>>({
18+
initialState: {},
19+
config: {
20+
screen: { mode: "inline", inlineRows: 9 },
21+
},
22+
});
23+
24+
const SyncPanel = defineWidget((_props: Record<string, never>, ctx) => {
25+
const [tick, setTick] = ctx.useState(0);
26+
const [percent, setPercent] = ctx.useState(0);
27+
28+
useInterval(
29+
ctx,
30+
() => {
31+
setTick((value) => value + 1);
32+
setPercent((value) => Math.min(100, value + 1));
33+
},
34+
TICK_MS,
35+
);
36+
37+
const done = percent >= 100;
38+
const spinner = done ? "*" : SPINNER[tick % SPINNER.length];
39+
return ui.panel("Sync", [
40+
ui.row({ gap: 1 }, [
41+
ui.text(`${spinner} ${done ? "All chunks synced" : "Syncing chunks..."}`),
42+
ui.spacer({ flex: 1 }),
43+
ui.text(`${String(percent)}%`, { variant: "label" }),
44+
]),
45+
ui.progress(percent / 100),
46+
ui.text("scrollback stays above - press q to quit (frame stays visible)", {
47+
variant: "caption",
48+
}),
49+
]);
50+
});
51+
52+
app.view(() => SyncPanel({}));
53+
54+
app.keys({
55+
q: () => void app.stop(),
56+
escape: () => void app.stop(),
57+
"ctrl+c": () => void app.stop(),
58+
});
59+
60+
await app.run();
61+
process.exit(0);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"composite": true,
5+
"rootDir": "./src",
6+
"outDir": "./dist",
7+
"lib": ["ES2022"],
8+
"types": ["node"]
9+
},
10+
"include": ["src/**/*.ts"],
11+
"references": [{ "path": "../../packages/core" }, { "path": "../../packages/node" }]
12+
}

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ nav:
113113
- Animation: guide/animation.md
114114
- Runtime & Layout: guide/runtime-and-layout.md
115115
- Lifecycle & Updates: guide/lifecycle-and-updates.md
116+
- Screen Modes: guide/screen-modes.md
116117
- Error Handling: guide/error-handling.md
117118
- Routing: guide/routing.md
118119
- Layout: guide/layout.md

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)