@siphon_npm/ui
v0.7.0
Published
Shared terminal presentation defaults for siphon CLIs.
Readme
@siphon/ui
The shared terminal floor for Siphon CLIs: a readable dark-first semantic palette, Unicode- and ANSI-safe text primitives, responsive tables, and explicit emphasis rules. It does not choose a site's information hierarchy or rows.
import { createTheme, renderTable } from "@siphon/ui";
const text = renderTable({
theme: createTheme(),
columns: [
{ key: "symbol", header: "SYMBOL", priority: 100 },
{ key: "price", header: "PRICE", align: "right", priority: 90 },
{ key: "volume", header: "VOLUME", align: "right", priority: 10 },
],
rows,
});priority keeps the most useful fields as the terminal narrows. A minWidth
allows a text column to shrink before a field is dropped. Mark stable identity
with required, remove genuinely empty fields with omitWhenEmpty, provide
compact variants; every dropped non-empty value moves into a continuation
under its header, while overflowLabel customizes that label. layoutTable() returns the
active/dropped column receipt; renderTable() returns its text compatibility
view. Colour follows the TTY/NO_COLOR contract and can be overridden with
createTheme({ colors }).
For an existing typed record, columns may use value(row, index) instead of a
key. flex shares the remaining terminal width, while detail(row, index)
adds one bounded continuation line under the first flex column. Compact counts
and relative timestamps use formatCompactNumber and formatRelativeAge so
every listing speaks the same scan vocabulary. formatTriState renders source
availability as Yes, No, or —; null is never guessed false.
For a site-native renderer, use the semantic palette directly rather than inventing colours per table:
import { createPalette } from "@siphon/ui";
const ui = createPalette();
ui.id("3");
ui.num("1.1k");
ui.warn("2y");
ui.tag("r/anime");
ui.asset("img:3");
ui.title("A row's primary field");
ui.highlight("standout");tag() hashes a category to a stable terminal hue. selection() uses the ref
role and highlight() the standout role. Styling only adds ANSI: NO_COLOR,
piped, and explicit structured output remain plain, and every style closes only
the attribute it opened so an outer dim line survives an inner coloured field.
Columns can opt into shared rule vocabulary without giving the package any
product judgment: magnitude applies configured numeric tiers, threshold
highlights a configured bound, and frequency highlights uncommon values.
renderRows is the compatibility seam for existing CLIs: it preserves an
established row layout while gaining correct ANSI/emoji/CJK cell widths. New
CLIs should start with renderTable.
renderComparison({ entities, metrics }) is the shortlist seam. It emits one
transposed decision model as width-safe TTY, durable Markdown, or text; tied
winners are preserved and a metric with only one known value awards nobody.
Targets retain control of metric labels, values, proxies and whether lower or
higher is better.
renderDocument(document) is the detail-view twin. It consumes the portable
@siphon/core/document shape and applies the same palette and width engine to
headings, nested lists, data/infobox tables, figures with captions, TeX math,
quotes and code. Pass image(figure) or code(block) callbacks to replace a
structured block with a target renderer without moving downloads, protocols or
domain-specific diagrams into UI.
renderDocument(record.document, {
width: process.stdout.columns,
theme,
image: (figure) => renderFocalImage(figure.media),
code: (block) => block.language === "go-board" ? renderBoard(block.code) : undefined,
});Run bun test for the package contracts and bun run gallery to write the
review artifact at packages/ui/.ui/default-surfaces.html.
