tui: the lifecycle — every way out is one road, Static, print, the clock and progress; step 5 complete - #403
Merged
Merged
Conversation
…ernate screen, non-TTY output, colors, the console
Ink's static-*.tsx, issue-973-static-commit.tsx and the Static cases of components, render-to-string and background ported into test/ink/static.rip, with cursor.tsx's useStdout and useStderr cases through print; byte pins for the write above the frame, the mouse origin after it, the clock's timer and the progress sequence in test.rip; stubs so every file loads.
A Static item is laid out once as a root at the terminal's width, painted to rows of its own and written above the live frame through Screen.above — the frame's rows cleared, the rows written, the frame drawn again below, in one write — then hidden, so the live frame never holds it and the mouse origin moves by the rows written. print takes the same road, print.err on stderr. clock(interval) is one timer per interval that runs while a mounted component holds and reads it, on the mount's own clock under mount. screen.progress reports OSC 9;4 with the next frame's write, and Screen.clearProgress answers the clear for the way out. Grid.toString(true) leaves a row's trailing default-style blanks off, as the plain form does, so a static row carries no padding into the scrollback; the six pins that spelled those blanks out are updated. Static's registration keeps its node as a plain value: a cleanup that read the ref cell subscribed the disposing branch effect to it, and the detach's clear built the replacement branch twice.
…spend, the alternate screen, non-TTY output, colors, the console terminal.rip owns raw mode, the modes, the probes, the cursor, the alternate screen, the signal, crash and exit handlers, and the console capture, as one setup / teardown pair; tui.rip's listen, ask, answer and close are thin over it, and `suspend` is exported. screen.rip gains the `alt` flag and `leave` moves nowhere under it; paint.rip emits colors at the depth `run` read, downsampling 24-bit colors to the 256 and the 16. Rows of the contract corrected while building, each with its reason: - a log while suspended is the console's own, not relayed: teardown restores the console when the terminal is handed over - the frame is cleared before warn and error too: the clear precedes the line whatever stream it goes to - at 16 colors full-intensity red is bright red and full blue bright blue (91, 104): xterm's palette puts the full-intensity primaries at 8 to 15 - the CI=false child is held to its frame count, not to contiguous text: a diffed frame writes only the changed cell - a stderr stack need not name the script: what the runtime prints of a stack is the runtime's - an unhandled rejection may let `done`'s continuation print before the exit: the runtime's microtask timing - a listener hears Ctrl-Z before its default action, as it hears Ctrl-C - a failure leaves whatever reached the screen: the fuzz demands the last frame only of the ways that draw it
…pen terminal items
examples/log.rip is a Static list of finished steps under a spinner and a progress bar driven by one clock, a warning printed above the frame, q to quit; test.rip runs it headless through mount and view.tick. A written item its own component shows again is hidden again before the next batch, and a print called with an item already in the tree comes after that item. README gains Static output, Animation and Progress; PLAN §3, §6 and §9 say what is built; TODO lists what is open.
…ing — into the terminal lifecycle
…ve, progress cleared on every way out, the screen's own alt and interactive A console line takes the road a Static item takes: Screen.above clears the frame, writes the line to its stream, moves the frame's row down by the lines written, and books the frame that draws it again, so the clear, the line and the frame go out in one write. terminal.rip's own clear-and-diff path is gone; under the alternate screen a line is still kept and replayed once the screen is left, the one case above does not carry. Teardown clears the progress indicator after the last frame. The screen's `alt` and `interactive` fields are the one record of both; off a terminal the frame writes nothing and the last one is written once at exit through rowsToString, so a Static item and a print are written as they arrive. A frame booked before a close draws nothing after it, and Clock.tick's `stop` no longer shadows the terminal's. Pins re-spelled to above's coalesced write: a log's clear, line and frame are one write on the next frame, which the line books; a stderr line's clear goes out at once; with the mouse on, a log moves the frame's row instead of asking for it again. Added: a Static item and a print under the alternate screen and off a terminal, the log example under both, the progress cleared on a signal, a frame booked before a quit.
…ead of the runtime's, off first at teardown; a signal death maps to 128 + n in bin/rip
…ners after this package's, once; a signal's way out never throws
…s the process's own stdin
…le stopped, keys in the read, stops twice — and every state against every way out
… conventions read them, and every lowered color the nearest of xterm's own table
… Console of the runtime's own over two relaying sinks
…le do now, and SOURCE.md counts Ink's titles as it registers them
…s untracked; a Static under a swapping branch is built once
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.
PLAN step 5, both halves, reviewed cold and planned by three agents; every finding fixed and proven under a real job-controlled pty.
terminal.rip. One idempotentsetup/teardownowns raw mode, bracketed paste, focus reports, the mouse modes, the kitty push and pop, the probes, the cursor, and the alternate screen (altScreen: true,?1049h, frames absolute, left after the last frame). Every way out —quit, Ctrl-C, SIGINT / SIGTERM / SIGHUP (exit 128 + n), an uncaught error or unhandled rejection (exit 1),process.exit,beforeExitwith a live app, a listener or frame that throws, a stdin whose raw mode cannot be set, a write that fails at close — takes the same road. The process handlers belong to the app's life: on atopen(terminal only, ahead of the runtime's), off first at teardown, never touched by suspend or resume; a crash closes the app with its error (donerejects), removes our handlers, and leaves the error to the listeners after ours, once. A fuzz of keys, resizes, logs and suspends holds the terminal's modes to what the app believes after every step.Suspend. Ctrl-Z is a default action of
keydown(preventable): teardown, then SIGTSTP to the whole process group — what the tty driver does for a cooked ^Z — and only when the app reads the process's own stdin (a fake stream is not the terminal's job, so in-process tests take the same road without the signal). A stopped app holds the loop, so a timer-less app survivesfg; on SIGCONT: setup, the origin asked again with the mouse, the kitty flag re-pushed only if it was pushed, pointer and parser reset, a whole repaint at the terminal's size.suspend(fn)is the same road without the signal. Keys left in the read that carried the ^Z are nobody's. Proven undertest/terminal/ptyrun.py, a job-controlled pty shell checked in with the suite: ^Z stops the job leader (thebin/ripwrapper),%fgcontinues a key-driven app with no timer and it redraws whole and hears the next key, ^Z + SIGTERM +%fgexits 143 with the user's exit hook run, two stops and two continues exit 0. A stop signal to an orphaned process group is discarded by the kernel, so only such a shell can prove a stop; the earlier "for real" pin never did and is gone.Output.
Staticpaints each keyed child once, as its own rows at the terminal's width, written above the live frame and never again — a no-op on the alternate screen, written as it arrives off a terminal.print(text)andprint.errwrite above the frame the same way; console output is captured by onenode:consoleConsoleover two sinks, so every writing method (table,group,trace,assert,count,time*…) lands above the frame and the alternate screen replays it at leave. All three go throughScreen.above: the frame's row moves down by the lines written, so a click after a log still lands on its row.clock(interval)is one shared timer per interval that runs only while a mounted component reads it and stops at close.screen.progressdrives the terminal's own indicator (OSC 9;4), sent with the frame's write and cleared on every way out.Newline.renderToStringanswers the rowsStaticwrote and then the frame.Off a terminal. No modes, no probes, no raw mode; the last frame is written once at exit,
Staticitems as they arrive. Color depth is read once (screen.colors):NO_COLORpresent and not empty is none;FORCE_COLOR0/falsenone,1–3that depth,trueor empty 16; else byCOLORTERMandTERM. Below full depth a color becomes the nearest of xterm's 256 by RGB distance (a color on the cube is sent as that point), and below that the nearest of xterm's 16 — a 256-entry table of xterm's own values, pinned at 33 points at both depths.Held to Ink. 27 lifecycle titles held and 1 stated difference (
test/terminal/SOURCE.md, recounted per file with sites and titles); 50 more paint cases intest/ink/static.rip(540 in the Ink suite, 521 of Ink's 632 titles);examples/log.ripis a build log —Staticsteps into the scrollback, a spinner on the clock, a progress bar, aprint.Also here: the
Staticcleanup reads itsref:cell directly (PR #402 made that sound) with a pin that aStaticunder a swappingifbuilds once;bin/ripmaps a signal death to 128 + n.Suites: test 129, text 49, layout 57, input 316, events 226, mouse 65, ink 540, yoga 543 (4 pinned), yoga-aspect 37, yoga-hand 53, fuzz 7, damage 6, terminal 78. The pty harness leaves no process behind (checked right after the suite and ten seconds later).
bun run test:all: 29 lanes, 12,632 tests.