This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

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.

authored by

Nathan and committed by
GitHub
(Jul 7, 2026, 12:04 PM +0200) 4dd6f9f5 377d8ccf

+71 -13
+6 -1
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 - ## Unreleased 3 + ## 0.11.0 4 + 5 + ### `@myobie/pty/tui` — `text()` accepts an object form `{ fg, bold, ... }` 6 + 7 + - `text()` gains a second calling shape: `text(str, { fg: someColor, bold: true, italic: true, ... })`. The `fg` key maps to the `color` slot; the rest are the existing `TextOpts`. The original positional shape `text(str, color?, opts?)` continues to work — dispatch is by second-arg TYPE (string / array → positional Color, plain object → opts-with-fg), not arity. 8 + - Motivation: consumers of the `@myobie/pty/tui` barrel that lay out `{ fg: ..., bold: ... }` maps ran headlong into a compile error (`'fg' does not exist`) when the object reached the positional-`color` slot. Both shapes now compose cleanly. 4 9 5 10 ### Lean-core: `pty state` and `pty wrap` REMOVED (BREAKING) 6 11
+1 -1
flake.nix
··· 29 29 30 30 # Generated from package-lock.json. 31 31 # Regenerate with: nix run nixpkgs#prefetch-npm-deps -- package-lock.json 32 - npmDepsHash = "sha256-y1hEiPG6kZLilTHF6dH7Ql/X9ddDBucsZYJqETlTuyE="; 32 + npmDepsHash = "sha256-mer8rhDyD/j+htWDU8F1EH7MrnuS8pS57WdQgGH8cnQ="; 33 33 34 34 # node-pty has native code that needs these at build time 35 35 nativeBuildInputs = with pkgs; [ python3 pkg-config ];
+2 -2
package-lock.json
··· 1 1 { 2 2 "name": "@myobie/pty", 3 - "version": "0.10.0", 3 + "version": "0.11.0", 4 4 "lockfileVersion": 3, 5 5 "requires": true, 6 6 "packages": { 7 7 "": { 8 8 "name": "@myobie/pty", 9 - "version": "0.10.0", 9 + "version": "0.11.0", 10 10 "hasInstallScript": true, 11 11 "license": "MIT", 12 12 "dependencies": {
+1 -1
package.json
··· 1 1 { 2 2 "name": "@myobie/pty", 3 - "version": "0.10.0", 3 + "version": "0.11.0", 4 4 "description": "Persistent terminal sessions with detach/attach, plus a Playwright-style testing library for TUI apps", 5 5 "type": "module", 6 6 "license": "MIT",
+32 -8
src/tui/builders.ts
··· 60 60 }; 61 61 } 62 62 63 + interface TextOpts { 64 + bold?: boolean; dim?: boolean; italic?: boolean; inverse?: boolean; 65 + background?: Color; 66 + truncate?: boolean; wrap?: boolean; 67 + highlight?: (text: string) => import("./nodes.ts").Span[]; 68 + } 69 + 70 + /** Two shapes are supported: 71 + * 72 + * text("hi") 73 + * text("hi", someColor) 74 + * text("hi", someColor, { bold: true }) 75 + * text("hi", { fg: someColor, bold: true }) // object shape; `fg` maps to color 76 + * 77 + * The object shape came in for consumers that prefer object-with-`fg` — everyone 78 + * ends up laying out `{ fg: ..., bold: ... }` anyway, so having to hop between 79 + * positional-color and object-opts was friction. Existing call sites keep working 80 + * because the runtime dispatch checks the second-arg TYPE, not arity: a string 81 + * (SemanticColor) or array ([r,g,b]) is a Color; a plain object is TextOpts. */ 82 + export function text(str: string): TextNode; 83 + export function text(str: string, color: Color, opts?: TextOpts): TextNode; 84 + export function text(str: string, opts: TextOpts & { fg?: Color }): TextNode; 63 85 export function text( 64 86 str: string, 65 - color?: Color, 66 - opts?: { 67 - bold?: boolean; dim?: boolean; italic?: boolean; inverse?: boolean; 68 - background?: Color; 69 - truncate?: boolean; wrap?: boolean; 70 - highlight?: (text: string) => import("./nodes.ts").Span[]; 71 - }, 87 + colorOrOpts?: Color | (TextOpts & { fg?: Color }), 88 + opts?: TextOpts, 72 89 ): TextNode { 73 - return { type: "text", text: str, color, ...opts }; 90 + if (colorOrOpts === undefined) return { type: "text", text: str }; 91 + if (typeof colorOrOpts === "string" || Array.isArray(colorOrOpts)) { 92 + // Positional shape: colorOrOpts IS the Color. 93 + return { type: "text", text: str, color: colorOrOpts, ...opts }; 94 + } 95 + // Object shape: pull `fg` off as the color; the rest are ordinary opts. 96 + const { fg, ...rest } = colorOrOpts; 97 + return { type: "text", text: str, color: fg, ...rest }; 74 98 } 75 99 76 100 export function spacer(): SpacerNode {
+29
tests/tui-framework.test.ts
··· 34 34 expect(n.color).toEqual([255, 0, 0]); 35 35 }); 36 36 37 + it("text() object shape: { fg, bold, ... }", () => { 38 + // Consumers that prefer the object form pass `fg` for the color and 39 + // any TextOpts inline. `fg` maps to `color`; the rest spread as-is. 40 + const n = text("hi", { fg: [255, 0, 0], bold: true }); 41 + expect(n.color).toEqual([255, 0, 0]); 42 + expect(n.bold).toBe(true); 43 + }); 44 + 45 + it("text() object shape: `fg` with a SemanticColor string", () => { 46 + const n = text("hi", { fg: "primary", italic: true }); 47 + expect(n.color).toBe("primary"); 48 + expect(n.italic).toBe(true); 49 + }); 50 + 51 + it("text() object shape without `fg` leaves color unset", () => { 52 + const n = text("hi", { bold: true, dim: true }); 53 + expect(n.color).toBeUndefined(); 54 + expect(n.bold).toBe(true); 55 + expect(n.dim).toBe(true); 56 + }); 57 + 58 + it("text() with no args after str", () => { 59 + const n = text("hi"); 60 + expect(n.type).toBe("text"); 61 + expect(n.text).toBe("hi"); 62 + expect(n.color).toBeUndefined(); 63 + expect(n.bold).toBeUndefined(); 64 + }); 65 + 37 66 it("spacer, gap, separator, indent", () => { 38 67 expect(spacer().type).toBe("spacer"); 39 68 expect(gap(3).size).toBe(3);