diff --git a/template/AGENTS.md b/template/AGENTS.md index 34ce154..1bc4fa1 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -199,17 +199,42 @@ a subscription callback, or `Promise.resolve().then(() => sig.set(…))`. `"fit"`, `"50vw"`, `Size.min/max/clamp/add/sub(...)`. **Frame the root with `1fr` or a fixed size or the tree collapses to 0.** -**Color & faces** — combinators wrap a string (innermost wins): -`fg(color)`, `bg(color)`, `bold`, `dim`, `italic`, `underline`, `reverse`, -`strike`. Colors: `idx(0..255)`, `rgb(0xRRGGBB)` / `rgb(r,g,b)`, -`rgba(...,a)`, `color("#ff0080")`, `DEFAULT`. +**Color & faces** — a ``'s bare attributes *are* its face: `fg`, `bg`, +and the boolean SGR flags `bold`/`dim`/`italic`/`underline`/`reverse`/`strike`. +Colors: a hex string anywhere (`"#ff0080"`, `"#f08"`, `"#ff0080cc"`), or +`idx(0..255)`, `rgb(0xRRGGBB)` / `rgb(r,g,b)`, `rgba(...,a)`, `DEFAULT`. ```jsx -{() => bold(fg(idx(2))(`${pct(frac.get())}`))} +{() => pct(frac.get())} +``` + +**Uniform style → bare attrs. Per-span → nest. Runtime-computed → `face()`.** +Bare attrs face the *whole* Text, so a line with per-span colors nests `` +runs as children — the inner face merges over the outer, so it wins: + +```jsx + + + {n} + {name} + +``` + +When the face itself is computed at runtime, `face(patch)` applies a patch +object to content — the programmatic form behind ``, and the escape hatch +when bare attrs can't carry a dynamic value: + +```jsx +import { face } from "yeet:tui"; +{() => face({ fg: heat(frac.get()), bold: frac.get() > 0.9 })(label)} ``` (There's also a separate `style.red(s)` / `style.bold(s)` global — that's for -raw `tty.write` line-mode tools, *not* the JSX tree. Use face combinators here.) +raw `tty.write` line-mode tools, *not* the JSX tree.) + +> The named combinators `fg(c)(s)` / `bold(s)` / `dim(s)` … are **deprecated**, +> kept only for back-compat. Reach for bare attrs, nested ``, or +> `face(patch)` instead. ## Data sources @@ -296,7 +321,9 @@ writes atomically. `yeet.exit()` tears the script down. 3. **Set-during-render throws** — defer signal writes out of the render path. 4. **`column` is the default Box direction** (Yoga, not CSS) — set `direction="row"` explicitly for horizontal. -5. **Use `fg`/`bg`/`bold`** in the tree — never `color`/`style`/`backgroundColor`. +5. **Style with bare `` attrs** (`fg`/`bg`/`bold`/…) — never + `color`/`style`/`backgroundColor`, and not the deprecated `fg(c)(s)`/`bold(s)` + combinators (use `face(patch)` when the style is computed at runtime). 6. **Ring-buffer events are wrapped** under the `btf_struct` name — unwrap with `w?. ?? w`. 7. **`@/` and `#/` are bundle-time only** — the runtime resolver doesn't know @@ -333,7 +360,7 @@ widths read `frac`, so each fill is a thunk child that re-mints its Box when ```jsx // components/gauge.jsx -import { Box, Text, bold, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; const RAIL = idx(238); const heat = (f) => (f < 0.6 ? idx(2) : f < 0.85 ? idx(3) : idx(1)); @@ -343,10 +370,10 @@ const lpad = (s, n) => `${s}`.padStart(n); export default function Gauge({ frac, label }) { return ( - {fg(idx(244))(label)} + {label} {() => } {() => } - {() => bold(lpad(pct(frac.get()), 5))} + {() => lpad(pct(frac.get()), 5)} ); } @@ -390,7 +417,7 @@ export const cpuPct = computed(() => Math.round(cpu.get() * 100)); ```jsx // main.jsx -import { Box, Text, bold, mount } from "yeet:tui"; +import { Box, Text, mount } from "yeet:tui"; import { cpu } from "@/probes/sysload.js"; import Gauge from "@/components/gauge.jsx"; @@ -400,7 +427,7 @@ tty.on("keydown", (e) => { const Root = () => ( - {bold(" sysload — q to quit")} + {" sysload — q to quit"} @@ -557,7 +584,7 @@ The other egress: the kernel aggregates into an array map, JS just reads slots. ```jsx // components/histogram.jsx — `latency` is a signal of per-bucket counts -import { Box, Text, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; const BARS = "▁▂▃▄▅▆▇█"; const lo = (i) => (i === 0 ? 0 : 1 << (i - 1)); // log2 bucket lower bound (ns) @@ -571,7 +598,7 @@ export default function Histogram({ latency }) { return slots.map((n, i) => ( {`${String(lo(i)).padStart(12)}ns `} - {fg(idx(4))(BARS[Math.min(7, Math.floor((n / peak) * 7.99))].repeat(Math.ceil((n / peak) * 40)))} + {BARS[Math.min(7, Math.floor((n / peak) * 7.99))].repeat(Math.ceil((n / peak) * 40))} {` ${n}`} )); @@ -637,7 +664,7 @@ The crash screen itself is just a `bg`-filled box — no special API: ```jsx // components/bsod.jsx -import { Box, Text, bold, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; const BLUE = idx(20); const WHITE = idx(15); @@ -646,11 +673,11 @@ export default function Bsod({ error }) { const lines = String(error?.stack ?? error?.message ?? error).split("\n"); return ( - {bold(fg(WHITE)(":( your dashboard hit an error"))} + {":( your dashboard hit an error"} {" "} - {lines.map((l) => {fg(WHITE)(l)})} + {lines.map((l) => {l})} {" "} - {fg(idx(250))("press q to quit")} + {"press q to quit"} ); } diff --git a/template/src/components/detail.jsx b/template/src/components/detail.jsx index 2a25afe..aac49b2 100644 --- a/template/src/components/detail.jsx +++ b/template/src/components/detail.jsx @@ -1,27 +1,25 @@ // The selected CPU's recent context switches, streamed live: prev -> next // with the outgoing task's on-CPU slice. Reads `cpus` + `selected`. -import { Box, Text, bold, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; import { fmtDuration, lpad, pad } from "@/lib/format.js"; export default ({ cpus, selected, rows }) => ( - - {() => bold(fg(idx(214))(` ▸ CPU ${selected.get()}`))} + + {() => ` ▸ CPU ${selected.get()}`} {() => { const cpu = cpus.get()[selected.get()]; const recent = cpu ? cpu.recent : []; const n = Math.max(1, rows); - if (!recent.length) return [{fg(idx(240))(" (idle — no switches in window)")}]; + if (!recent.length) return [{" (idle — no switches in window)"}]; return recent.slice(0, n).map((s) => ( - {[ - fg(idx(252))(` ${pad(s.prev, 16)}`), - fg(idx(240))(" → "), - fg(idx(45))(pad(s.next, 16)), - fg(idx(244))(lpad(fmtDuration(s.slice), 8)), - ]} + {` ${pad(s.prev, 16)}`} + {" → "} + {pad(s.next, 16)} + {lpad(fmtDuration(s.slice), 8)} )); }} diff --git a/template/src/components/footer.jsx b/template/src/components/footer.jsx index 9cb5427..2fc13b3 100644 --- a/template/src/components/footer.jsx +++ b/template/src/components/footer.jsx @@ -3,7 +3,7 @@ // strips as trailing break-whitespace). Each shortcut is a raised key-cap — // the key glyph in bold gold on a tile a shade lighter than the rail — // followed by a dimmed label, so the keys pop out of the line. -import { Box, Text, bg, bold, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; const RAIL = idx(235); // hint-rail background const CAP = idx(238); // key-cap tile, a shade lighter than the rail @@ -11,8 +11,8 @@ const GLYPH = idx(222); // bright gold key text const LABEL = idx(247); // dimmed description const hint = (keys, label) => [ - bg(CAP)(bold(fg(GLYPH)(` ${keys} `))), - fg(LABEL)(` ${label} `), + {` ${keys} `}, + {` ${label} `}, ]; export default () => ( diff --git a/template/src/components/heatmap.jsx b/template/src/components/heatmap.jsx index 80f99dd..b7a95db 100644 --- a/template/src/components/heatmap.jsx +++ b/template/src/components/heatmap.jsx @@ -4,18 +4,18 @@ // brighter than quiet ones. `gridCols`/`maxRows` come from the responsive // layout: it renders the most recent `gridCols` of history and, when there // are more cores than rows, windows the list to keep the selection in view. -import { Box, Text, bold, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; import { heat } from "@/lib/format.js"; const HEADER = " cores × time (newest → right) brightness = switch rate"; export default ({ cpus, selected, gridCols, maxRows }) => ( - + {() => { const total = cpus.get().length; const shown = Math.min(maxRows, total); - return fg(idx(244))(total > shown ? `${HEADER} · ${total} cores` : HEADER); + return total > shown ? `${HEADER} · ${total} cores` : HEADER; }} @@ -39,10 +39,13 @@ export default ({ cpus, selected, gridCols, maxRows }) => ( const label = `${on ? "▸" : " "} cpu ${String(i).padStart(2)} `; const cells = c.hist .slice(-gridCols) - .map((v) => fg(heat(v <= 0 ? 0 : Math.sqrt(v / peak)))("█")); + .map((v) => {"█"}); + const head = on + ? {label} + : {label}; return ( - {[on ? bold(fg(idx(231))(label)) : fg(idx(245))(label), ...cells]} + {[head, ...cells]} ); }); diff --git a/template/src/components/histogram.jsx b/template/src/components/histogram.jsx index 18208cc..b54bbd2 100644 --- a/template/src/components/histogram.jsx +++ b/template/src/components/histogram.jsx @@ -1,7 +1,7 @@ // Run-queue latency as a log2 histogram. Reads the polled `latency` signal // (cumulative counts per bucket) and draws a bar per bucket, the label // being the bucket's lower bound. -import { Box, Text, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; import { fmtDuration, lpad } from "@/lib/format.js"; const LO = 8; // 2^8 ns = 256ns @@ -10,8 +10,8 @@ const BARW = 16; export default ({ latency, maxRows }) => ( - {fg(idx(244))(" runqueue latency")} - {fg(idx(244))(" (wakeup → on-cpu)")} + {" runqueue latency"} + {" (wakeup → on-cpu)"} {() => { const slots = latency.get(); @@ -36,11 +36,9 @@ export default ({ latency, maxRows }) => ( const w = Math.round((n / peak) * BARW); rows.push( - {[ - fg(idx(244))(`${lpad(fmtDuration(2 ** i), 7)} `), - fg(idx(75))("█".repeat(w)), - fg(idx(240))(n ? ` ${n}` : ""), - ]} + {`${lpad(fmtDuration(2 ** i), 7)} `} + {"█".repeat(w)} + {n ? ` ${n}` : ""} , ); } diff --git a/template/src/components/procs.jsx b/template/src/components/procs.jsx index 3d9aa58..26e1a60 100644 --- a/template/src/components/procs.jsx +++ b/template/src/components/procs.jsx @@ -5,7 +5,7 @@ // heatmap's inferno ramp; the label is the command and its share of total CPU // time. Only switches the kernel emitted are counted, so sub-`min-slice` tasks // don't show — it tracks CPU hogs, not every wakeup. -import { Box, Text, bold, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; import { heat, lpad, pad } from "@/lib/format.js"; const NAMEW = 15; // command column width @@ -13,8 +13,8 @@ const PCTW = 7; // "100.0%" + a space export default ({ procs, width, maxRows }) => ( - - {fg(idx(244))(" top processes · on-cpu time (per 500ms window)")} + + {" top processes · on-cpu time (per 500ms window)"} {() => { @@ -22,7 +22,7 @@ export default ({ procs, width, maxRows }) => ( const barW = Math.max(6, width - NAMEW - PCTW - 3); const shown = list.slice(0, maxRows); if (!shown.length) { - return [{fg(idx(240))(" (idle — no qualifying switches yet)")}]; + return [{" (idle — no qualifying switches yet)"}]; } const top = shown[0].pct || 1; // scale bars to the busiest process @@ -31,12 +31,10 @@ export default ({ procs, width, maxRows }) => ( const w = Math.max(1, Math.round(frac * barW)); return ( - {[ - fg(idx(252))(` ${pad(p.comm, NAMEW)}`), - fg(heat(frac))("█".repeat(w)), - fg(idx(237))("·".repeat(Math.max(0, barW - w))), - bold(fg(idx(250))(lpad(`${p.pct.toFixed(1)}%`, PCTW))), - ]} + {` ${pad(p.comm, NAMEW)}`} + {"█".repeat(w)} + {"·".repeat(Math.max(0, barW - w))} + {lpad(`${p.pct.toFixed(1)}%`, PCTW)} ); }); diff --git a/template/src/components/titlebar.jsx b/template/src/components/titlebar.jsx index 39b0e5d..bc72f74 100644 --- a/template/src/components/titlebar.jsx +++ b/template/src/components/titlebar.jsx @@ -2,21 +2,21 @@ // min-slice knob value (what +/- patches into the kernel). A one-row Box // tinted as the rail via the container's own bg — full width, no fragile // space-fill. -import { Box, Text, bold, fg, idx } from "yeet:tui"; +import { Box, Text, idx } from "yeet:tui"; import { fmtDuration, fmtRate } from "@/lib/format.js"; const RAIL = idx(235); +const sep = () => {" ▏ "}; export default ({ cpus, minSlice }) => ( {() => { const total = cpus.get().reduce((s, c) => s + c.rate, 0); - const sep = fg(idx(240))(" ▏ "); return [ - bold(fg(idx(214))(" ● cpusched ")), sep, - bold(`${fmtRate(total)}`), fg(idx(245))(" switches/s"), sep, - fg(idx(245))("min-slice ≥ "), bold(fmtDuration(minSlice.get() * 1000)), fg(idx(240))(" [ +/- ]"), + {" ● cpusched "}, sep(), + {fmtRate(total)}, {" switches/s"}, sep(), + {"min-slice ≥ "}, {fmtDuration(minSlice.get() * 1000)}, {" [ +/- ]"}, ]; }} diff --git a/template/src/main.jsx b/template/src/main.jsx index c76ee08..edfe702 100644 --- a/template/src/main.jsx +++ b/template/src/main.jsx @@ -16,7 +16,7 @@ * * To ship a pure-JS script, delete src/bpf/, bin/, and probes/cpusched.js. */ -import { Box, Text, fg, idx, mount, signal } from "yeet:tui"; +import { Box, Text, idx, mount, signal } from "yeet:tui"; import { numCpus } from "@/probes/probe.js"; import { cpus, minSlice, procs, setMinSlice } from "@/probes/cpusched.js"; import { latency } from "@/probes/runqlat.js"; @@ -52,7 +52,7 @@ tty.on?.("mousedown", (e) => { if (i >= 0 && i < numCpus) selected.set(i); }); -const Rule = () => {fg(idx(238))("─".repeat(400))}; +const Rule = () => {"─".repeat(400)}; // One layout cell → its component, filled with the extents the layout chose. const cell = (c) => {