feat(core,node,native): app.printAbove + app.setInlineRows (scrollback commits)#417
Conversation
…crollback commits)
Exposes the engine's scrollback-commit primitive (Zireael ABI 1.4.0) as
first-class app APIs for agent-style inline CLIs:
- app.printAbove(view, { rows? }): render a standalone widget tree at the
current viewport width (rows auto-measured, clamped to the engine's 1024)
and commit it into terminal scrollback above the live region. Committed
blocks become ordinary terminal history and survive app exit.
- app.setInlineRows(rows): change the inline viewport height at runtime;
the engine clamps to the terminal and emits a resize so layout follows.
Plumbing:
- vendor: Zireael bumped to the scrollback-commit build (ca3e6b5; repin to
the squash SHA after RtlZeroMemory/Zireael#112 merges)
- native: engineCommitScrollback NAPI binding + FFI extern; fixed the
hand-maintained index.js export list (the silent-undefined trap)
- core: renderViewForScrollback helper (measure -> commit -> layout ->
renderToDrawlist, mirroring the testkit pipeline); optional
RuntimeBackend.commitScrollback/setInlineRows; requested ABI 1.4.0
- node: commitScrollback worker message with rc-carrying commitResult ack
(commit failures are recoverable backpressure, not fatal); runtime
config baseline derivation so setConfig resends created limits/plat with
only inlineRows changed; both worker and in-process execution paths
- example: inline-status prints checkpoints above and resizes on +/-
Tests: 14 new (pure render helper incl. measured wrapping/gap heights;
backend commit/resize round-trips on both paths via a strict expectation
shim; engine-rejection surfacing; alt-mode and bounds validation).
5093/5093 suite green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR adds inline-screen scrollback commit and dynamic region resize capabilities. It introduces app.printAbove(view) to render content into scrollback and app.setInlineRows(rows) to adjust region height. Engine ABI advances to 1.4.0 with corresponding C/Rust/Node backend implementations spanning staged commit queuing, framebuffer rendering, worker protocol messages, and integration tests. ChangesInline scrollback commit and resize feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Tools execution failed with the following error: Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error) Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/guide/screen-modes.md (1)
113-114:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix ABI version mismatch in the guide.
Line 113 says engine ABI
1.3.0, but this PR introduces/depends on ABI1.4.0. Please align this line to avoid contradictory integration guidance.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/guide/screen-modes.md` around lines 113 - 114, Update the documentation line describing Inline mode to reflect the correct engine ABI version used by this PR: change the ABI from "1.3.0" to "1.4.0" so the sentence reads that Zireael engine (v1.4.0+, engine ABI 1.4.0) implements Inline mode; ensure the phrase in the Inline mode description that references "engine ABI" and the version number is updated consistently to 1.4.0.packages/node/src/backend/nodeBackend.ts (1)
231-245:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHandle
commitWaitersin every teardown path.The new queue is drained in
failAll()andrejectPending(), butdispose()still leaves in-flightcommitScrollback()promises unresolved. Disposing the backend mid-commit now hangs callers waiting on that promise.Suggested fix
dispose(): void { if (disposed) return; disposed = true; @@ const err = new Error("NodeBackend: disposed"); while (eventWaiters.length > 0) eventWaiters.shift()?.reject(err); while (capsWaiters.length > 0) capsWaiters.shift()?.reject(err); + while (commitWaiters.length > 0) commitWaiters.shift()?.reject(err); eventQueue.length = 0; rejectFrameWaiters(frameTracking, frameAudit, err); rejectDebugWaiters(debugChannel, err);Also applies to: 259-263
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/node/src/backend/nodeBackend.ts` around lines 231 - 245, The teardown logic misses rejecting pending commitScrollback() promises: ensure commitWaiters are rejected wherever other waiters are (failAll(), rejectPending(), and dispose()) so in-flight commitScrollback() callers don't hang; update dispose() and any early-return/error teardown paths to iterate commitWaiters (like you do for eventWaiters and capsWaiters) and call .reject(err) (or .reject(new Error("disposed"))) on each Deferred, and clear commitWaiters and eventQueue after rejecting.
🧹 Nitpick comments (3)
packages/node/src/__tests__/worker/testShims/commitNative.ts (1)
1-3: ⚡ Quick winUse
interfacefor object-shape declarations in this TS file.On Line 1 and Lines 27-33, object shapes are declared with
typealiases (EngineState,RuntimeCfg,PlatCfg). Please switch these tointerfaceto match repo standards.Also applies to: 27-33
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/node/src/__tests__/worker/testShims/commitNative.ts` around lines 1 - 3, Replace the three object-shape type aliases with equivalent interfaces: change the `type EngineState = Readonly<{ destroyed: boolean }>` to `interface EngineState { readonly destroyed: boolean }`, and convert `type RuntimeCfg = Readonly<...>` and `type PlatCfg = Readonly<...>` to `interface RuntimeCfg { ... }` and `interface PlatCfg { ... }` respectively, retaining all property names and readonly modifiers (use `readonly` for properties instead of `Readonly<...>`). Update any local references if necessary to the interface names (EngineState, RuntimeCfg, PlatCfg) so the rest of the file still compiles.Source: Coding guidelines
packages/node/src/__tests__/scrollback_commit.test.ts (1)
52-58: ⚡ Quick winAvoid asserting internal error-message wording as primary evidence.
On Lines 56-57, checking for
"engine_commit_scrollback failed"ties the test to implementation phrasing. Prefer stable contract evidence (error type/code and operation context) to keep the test behavior-first.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/node/src/__tests__/scrollback_commit.test.ts` around lines 52 - 58, The test currently asserts the rejected error by matching an internal message substring ("engine_commit_scrollback failed"); update the assert.rejects predicate to only check the error type and stable error code: ensure the rejected error is an instance of ZrUiError and has err.code === "ZRUI_BACKEND_ERROR" (remove the err.message.includes(...) check). Keep the call under test (backend.commitScrollback?.(makeClearDrawlist(), 99)) and the use of assert.rejects unchanged, but simplify the predicate to only verify ZrUiError and the error code.Source: Coding guidelines
packages/core/src/app/printAbove.ts (1)
20-23: ⚡ Quick winUse an
interfacefor this exported object shape.At Line 20,
PrintAboveRenderis an object-shape alias; repository standards preferinterfacefor this case.As per coding guidelines, `**/*.{ts,tsx}`: "Prefer `interface` for defining object shapes in TypeScript files".♻️ Proposed change
-export type PrintAboveRender = Readonly<{ - bytes: Uint8Array; - rows: number; -}>; +export interface PrintAboveRender { + readonly bytes: Uint8Array; + readonly rows: number; +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/app/printAbove.ts` around lines 20 - 23, Replace the exported type alias PrintAboveRender with an exported interface to follow repo style: declare export interface PrintAboveRender { readonly bytes: Uint8Array; readonly rows: number; } — keep the same property names and readonly modifiers so runtime shapes and usages (e.g., any code referencing PrintAboveRender) remain compatible; update any local references if needed to use the interface name (no other behavior changes).Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/inline-status/src/index.ts`:
- Around line 79-86: The handlers mutate inlineRows before the async call so
local state can drift if app.setInlineRows fails; change both handlers to
compute a nextRows local variable (e.g., nextRows = Math.min(16, inlineRows + 1)
and nextRows = Math.max(5, inlineRows - 1)), call app.setInlineRows(nextRows)
and only assign inlineRows = nextRows after that promise resolves (or in .then),
and handle rejection without changing inlineRows (optionally log or show the
error) so the local value stays consistent with the backend.
In `@packages/core/src/app/createApp.ts`:
- Around line 1019-1051: printAbove and setInlineRows call backend methods while
stop() may be tearing down (state still "Running") and they skip the module's
in-render/in-commit re-entrancy protections; fix by acquiring the same
lifecycle/re-entrancy guard used elsewhere before calling
backend.commitScrollback or backend.setInlineRows (e.g. enter the in-render /
in-commit guard), re-check that the app is still operational after acquiring the
guard, and only then invoke commitFn(rendered...) or setFn(rows); if the guard
indicates shutdown/in-progress, bail with the existing guards.throwCode path
instead. Ensure you reference the existing symbols printAbove, setInlineRows,
commitFn (backend.commitScrollback), setFn (backend.setInlineRows),
guards.assertOperational and sm.assertOneOf when applying the guard.
In `@packages/node/src/__tests__/scrollback_commit.test.ts`:
- Around line 34-44: The test bodies currently call backend.start() then perform
assertions and call backend.stop()/backend.dispose() only on the happy path;
wrap each test body (e.g., the test that uses createNodeBackendInternal and
calls backend.start(), backend.commitScrollback, etc.) in a try/finally so that
backend.stop() and backend.dispose() are always executed in the finally block;
apply the same change to the other tests mentioned (the blocks covering lines
46-61, 63-76, 78-90, 93-111) to guarantee teardown even if assertions fail.
In `@packages/node/src/backend/nodeBackend.ts`:
- Around line 207-210: The runtimeConfigBase is derived too early (before
start() resolves widthPolicy), causing setInlineRows() to resend a stale
baseline; move the call to deriveRuntimeConfigBase so it runs after start() has
completed and the actual widthPolicy is available (i.e., compute
runtimeConfigBase only after start() resolves), update any code paths that call
setInlineRows() to use the post-start runtimeConfigBase, and apply the same
change for the second occurrence that uses deriveRuntimeConfigBase (the block
around the other setInlineRows()/start() pair referenced in the comment).
- Around line 883-896: commitScrollback currently sends a backend request
without validating the rows argument locally; call
validateInlineRowsOrThrow(rows) at the start of commitScrollback (same
validation used by setInlineRows) before creating the ArrayBuffer, adding the
deferred, or calling send so invalid row values raise ZRUI_INVALID_PROPS
immediately instead of after a worker round-trip.
- Around line 883-896: In commitScrollback, a deferred (d) is pushed into
commitWaiters before calling send, so if send (worker.postMessage) throws
synchronously the stale waiter remains; change the flow to either push d only
after send succeeds or wrap send(...) in try/catch and on synchronous throw
remove the queued waiter (commitWaiters) and rethrow the error; refer to
commitScrollback, the local deferred d, commitWaiters, and send to locate the
change.
---
Outside diff comments:
In `@docs/guide/screen-modes.md`:
- Around line 113-114: Update the documentation line describing Inline mode to
reflect the correct engine ABI version used by this PR: change the ABI from
"1.3.0" to "1.4.0" so the sentence reads that Zireael engine (v1.4.0+, engine
ABI 1.4.0) implements Inline mode; ensure the phrase in the Inline mode
description that references "engine ABI" and the version number is updated
consistently to 1.4.0.
In `@packages/node/src/backend/nodeBackend.ts`:
- Around line 231-245: The teardown logic misses rejecting pending
commitScrollback() promises: ensure commitWaiters are rejected wherever other
waiters are (failAll(), rejectPending(), and dispose()) so in-flight
commitScrollback() callers don't hang; update dispose() and any
early-return/error teardown paths to iterate commitWaiters (like you do for
eventWaiters and capsWaiters) and call .reject(err) (or .reject(new
Error("disposed"))) on each Deferred, and clear commitWaiters and eventQueue
after rejecting.
---
Nitpick comments:
In `@packages/core/src/app/printAbove.ts`:
- Around line 20-23: Replace the exported type alias PrintAboveRender with an
exported interface to follow repo style: declare export interface
PrintAboveRender { readonly bytes: Uint8Array; readonly rows: number; } — keep
the same property names and readonly modifiers so runtime shapes and usages
(e.g., any code referencing PrintAboveRender) remain compatible; update any
local references if needed to use the interface name (no other behavior
changes).
In `@packages/node/src/__tests__/scrollback_commit.test.ts`:
- Around line 52-58: The test currently asserts the rejected error by matching
an internal message substring ("engine_commit_scrollback failed"); update the
assert.rejects predicate to only check the error type and stable error code:
ensure the rejected error is an instance of ZrUiError and has err.code ===
"ZRUI_BACKEND_ERROR" (remove the err.message.includes(...) check). Keep the call
under test (backend.commitScrollback?.(makeClearDrawlist(), 99)) and the use of
assert.rejects unchanged, but simplify the predicate to only verify ZrUiError
and the error code.
In `@packages/node/src/__tests__/worker/testShims/commitNative.ts`:
- Around line 1-3: Replace the three object-shape type aliases with equivalent
interfaces: change the `type EngineState = Readonly<{ destroyed: boolean }>` to
`interface EngineState { readonly destroyed: boolean }`, and convert `type
RuntimeCfg = Readonly<...>` and `type PlatCfg = Readonly<...>` to `interface
RuntimeCfg { ... }` and `interface PlatCfg { ... }` respectively, retaining all
property names and readonly modifiers (use `readonly` for properties instead of
`Readonly<...>`). Update any local references if necessary to the interface
names (EngineState, RuntimeCfg, PlatCfg) so the rest of the file still compiles.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b9c70594-96c5-4905-870e-59f64ac5520d
📒 Files selected for processing (33)
CLAUDE.mddocs/backend/node.mddocs/guide/screen-modes.mdexamples/inline-status/src/index.tspackages/core/src/abi.tspackages/core/src/app/__tests__/printAbove.test.tspackages/core/src/app/createApp.tspackages/core/src/app/inspectorOverlayHelper.tspackages/core/src/app/printAbove.tspackages/core/src/app/types.tspackages/core/src/backend.tspackages/native/index.d.tspackages/native/index.jspackages/native/src/ffi.rspackages/native/src/lib.rspackages/native/vendor/VENDOR_COMMIT.txtpackages/native/vendor/zireael/include/zr/zr_config.hpackages/native/vendor/zireael/include/zr/zr_engine.hpackages/native/vendor/zireael/include/zr/zr_version.hpackages/native/vendor/zireael/src/core/zr_diff.cpackages/native/vendor/zireael/src/core/zr_diff.hpackages/native/vendor/zireael/src/core/zr_engine.cpackages/native/vendor/zireael/src/core/zr_engine_present.incpackages/node/src/__tests__/scrollback_commit.test.tspackages/node/src/__tests__/worker/testShims/commitNative.tspackages/node/src/backend/backendSharedConfig.tspackages/node/src/backend/nodeBackend.tspackages/node/src/backend/nodeBackendInline.tspackages/node/src/worker/engineWorker.tspackages/node/src/worker/engineWorker/messageHandlers.tspackages/node/src/worker/engineWorker/shared.tspackages/node/src/worker/protocol.tsvendor/zireael
- core: printAbove/setInlineRows gain lifecycle-idle and in-commit/ in-render re-entrancy guards (mirroring update()) - node: setInlineRows derives the runtime baseline from the START-resolved config (width policy settles during start), stripping Node-transport keys (fpsCap/maxEventBytes/frameTransport) so engine_set_config strict validation cannot reject the resend; commitScrollback validates rows client-side on both paths - tests: guaranteed backend teardown via try/finally in all bodies - example: inlineRows updates commit only after setInlineRows resolves Re-verified in tmux: 4 printAbove checkpoints, live +/- resize, no stderr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All five review findings addressed in the latest commit: lifecycle/re-entrancy guards on both app APIs, runtime baseline now derived from the start-resolved config (width policy preserved; Node-transport keys stripped), client-side rows validation on commitScrollback for both paths, try/finally teardown in every test body, and the example commits local inlineRows only after the backend resolves. Suite 5093/5093 green; tmux re-verified. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
examples/inline-status/src/index.ts (2)
24-35: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy liftSeparate side effects from view component lifecycle.
The
SyncPanelcomponent triggers side effects by callingmaybePrintCheckpoint(which mutates module-levellastCheckpointand invokesapp.printAbove) from within theuseIntervalcallback. This violates the guideline to keep view functions pure without side effects or external dependencies.Consider refactoring to handle checkpoint printing outside the component—for example, by tracking progress in app state and using a separate observer or lifecycle hook to call
app.printAbove.As per coding guidelines: "Keep view functions pure without side effects or external dependencies".
Also applies to: 48-50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/inline-status/src/index.ts` around lines 24 - 35, maybePrintCheckpoint and the module-level lastCheckpoint are causing side effects from inside the SyncPanel's useInterval callback (mutating state and calling app.printAbove); move this behavior out of the view by converting progress updates into pure state events and performing the printing in a separate effect/observer: update SyncPanel/useInterval to only emit or set a progress value (e.g., via a callback or shared state) and remove direct calls to maybePrintCheckpoint from the component; implement a dedicated effect or watcher (outside the render path) that listens to progress changes and runs maybePrintCheckpoint (which may keep lastCheckpoint and call app.printAbove) so view code stays pure while side effects live in a lifecycle handler.Source: Coding guidelines
70-70: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winWrap the root view in
ui.page()to follow framework structure.The coding guidelines require that the root view use
ui.page()orui.appShell()with at leastp: 1spacing. Currently, the view is a bare widget call. Even in inline mode, examples should demonstrate proper structural patterns.📐 Proposed fix to add page wrapper
-app.view(() => SyncPanel({})); +app.view(() => ui.page({ p: 1 }, [SyncPanel({})]));As per coding guidelines: "Root view must use
ui.page()orui.appShell()with at leastp: 1spacing".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/inline-status/src/index.ts` at line 70, The root view currently registers a bare widget via app.view(() => SyncPanel({})); update it to wrap the root in a page or appShell per guidelines by returning ui.page(...) or ui.appShell(...) with at least p: 1 and placing the SyncPanel widget inside that container (i.e., change the app.view callback to return a ui.page/ui.appShell with p: 1 containing SyncPanel({})).Source: Coding guidelines
🧹 Nitpick comments (1)
examples/inline-status/src/index.ts (1)
48-50: ⚡ Quick winAvoid
setTimeoutby computing the next value inline.Using
setTimeout(..., 0)to defer readingpercentRef.currentafter the state update is fragile and relies on implicit timing. Instead, compute the next percent value directly and pass it tomaybePrintCheckpoint:const nextPercent = Math.min(100, percentRef.current + 1); maybePrintCheckpoint(nextPercent);This eliminates the timing dependency and makes the logic more explicit.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/inline-status/src/index.ts` around lines 48 - 50, The code uses setTimeout(..., 0) to defer reading percentRef.current before calling maybePrintCheckpoint; replace that by computing the next percent synchronously (e.g., const nextPercent = Math.min(100, percentRef.current + 1)) and call maybePrintCheckpoint(nextPercent) directly so you remove the timing dependency and make the update explicit (update the call site where setTimeout wraps maybePrintCheckpoint and reference percentRef and maybePrintCheckpoint).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@examples/inline-status/src/index.ts`:
- Around line 24-35: maybePrintCheckpoint and the module-level lastCheckpoint
are causing side effects from inside the SyncPanel's useInterval callback
(mutating state and calling app.printAbove); move this behavior out of the view
by converting progress updates into pure state events and performing the
printing in a separate effect/observer: update SyncPanel/useInterval to only
emit or set a progress value (e.g., via a callback or shared state) and remove
direct calls to maybePrintCheckpoint from the component; implement a dedicated
effect or watcher (outside the render path) that listens to progress changes and
runs maybePrintCheckpoint (which may keep lastCheckpoint and call
app.printAbove) so view code stays pure while side effects live in a lifecycle
handler.
- Line 70: The root view currently registers a bare widget via app.view(() =>
SyncPanel({})); update it to wrap the root in a page or appShell per guidelines
by returning ui.page(...) or ui.appShell(...) with at least p: 1 and placing the
SyncPanel widget inside that container (i.e., change the app.view callback to
return a ui.page/ui.appShell with p: 1 containing SyncPanel({})).
---
Nitpick comments:
In `@examples/inline-status/src/index.ts`:
- Around line 48-50: The code uses setTimeout(..., 0) to defer reading
percentRef.current before calling maybePrintCheckpoint; replace that by
computing the next percent synchronously (e.g., const nextPercent =
Math.min(100, percentRef.current + 1)) and call
maybePrintCheckpoint(nextPercent) directly so you remove the timing dependency
and make the update explicit (update the call site where setTimeout wraps
maybePrintCheckpoint and reference percentRef and maybePrintCheckpoint).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b45066ce-80ce-42e4-8c09-a9c565332c28
📒 Files selected for processing (9)
examples/inline-status/src/index.tspackages/core/src/app/createApp.tspackages/native/vendor/VENDOR_COMMIT.txtpackages/native/vendor/zireael/src/core/zr_diff.cpackages/node/src/__tests__/scrollback_commit.test.tspackages/node/src/backend/backendSharedConfig.tspackages/node/src/backend/nodeBackend.tspackages/node/src/backend/nodeBackendInline.tsvendor/zireael
✅ Files skipped from review due to trivial changes (2)
- packages/native/vendor/VENDOR_COMMIT.txt
- vendor/zireael
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/node/src/tests/scrollback_commit.test.ts
- packages/node/src/backend/nodeBackendInline.ts
- packages/core/src/app/createApp.ts
- packages/node/src/backend/backendSharedConfig.ts
- packages/native/vendor/zireael/src/core/zr_diff.c
- packages/node/src/backend/nodeBackend.ts
Summary
Platform work for the upcoming pi-style coding-agent TUI: exposes Zireael's new scrollback-commit primitive end-to-end as
app.printAbove(view), plus runtime inline viewport resizing asapp.setInlineRows(rows).This is the Ink
<Static>/ Bubble TeaPrintlnpattern: committed blocks become ordinary terminal scrollback (they scroll with the session and survive app exit) while the live region keeps rendering below.Depends on RtlZeroMemory/Zireael#112 (vendored at its branch head⚠️ repin submodule +
ca3e6b5;VENDOR_COMMIT.txtto the squash SHA once that PR merges, before merging this one).What changed
Native (
@rezi-ui/native)engineCommitScrollback(engineId, drawlist, rows)NAPI binding + FFI extern.index.jsexport list — NAPI-RS regeneratesindex.d.tsbut not this file, so the new symbol was silentlyundefinedthrough the package entry (found via real-terminal testing; worth a future codegen guard).Core (
@rezi-ui/core)renderViewForScrollback(): one-off widget-tree → ZRDL bytes at (viewport cols × measured rows), mirroring the testkit pipeline (commit → layout → renderToDrawlist) with auto height viameasure()(wrapping and stack gaps included), clamped to the engine's 1024-row bound.App.printAbove(view, { rows? })+App.setInlineRows(rows)with lifecycle guards; optionalRuntimeBackend.commitScrollback/setInlineRowsso other backends are unaffected; inspector-overlay wrapper delegates added.Node (
@rezi-ui/node)commitScrollbackworker message with an rc-carryingcommitResultack — commit failures (e.g. staged-rows backpressureZR_ERR_LIMIT) surface as rejected promises rather than the fatal path used bysetConfig.setInlineRowsresends the derived runtime-config baseline (created limits/plat/toggles) with onlyinlineRowschanged — required becauseengine_set_configvalidates the full surface and rejects plat drift.ZRUI_INVALID_PROPSbounds,ZRUI_BACKEND_ERRORoutside inline screen mode).Example + docs
examples/inline-status: prints checkpoint lines above at each progress quarter,+/-resize the region live.Test evidence
inlineRows=5+plat.screenMode=1, so a resolving call proves payload integrity), engine-rejection surfacing, alt-mode rejection.printAbovecheckpoints stacked in scrollback above the live Rezi panel during the run, surviving exit with the prompt below;+/-resized the region live in both directions; zero stderr errors.Compatibility
ZRUI_BACKEND_ERRORunless the app was created withscreen.mode: "inline"; backends without the methods are unaffected (optional interface members).🤖 Generated with Claude Code