feat(core): route renderer output through NativeSpanFeed for custom stdout#958
Open
feat(core): route renderer output through NativeSpanFeed for custom stdout#958
Conversation
…tdout When stdout !== process.stdout, CliRenderer auto-wires a NativeSpanFeed and pipes frame bytes to the user's Writable. Zig side gains an OutputBackend tagged union with inline-else dispatch to keep the hot path generic over the transport.
4a8451d to
5e21b24
Compare
Write NativeSpanFeed output through the original stdout sink so renderer-owned ANSI for custom split-footer transports isn't re-captured as external output.
Member
|
/pkg-preview |
# Conflicts: # packages/core/src/zig/renderer.zig
Wait for feed-backed startup output to drain before returning a renderer so immediate teardown on custom transports can't race in-flight writes. Also preserve fallback dimensions, clean up failed setup consistently, and fix non-threaded stdout debug dumps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
NativeSpanFeedinto@opentui/corerenderer so non-process.stdoutconsumers can receive rendered ANSI output via a zero-copy feed piped to any Node.jsWritableOutputBackendtagged union to unify transport dispatch with a singleinline elseswitchCliRendererconstructor to 5 params — the class auto-detects stream identity and wires the correct backend internallyMotivation
Enables a future
@opentui/sshpackage (and similar transports) by letting the renderer pipe to anyWritablewithout callers needing to know aboutNativeSpanFeed, feed pointers, or backend selection. The renderer takesstdin/stdoutand figures out the rest.What changed
TypeScript (
renderer.ts,zig.ts,test-renderer.ts)CliRendererconstructor:(stdin, stdout, width, height, config)— ifstdout !== process.stdout, aNativeSpanFeedis allocated internally and rendered bytes are piped through itcreateCliRendererfactory significantly simplified (dimension resolve +new CliRenderer(...)+await setupTerminal())test-renderer.tsrenderer construction reduced to a singlenew CliRenderer(...)call — zero feed-wiring knowledge requiredrendererTrackergains aprocessStdinUsersrefcount soprocess.stdin.pause()only fires when the last process.stdin-using renderer is destroyedwriteOutroutes through the native backend when a feed is wired, regardless of threading state (fixes ANSI interleaving on Linux + custom stdout)CliRendererConfiggains optionalwidth/heightfallback fields for non-TTY stdoutsresize(w, h)method for external resize signals (e.g. SSHwindow-change)Zig (
renderer-output.zig,renderer.zig,lib.zig)OutputBackendtagged union (StdoutBackend+FeedBackend) extracted intorenderer-output.zigCliRenderer.render()performs exactly oneswitch (self.backend) { inline else => ... }— monomorphized writer types, zero vtable costshouldTerminateexits the render thread without replaying the stale last-frame buffer after shutdown ANSIdumpStdoutBufferrenamed todumpOutputBufferand delegated toOutputBackend.dumpTo(out)— eliminates the last cross-module scattered switchcollectFrameStatshelper extracted to deduplicate stat collection across 4 render pathscreateWithOptionsconstructor deleted;createWithFullOptionsis the single entry pointrepaintSplitFooter,commitSplitFooterSnapshotBatched,appendSplitFooterSnapshotCommit, etc.) adapted to useinline elsebackend dispatchTests
renderer.custom-stdout.test.ts— 14 behavioral tests: byte routing, shutdown-ANSI regression, backpressure, dimension fallback, duck-typed streams, resize API, teardown resiliencerenderer.tracker.test.ts— 5 tests forprocess.stdinrefcount semantics across mixed configurationsrenderer_test.zig— 6 new Zig tests: FeedBackend write-through, shouldSkipFrame at queue saturation, supportsThreading invariant, per-instance buffer isolation, threaded-stdout destroy protocol, allocation-failure cleanuprenderer.console-startup.test.ts— removed 2 stale monkey-patch lines (superseded by core: add native split-footer commit path #890's native split-footer)Example
examples/xterm-web-demo/— working browser demo that renders an opentui app over xterm.js via WebSockets. Each tab gets its ownCliRendererbacked by aNativeSpanFeed. Responsive terminal via@xterm/addon-fit. IncludesREADME.mdwith architecture diagram and run instructions.Breaking changes
CliRenderer.dumpStdoutBuffer()renamed todumpOutputBuffer()(debug-only method, unlikely to affect consumers)CliRendererconstructor signature changed from 7 params to 5 (internal —createCliRendererpublic API unchanged)Test results