at main
1 folder
22 files
Readline-style shortcuts in the interactive filter input (closes #24)
The pty / pty i / pty interactive picker's filter input was append-
only — backspace + printable chars, no cursor, no word motion, no
kill-line. Editing longer queries was awkward compared to the shell
prompts people build muscle memory for.
Fixed at two layers:
1. applyTextKey (src/tui/widgets/form.ts) — the shared text-field
helper — gains ctrl+w (delete previous word) and ctrl+k (kill to
end of line). It already had backspace / delete / arrows / home
/ end / ctrl+a / ctrl+e / ctrl+u / alt+arrow and alt+b/f word
motion. So every consumer (interactive filter, form widget,
command palette, prompt bar) picks up the new shortcuts for free.
2. Interactive filter (src/tui/interactive.ts) — replaces the hand-
rolled append/slice key handling with a TextFieldState signal
routed through applyTextKey. The filter line renders via
renderFieldNodes so the cursor paints on top of the char under
it (inverse-video) without shoving neighbors sideways. Typing
resets the selection to 0 as before; pure cursor motion (home,
end, arrows) preserves the current selection.
Tests:
- widgets-form.test.ts: 8 new unit tests for ctrl+a, ctrl+e, ctrl+w
(end-of-line, mid-string, trailing-whitespace boundary, empty
field) and ctrl+k (mid-cursor, at-end).
- tui.test.ts: 2 new end-to-end tests through a real PTY confirming
ctrl+u clears the filter in one keystroke and ctrl+w removes the
trailing word, updating the rendered filter line.
Add TUI framework (alpha), three demo apps, testing docs
TUI framework (src/tui/):
Declarative UI with reactive signals, two-pass layout engine,
cell-buffer diffing, and 5 color themes. Exported as ptym/tui.
Includes text wrapping with word-boundary breaking and a
highlight callback for per-character span styling.
Demo apps (demos/):
Three standalone apps that showcase the framework and the
PTY testing library:
- file-browser: two-pane directory tree + file preview with
soft-wrap and markdown highlighting
- reminders: full CRUD backed by .md files with YAML frontmatter,
three views (grouped list, kanban board, calendar), overlays
- agent-teams: live dashboard of a simulated AI agent hierarchy
with real-time timeline, inter-agent messages, progress bars
Each demo has unit tests and PTY integration tests that verify
actual rendered screen output — not just string presence, but
layout correctness (gutter alignment, panel sizing, selection
indicators, overlay compositing).
Testing improvements:
- JSDoc on all Session methods and type fields
- docs/testing.md: server-mode API (attach, reconnect, resize,
connectToExisting, hasExited)
- Fix timing-sensitive filter test (waitForText instead of sleep)
README:
- Beta notice
- Expanded testing library section with realistic example
- TUI framework alpha notice with demo descriptions
Also: vitest.config.ts, demos/run convenience script.
Preserve palette index through CellBuffer so re-emitters can keep the
outer terminals theme
Part 2 of the palette-preservation work — first half was on
PtyHandle.readCells (commit 6248390). That change landed fgIndex /
bgIndex on the PtyCell shape but pty-layout tracing showed the round
trip broke inside the CellBuffer pipeline: setCell -> Cell (in
types.ts) had no index fields, so writeAnsi / diff / fullRender
silently dropped the index and re-emitted as truecolor.
Now the buffer-side Cell carries fgIndex / bgIndex (required,
number | null) and the pipeline plumbs them through:
- writeAnsi captures the index on SGR 30-37 / 90-97 / 38;5;N /
48;5;N, and clears it on 38;2 / 48;2 truecolor and on SGR 0 / 39 /
49 resets.
- cellsEqual compares the index so diff re-emits correctly when only
the index changed (same flattened RGB, different palette slot).
- diff + fullRender use shared emitFg / emitBg helpers that prefer
indexed SGR when fgIndex is non-null (SGR 30-37 for 0-7, 90-97 for
8-15, 38;5;N for 16-255) and fall back to truecolor SGR 38;2 /
48;2 otherwise.
- ptyView rendering path forwards cell.fgIndex / bgIndex from the
upstream PtyCell, so embedded panes re-emitted by consumers like
pty-layout keep the outer terminal's theme for indexed cells while
still supporting truecolor for programs that emit it.
Required rather than optional on Cell — nothing outside this repo
constructs Cell objects in our tree, and strict typing keeps the few
internal construction sites (emptyCell, makeCell, writeAnsi, FPS
counter literal in app.ts) honest. pty-layout's one selection-
highlight construction site will need a trivial fgIndex/bgIndex: null
addition.
fix(tui): render wide (astral-plane) glyphs correctly across incremental re-render (#51)
Two bugs in src/tui/buffer.ts making width-2 emoji (📬, 📭, 📫 —
astral-plane codepoints U+1F4XX) fossilize under app() incremental
re-render, observed by tui-sup while building agent-viz on top of
@myobie/pty/tui: a `📬` on a cards grid navigating until it scrolled
would end up displayed as `📬📬` (fossil + new) with shifted digits.
Root causes:
1. writeAnsi iterated the input by UTF-16 code unit (`ansi[i]`), so
astral-plane codepoints stored as surrogate pairs got split into
two lone surrogate halves in separate width-1 cells. Downstream
diff() and fullRender() then emitted the halves independently.
Modern host terminals (kitty, iTerm2, Ghostty) recombined them as
one wide glyph, but the CellBuffer's cell-index → terminal-column
mapping was off by one on every emoji, cascading into misaligned
writes as the row mutated. Fixed by detecting the high-surrogate
/ low-surrogate pattern and storing the full 2-code-unit string
in one Cell.
2. diff()'s `lastCol = c + 1` cursor-position tracker didn't account
for wide chars advancing the terminal cursor by 2. After emitting
a width-2 glyph at column c, the terminal cursor is at c+2, not
c+1. The adjacency check on the next emit then mispredicted,
causing an extra cursor move (efficiency loss) that combined with
the surrogate-split bug above produced positional drift on rows
with mixed content. Fixed by `lastCol = c + charWidth(nc.char)`.
Refactored the per-cell emit block into an `emit()` closure so both
the wide-char path and any future placeholder-clearing path share the
style-reset + SGR emit logic.
Test coverage: tests/buffer-wide-char-diff.test.ts — 10 cases using
xterm-headless + @xterm/addon-unicode11 as the oracle. The unicode11
addon is required because default xterm.js width tables treat all
astral-plane codepoints as width-1, which hides the bug behind an
environment quirk; with unicode11 the harness matches real modern
terminal semantics.
Fixes the framework bug tui-sup filed via coord; unblocks agent-viz
using `📬` directly without a width-1 workaround.
tui: text() accepts { fg, bold, ... } object form; bump 0.11.0 (#59)
Adds a second calling shape to text() so consumers that lay out
{ fg: color, bold: true, ... } maps compose cleanly. Original
text(str, color?, opts?) positional shape unchanged — dispatch is
by second-arg TYPE (string / array → positional Color, plain
object → opts-with-fg), not arity.
Fixes the demo-blocking issue where consumers of @myobie/pty/tui
using text("x", { fg: someColor }) hit `'fg' does not exist` on tsc
and "nodes is not iterable" at render time — the object shape was
reaching the positional `color` slot and getting spread as an
object where downstream code expected a Color.
Version bump 0.10.0 → 0.11.0 to release everything queued since
the last publish: the text() overload PLUS the accumulated
BREAKING changes since 0.10.0 (removed `pty state`, `pty wrap`,
supervisor/launchd-wrapper replaced by cron-driven `pty gc`,
name/displayName decouple, PTY_ROOT canonical env, `session_flapping`
event + fast-fail cap, etc.). See CHANGELOG.md 0.11.0 section.
5 new tests in tests/tui-framework.test.ts cover the object shape
(fg + bold; fg = SemanticColor string; object without fg; no-args
after str). Full suite: 1206 passed, 21 skipped, 0 failed.
Add TUI framework (alpha), three demo apps, testing docs
TUI framework (src/tui/):
Declarative UI with reactive signals, two-pass layout engine,
cell-buffer diffing, and 5 color themes. Exported as ptym/tui.
Includes text wrapping with word-boundary breaking and a
highlight callback for per-character span styling.
Demo apps (demos/):
Three standalone apps that showcase the framework and the
PTY testing library:
- file-browser: two-pane directory tree + file preview with
soft-wrap and markdown highlighting
- reminders: full CRUD backed by .md files with YAML frontmatter,
three views (grouped list, kanban board, calendar), overlays
- agent-teams: live dashboard of a simulated AI agent hierarchy
with real-time timeline, inter-agent messages, progress bars
Each demo has unit tests and PTY integration tests that verify
actual rendered screen output — not just string presence, but
layout correctness (gutter alignment, panel sizing, selection
indicators, overlay compositing).
Testing improvements:
- JSDoc on all Session methods and type fields
- docs/testing.md: server-mode API (attach, reconnect, resize,
connectToExisting, hasExited)
- Fix timing-sensitive filter test (waitForText instead of sleep)
README:
- Beta notice
- Expanded testing library section with realistic example
- TUI framework alpha notice with demo descriptions
Also: vitest.config.ts, demos/run convenience script.
Preserve palette index through CellBuffer so re-emitters can keep the
outer terminals theme
Part 2 of the palette-preservation work — first half was on
PtyHandle.readCells (commit 6248390). That change landed fgIndex /
bgIndex on the PtyCell shape but pty-layout tracing showed the round
trip broke inside the CellBuffer pipeline: setCell -> Cell (in
types.ts) had no index fields, so writeAnsi / diff / fullRender
silently dropped the index and re-emitted as truecolor.
Now the buffer-side Cell carries fgIndex / bgIndex (required,
number | null) and the pipeline plumbs them through:
- writeAnsi captures the index on SGR 30-37 / 90-97 / 38;5;N /
48;5;N, and clears it on 38;2 / 48;2 truecolor and on SGR 0 / 39 /
49 resets.
- cellsEqual compares the index so diff re-emits correctly when only
the index changed (same flattened RGB, different palette slot).
- diff + fullRender use shared emitFg / emitBg helpers that prefer
indexed SGR when fgIndex is non-null (SGR 30-37 for 0-7, 90-97 for
8-15, 38;5;N for 16-255) and fall back to truecolor SGR 38;2 /
48;2 otherwise.
- ptyView rendering path forwards cell.fgIndex / bgIndex from the
upstream PtyCell, so embedded panes re-emitted by consumers like
pty-layout keep the outer terminal's theme for indexed cells while
still supporting truecolor for programs that emit it.
Required rather than optional on Cell — nothing outside this repo
constructs Cell objects in our tree, and strict typing keeps the few
internal construction sites (emptyCell, makeCell, writeAnsi, FPS
counter literal in app.ts) honest. pty-layout's one selection-
highlight construction site will need a trivial fgIndex/bgIndex: null
addition.
Add TUI framework (alpha), three demo apps, testing docs
TUI framework (src/tui/):
Declarative UI with reactive signals, two-pass layout engine,
cell-buffer diffing, and 5 color themes. Exported as ptym/tui.
Includes text wrapping with word-boundary breaking and a
highlight callback for per-character span styling.
Demo apps (demos/):
Three standalone apps that showcase the framework and the
PTY testing library:
- file-browser: two-pane directory tree + file preview with
soft-wrap and markdown highlighting
- reminders: full CRUD backed by .md files with YAML frontmatter,
three views (grouped list, kanban board, calendar), overlays
- agent-teams: live dashboard of a simulated AI agent hierarchy
with real-time timeline, inter-agent messages, progress bars
Each demo has unit tests and PTY integration tests that verify
actual rendered screen output — not just string presence, but
layout correctness (gutter alignment, panel sizing, selection
indicators, overlay compositing).
Testing improvements:
- JSDoc on all Session methods and type fields
- docs/testing.md: server-mode API (attach, reconnect, resize,
connectToExisting, hasExited)
- Fix timing-sensitive filter test (waitForText instead of sleep)
README:
- Beta notice
- Expanded testing library section with realistic example
- TUI framework alpha notice with demo descriptions
Also: vitest.config.ts, demos/run convenience script.
Add TUI framework (alpha), three demo apps, testing docs
TUI framework (src/tui/):
Declarative UI with reactive signals, two-pass layout engine,
cell-buffer diffing, and 5 color themes. Exported as ptym/tui.
Includes text wrapping with word-boundary breaking and a
highlight callback for per-character span styling.
Demo apps (demos/):
Three standalone apps that showcase the framework and the
PTY testing library:
- file-browser: two-pane directory tree + file preview with
soft-wrap and markdown highlighting
- reminders: full CRUD backed by .md files with YAML frontmatter,
three views (grouped list, kanban board, calendar), overlays
- agent-teams: live dashboard of a simulated AI agent hierarchy
with real-time timeline, inter-agent messages, progress bars
Each demo has unit tests and PTY integration tests that verify
actual rendered screen output — not just string presence, but
layout correctness (gutter alignment, panel sizing, selection
indicators, overlay compositing).
Testing improvements:
- JSDoc on all Session methods and type fields
- docs/testing.md: server-mode API (attach, reconnect, resize,
connectToExisting, hasExited)
- Fix timing-sensitive filter test (waitForText instead of sleep)
README:
- Beta notice
- Expanded testing library section with realistic example
- TUI framework alpha notice with demo descriptions
Also: vitest.config.ts, demos/run convenience script.
Preserve palette index through CellBuffer so re-emitters can keep the
outer terminals theme
Part 2 of the palette-preservation work — first half was on
PtyHandle.readCells (commit 6248390). That change landed fgIndex /
bgIndex on the PtyCell shape but pty-layout tracing showed the round
trip broke inside the CellBuffer pipeline: setCell -> Cell (in
types.ts) had no index fields, so writeAnsi / diff / fullRender
silently dropped the index and re-emitted as truecolor.
Now the buffer-side Cell carries fgIndex / bgIndex (required,
number | null) and the pipeline plumbs them through:
- writeAnsi captures the index on SGR 30-37 / 90-97 / 38;5;N /
48;5;N, and clears it on 38;2 / 48;2 truecolor and on SGR 0 / 39 /
49 resets.
- cellsEqual compares the index so diff re-emits correctly when only
the index changed (same flattened RGB, different palette slot).
- diff + fullRender use shared emitFg / emitBg helpers that prefer
indexed SGR when fgIndex is non-null (SGR 30-37 for 0-7, 90-97 for
8-15, 38;5;N for 16-255) and fall back to truecolor SGR 38;2 /
48;2 otherwise.
- ptyView rendering path forwards cell.fgIndex / bgIndex from the
upstream PtyCell, so embedded panes re-emitted by consumers like
pty-layout keep the outer terminal's theme for indexed cells while
still supporting truecolor for programs that emit it.
Required rather than optional on Cell — nothing outside this repo
constructs Cell objects in our tree, and strict typing keeps the few
internal construction sites (emptyCell, makeCell, writeAnsi, FPS
counter literal in app.ts) honest. pty-layout's one selection-
highlight construction site will need a trivial fgIndex/bgIndex: null
addition.