Skip to content

ui: shared layout primitives, no dependencies - #388

Merged
ralyodio merged 1 commit into
mainfrom
worktree-termui-inspired-ui
Aug 13, 2026
Merged

ui: shared layout primitives, no dependencies#388
ralyodio merged 1 commit into
mainfrom
worktree-termui-inspired-ui

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Answers "can we use TermUI to pretty up the CLI?" — not as a dependency, but the ideas port cleanly.

TermUI is a shadcn-style registry of React + Ink components. moshcode has zero runtime dependencies, and install.sh untars a release and runs node with no npm install anywhere in the path — the same constraint documented in src/pty.mjs for why node-pty was rejected in favour of script(1). Adding React + Ink (plus a TypeScript build for the .tsx, in an all-.mjs codebase) would break the primary install path. The components are a few lines of string arithmetic each, so they are reimplemented instead.

What this adds to src/ui.mjs

  • strip / visible / pad / clip — measuring and padding in printable columns
  • table — columns sized to their contents, alignment, max with a "… n more" footer
  • panel — a box sized to its body, title set into the top edge
  • sparkline — moved here from crypto.mjs, unchanged
  • gauge — a horizontal meter, clamped at both ends

What it removes

Three modules had each grown a private copy of visible/pad (games.mjs, rss-ui.mjs, and the herd-ui test), and advisor.mjs and crypto.mjs held identical copies of clip. They now share one implementation. Existing export names (games.strip, games.visible, rss-ui.visibleWidth, crypto.sparkline) are preserved as re-exports, so no caller or test changed.

Two real fixes fell out

  • clip is now colour-aware. Cutting a painted string with slice can land inside an escape sequence or drop the reset that closes a colour run — and a terminal never told to stop keeps that colour for everything printed afterwards, including the next command's output.
  • renderRoster no longer hand-corrects for ANSI width. It was padding the state cell to a fixed 9 by adding the length of its own escape codes, and pinning cwd at 24 columns whether the paths were 8 or 60. It is now a table call.

Verification

node --test: 1924 tests, 0 failing (1893 before; 31 new in test/ui.test.mjs). package.json is untouched — still zero runtime dependencies.

🤖 Generated with Claude Code

Adds table, panel, gauge and the measuring helpers (strip/visible/pad/
clip) to src/ui.mjs, and moves sparkline there out of crypto.mjs.

The shapes are lifted from the TermUI registry, which is React + Ink and
so not installable here: moshcode has zero runtime dependencies and
install.sh untars a release and runs node with no npm install in the
path, the same constraint that kept node-pty out of pty.mjs. The ideas
are a few lines of string arithmetic each, so they are reimplemented
rather than depended on.

Three modules had each grown a private copy of visible/pad — games.mjs,
rss-ui.mjs and the herd-ui test — and advisor.mjs and crypto.mjs had
identical copies of clip. All now share one implementation.

clip is newly colour-aware: cutting a painted string with slice can drop
the reset that ends a colour run, and a terminal never told to stop
stays that colour for everything printed afterwards.

renderRoster is converted as the first caller. It was padding a coloured
state cell to a fixed width by hand-correcting for the length of its own
escape codes, and pinning cwd at 24 columns whether the paths were 8 or
60.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

97 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 41 | LOW: 51

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
HIGH tls-verification-disabled src/dns.mjs:741
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:61
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:75
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:101
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:265
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:269
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:314
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:499
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:675
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:677
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:736
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:782
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:852
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:955
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1063
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1199
MEDIUM js-unescaped-html-sink apps/pwa/src/routes/moshpit.mjs:1419
MEDIUM js-timing-unsafe-mac-compare apps/pwa/src/routes/settings-sync.mjs:192
MEDIUM js-dynamic-code-execution apps/pwa/test/apikey-mask.test.mjs:129
MEDIUM sql-template-interpolation apps/pwa/test/credits-webhook-event-match.test.mjs:111
MEDIUM sql-template-interpolation apps/pwa/test/credits-webhook-event-match.test.mjs:131
MEDIUM sql-template-interpolation apps/pwa/test/moshpit-terms.test.mjs:192
MEDIUM sql-string-concatenation src/cli-schema.mjs:128
MEDIUM sql-string-concatenation src/cli-schema.mjs:417
MEDIUM sql-string-concatenation src/cli-schema.mjs:543
MEDIUM js-timing-unsafe-mac-compare src/settings-sync.mjs:325
MEDIUM insecure-temp-file test/dns-disable-restore.test.mjs:93
MEDIUM insecure-temp-file test/dns-disable-restore.test.mjs:310
MEDIUM insecure-temp-file test/news.test.mjs:505
MEDIUM insecure-temp-file test/plugins.test.mjs:152
MEDIUM insecure-temp-file test/pty.test.mjs:28
MEDIUM insecure-temp-file test/pty.test.mjs:31
MEDIUM insecure-temp-file test/pty.test.mjs:40
MEDIUM insecure-temp-file test/pty.test.mjs:42
MEDIUM insecure-temp-file test/pty.test.mjs:47
MEDIUM insecure-temp-file test/pty.test.mjs:48
MEDIUM insecure-temp-file test/pty.test.mjs:49
MEDIUM insecure-temp-file test/tabs.test.mjs:8
MEDIUM insecure-temp-file test/tabs.test.mjs:13
MEDIUM insecure-temp-file test/tabs.test.mjs:14
MEDIUM insecure-temp-file test/tabs.test.mjs:22
MEDIUM insecure-temp-file test/trust.test.mjs:240
LOW secret-generic-credential apps/pwa/test/apikey-bearer-scheme.test.mjs:30
LOW secret-generic-credential apps/pwa/test/apikey-mask.test.mjs:38
LOW secret-generic-credential apps/pwa/test/apikey-reveal.test.mjs:35
LOW secret-generic-credential apps/pwa/test/approvals-context.test.mjs:28

…and 47 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio marked this pull request as ready for review August 13, 2026 15:38
@ralyodio
ralyodio merged commit 7506f9d into main Aug 13, 2026
4 checks passed
@ralyodio
ralyodio deleted the worktree-termui-inspired-ui branch August 13, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant