These are the human owner's stated decisions, recorded to prevent drift during self-iteration. Don't quietly drift past them; if a proposed change would cross one of these boundaries, stop and ask first.
Vouching is per-decision: a heading carrying [VOUCHED @hyf0] is settled — reopen
only on new evidence, a changed constraint, or @hyf0's explicit say-so. Unstamped
decisions are AI-recorded on the owner's behalf — followed, but challengeable.
See also: architecture.md for the how.
machud's whole reason to exist is the opposite of btop. btop wins on deep configurability; machud wins on beautiful + zero-config. A curated layout that looks good on first launch beats any settings system.
- Do: pick sensible defaults, make them look great, ship them.
- Don't: add config files, layout customization, per-user widget arrangement, or a "focus/expand" interaction mode. The single curated dashboard IS the product.
Open-the-box means no password prompt on startup, ever. Default data comes only
from unprivileged commands (sysctl, vm_stat, ioreg, pmset, netstat, df,
ps) + Node os. Anything needing sudo (powermetrics: precise per-cluster freq,
GPU/ANE watts, fan RPM, die temps) is shown as —, never blocks, never prompts.
- Do: keep the path sudo-free, period. There is no sudo mode — not even an
opt-in
--enhancedone. Never requesting a password is part of machud's identity (owner ruling 2026-06-20). Metrics that would needsudo/powermetrics (precise per-cluster freq, GPU/ANE watts, fan RPM, die temps, total system power) stay—. - Don't: make the experience depend on elevated privileges, or add any code path —
default or flagged — that shells
sudo. A privileged helper is exactly the kind of fragile, security-sensitive surface this project refuses (see CONTRIBUTING.md / D1). - Display (owner ruling 2026-06-21): a metric that is permanently sudo-only (e.g. fan RPM)
is omitted, not shown as a dead
— sudorow — it never carries information here, so it's just clutter. (A metric that's merely momentarily absent still shows—.) The UI never prints "sudo".
A monitor that crashes is worse than one showing —. sh() resolves "" on
failure, each collector returns safe nulls, collectAll() swaps any throwing
collector for its empty default.
- Don't: introduce a code path where one bad reading blanks or crashes the app.
Supersedes the old "wide-screen only, responsive deferred." @hyf0 reopened this (exactly the "reopen only with @hyf0" path the old decision reserved). machud now adapts to the terminal viewport size automatically, with no config or switch. Still zero-config, still one curated experience; it just fits the window. So it stays compatible with D1.
- Scope = TWO tiers, not five. A wide curated default (the 3-tier hierarchy in DESIGN.md) + ONE narrow/watch-face single-column fallback, with a single tested breakpoint. The full 5-breakpoint × per-panel-S/M/L ladder is rejected as a scope bomb for the hands-off loop.
- Build it LAST, after the visual redesign (RD5), and gate it on the verify width assertions.
- Mind the width seam:
useLayoutSize()exposes Runtime's reactive root-layout width. The--once/verify path has no TTY width, so the gate drivesCOLUMNSinto bothrenderToString({ width })and App's snapshot prop. Any responsivev-ifmust read that same width, so a breakpoint asserted atCOLUMNS=120/40is the width the code actually branches on. - Don't: let the loop self-grant responsive (it was a standing-anchor conflict — this reopen is the owner ruling that unblocks it). Don't add breakpoints the gate can't render-test.
Target is feature parity with Stats' 9 modules, delivered in phases with quality per module (not all-at-once-half-baked).
- Done: CPU (P/E split), Memory, GPU, Disk, Network, Battery, Sensors, Clock (in header).
- Deferred: Bluetooth module, a standalone Clock module.
- Do: when adding a deferred module, follow the existing collector→panel pattern (one collector file, one panel, props-only panel).
Parsers are written against actual command output captured on a real Mac, not
assumed formats. The black-box verification path is node dist/main.mjs --once; component
panels also get pnpm test render coverage through @vue-tui/testing (see D20 / architecture.md).
- Do: before changing a collector's parsing, run the real command and read its
output. After any change, confirm with
--onceshowing plausible live values.
The live dashboard must own the whole screen — switch to the terminal's alternate screen buffer on launch (clean buffer, hidden cursor, no leftover shell prompt or scrollback), and restore the user's terminal exactly on quit. Inline-below-the-prompt rendering was explicitly rejected — it looked like leftover junk and broke the "polished, app-like" feel.
- How: mount with
{ mode: "fullscreen", exitOnCtrlC: true }, quit viauseApp().exit(), and awaitapp.waitUntilExit()so Runtime finishes restoration before the process ends. Signal teardown restores the terminal too. On non-TTY stdout, Runtime uses its supported document host;--oncebypasses mounting and renders synchronously. - Don't: go back to inline rendering, or exit a live session with a raw
process.exit()that leaves the terminal in the alternate buffer / cursor hidden. - Verify: capture raw bytes through a PTY (
script) and confirm both\x1b[?1049h(enter) and\x1b[?1049l(exit) plus balanced cursor hide/show.
Owner ruling 2026-08-02: machud uses the palette shown in the current product screenshot for every session. It does not inspect macOS appearance, select a theme mode, or paint its own terminal background. The owner observed that this foreground palette works on both light and dark terminal backgrounds, so parallel palettes add product and implementation surface without adding value.
Appearance is not a system metric and is not carried in Metrics or --json. The runtime mirror is
one plain object in src/theme.ts; DESIGN.md records the same fixed tokens and pnpm verify pins
their complete key/value map.
machud's look is cool but refined: drama comes from form (gradient meters, 2×4 braille graphs,
big block numbers) and a confined Vivid Curated identity layer. The fixed palette uses green for CPU
and the hud brand mark, magenta for GPU, electric violet for Memory, emerald/azure for Network, and
electric cyan for Power. Disk and Status stay neutral until they alarm; amber/red remains reserved
for events.
The terminal background is not part of the palette. The exact foreground tokens live in
/DESIGN.md, with src/theme.ts as their runtime mirror and a full-map pin in
scripts/verify.mjs.
The full spec lives in /DESIGN.md and is the hands-off aesthetic anchor
the loop optimizes against — a [VOUCHED]-level surface. The staged RD0–RD5 backlog shipped it
(2026-06-21): the code is now Everforest + the 3-tier layout, pinned by the verify gate.
- Do: make
src/theme.tsthe runtime mirror of the DESIGN.md tokens, pinned in verify.mjs. - Don't: drift the look, or treat unbuilt DESIGN.md prose as a passing invariant.
Owner ruling 2026-06-20, recorded in /CONTRIBUTING.md. Because machud
is opinionated (D1), the contribution surface is intentionally narrow: bug/compat reports
welcome; ideas as discussion-first issues; feature PRs opened without a prior agreed issue are
closed directly; theming / feature / aesthetic changes are generally not accepted.
The gradient identity assumes 24-bit color, but macOS's default Terminal.app is 256-color. Without a fallback, same-hue ramps flatten to bands and muted hexes snap to saturated basic ANSI (the neon the design forbids). machud must detect the terminal color level and degrade: truecolor → full gradients; 256 → a single solid accent; 16 → muted named ANSI. Judge the look through the real render path at level 2/1, not raw truecolor.
Owner ruling 2026-06-20. The NETWORK panel drops the LAN IP (previously shown) and shows interface name + rates instead. Rationale: a LAN IP is low glance-value and machud is built to be screenshotted/shared, so any network identifier is needless exposure. This is an explicit, logged waiver of DoD rule 3 (a previously-live metric going absent), not a silent regression.
Owner ruling 2026-06-20. The common usage is npx machud — run-on-the-fly, no install. That
makes the first five seconds the whole product and hard-reinforces the rest: zero-config (D1);
never prompt for a password (D2 — a npx tool asking for root is a trust-killer); and the
truecolor color-tier fallback (D11) is mandatory, because npx users are on whatever terminal
they have, including macOS's default Terminal.app (256-color).
- Do: keep the published package runnable via
npx machud— un-private, a shebang on the bin, declaredfiles/engines, and a lean runtime dep tree for fast cold start. A verify assertion must confirm the built bin actually launches (don't ship a package that can't run). - Don't: add a required install/build step, a heavy runtime dependency, or anything that makes
the first
npx machudslow, broken, or privilege-prompting.
Owner ruling 2026-06-20. machud does not pursue accessibility. For a passive, full-screen TUI that takes over the terminal (D7), the realistic a11y surface is only colour-independence (screen-reader and keyboard a11y don't apply to a non-interactive alt-screen app), and the owner decided even that isn't worth it. So status is conveyed by colour (good/warn/bad) — no mandatory non-hue glyph, no colour-blind redundant-encoding requirement.
- Do: use colour freely for status; a text label (e.g. disk "FULL") is fine where it reads well, but it's a UX choice, not an a11y obligation.
- Don't: add
○/◐/●-style status glyphs, screen-reader hints, or other a11y scaffolding "to be safe." This is a deliberate, settled non-goal — reopen only with @hyf0. - Note:
⇡/⇣(charge direction) and—(unavailable) stay — they carry real information and replace a double-width emoji; they are not a11y features.
Owner ruling 2026-06-20. The top-left logo splits the word: mac in Apple aluminium/silver
(the silver token — #8d939a) and hud in the brand green (the accent/cpu token —
#8da101), surfacing the "a HUD for the Mac" reading.
- Settled: the split (
mac|hud), the silvermac, and the greenhud. Reopen only with @hyf0. Mirrored inHeaderBar.vue(theme.silver+theme.accent, both bold) and the DESIGN.mdsilvertoken; an owner-directed extension of the D9 palette. - Don't: flatten the wordmark to one colour, swap which half is silver vs green, or drop the
silvertoken.
Owner ruling 2026-08-02: machud has no theme key, theme configuration, appearance auto-detection, or
preview override. q remains the only input advertised by the dashboard; the footer describes
refresh timing but offers no appearance control. This keeps the product aligned with D1 and the
single fixed-palette decision in D8.
Historical implementation record. The decision to keep a component render layer remains; its Vite+ 0.1 / Runtime 0.1 constraints were superseded by D20 on 2026-08-02.
Owner asked (2026-06-21) to borrow vue-tui's verification layer. Two things shipped:
vp testun-broken. It failed because the catalog used@latest, pulling vite-plus-core/CLI 0.2.1 while vite-plus-test has no 0.2.x (maxes at 0.1.24) — the split breaks the test bin ("Could not find 'vitest' bin entry"). Fixed the way vue-tui structurally avoids skew: pin the whole toolchain to one matched line inpnpm-workspace.yaml(vite→core@0.1.24,vitest→test@0.1.24,vite-plus@0.1.24). Bump all three together when test ships a newer release.vp buildstill passes on this line.- Component render tests (
tests/*.test.ts): each panel is rendered with injected metrics through the runtime's synchronousrenderToString(component, {columns})— the same call--onceuses — and asserted on (title, headline value, degradation to—, narrow view).
Why renderToString, not @vue-tui/testing's render()/lastFrame() (vue-tui's own harness):
the published @vue-tui/testing@0.0.3 hard-pins @vue-tui/runtime@0.0.3, but machud runs runtime
0.1.0 — a mismatch with no matched testing release on npm. renderToString is exported by the
runtime itself, so it matches 0.1.0 and needs no harness. Panels are pure props→frame; the only
interactive surface (t/q) is already covered by the PTY checks in scripts/verify.mjs.
Config (vite.config.ts, defineConfig from vite-plus): FORCE_COLOR:"3" + CI:"false"
(keep chalk colour + vue-tui interactivity on) and environment:"happy-dom". Vitest defaults to the
node/SSR transform, which makes @vitejs/plugin-vue emit ssrRender; a browser-like env flips it to
the CLIENT render fn that renderToString needs (vue-tui sidesteps this by testing in JSX, never
importing SFCs — machud tests real .vue panels). pnpm verify runs vp test as its first step,
so the layer is enforced by the single gate, not a side command.
Owner ruling 2026-07-05; amended by owner direction 2026-07-06. The wide dashboard's durable reading path is:
Top: CPU | GPU | MEMORY
Middle: NETWORK | POWER | DISK
Bottom: STATUS
Why: the first row answers "what pressure is shaping the machine right now?" CPU opens the row on the left; GPU sits in the middle so the two compute-pressure channels can be compared directly; Memory moves to the far right. CPU now carries the largest panel width, Memory is second, and GPU remains smallest. GPU is allowed to be a first-tier hero with a large percentage, but it must not pretend to have CPU-style process attribution; it earns the space with current utilization, history/trend, renderer load, VRAM, and compact avg/peak context.
Tier-1 spatial weight is CPU > Memory > GPU, using the old 7/2/1 weight ladder reassigned to the new reading path. CPU must visually lead by width on the left; Memory is second on the right; GPU may be narrower because it does not have reliable zero-sudo per-process attribution.
The second row answers "which moving resources need attention?" Network owns the left slot because its rates change most frequently and deserve the highest scan position in that row. Power now sits before Disk (amended 2026-07-05): with the Vivid Curated palette, Power carries an electric-cyan identity (its story is 电) and becomes the second-row hero, while Disk trails as the low-drama capacity readout. Power remains visible as a compact environment box, but danger states override position: Disk full or low battery must escalate through colour/intensity/text without moving panels.
The bottom row is STATUS, not SENSORS. It summarizes health signals such as thermal pressure,
CPU speed cap, and battery pack temperature. The title must not imply unavailable full sensor coverage
(CPU/GPU die temps, fan RPM), which remain omitted under D2.
- Colour grammar: warm colours (yellow/orange/red) are event-only, not module identity. Disk may become warm when near-full/full; Power may become warm on low battery / Low Power Mode; Status may become warm on thermal pressure. Roomy Disk and nominal Status stay neutral; Power now carries an electric-cyan identity full-time (still escalating to amber/red when low). Vivid Curated hues (D9 amend 2026-07-06): CPU green, GPU magenta, Memory electric violet, Power electric cyan, Network emerald ▼ / azure ▲ (stacked ▲ up over ▼ down).
- Do: implement and verify this order in
App.vue,DESIGN.md, component tests, andscripts/verify.mjsbefore calling RD6 complete. - Don't: return GPU to a Network peer, put Disk before Power in the default row order, move Memory
away from the top-row right slot, or label the
health summary
SENSORSwithout an explicit owner reversal.
Owner ruling 2026-07-06. On a large, very tall, or oddly-shaped terminal the dashboard used to sit top-left at its natural size and full terminal width, so all the leftover space collected at the bottom as one dead gap (and on ultra-wide terminals the panels stretched into sparse, half-empty boxes). machud now frames the dashboard: it caps the content width (~128 columns) and centres the block in the viewport — horizontally always, vertically whenever the terminal is taller than the content. The leftover space becomes balanced margin around a composed block instead of a gap dumped at the bottom.
This is compatible with D1 (not configurable) because it is automatic and zero-config — the single curated dashboard just fits the window.
- Rejected — stretch panels to fill the height (btop-style vertical
flexGrow). machud's panels are curated (a hero number + a short graph + a 3-item list), not dense or scrollable. Growing them vertically only inflates the empty space inside each panel — a big number floating in a big box — which looks worse, not better. btop can stretch because its panels are dense; machud's are not. - Rejected — do nothing. Defensible for a generic monitor, but machud's whole pitch is "looks right out of the box" (D1), so a dead gap / stretched sparse panels undercut the product.
- Implementation:
App.vue's outer<Box>fills the terminal (columns×rows) and centres the inner, width-capped dashboard.MAX_CONTENT_WIDTH(128) is the single tunable knob. Vertical centring engages only when the viewport is taller than the content's (upper-bound) height, so a short terminal keeps the top-anchored layout and the header is never clipped. Height is threaded to the one-shot render via arowsprop (likecolumns), so the piped--oncesnapshot stays top-anchored (unknown height) while the gate can driveLINESto test centring deterministically. - Verify: width caps at
COLUMNS=200; a tall viewport (LINES=45) centres (blank rows above the header); a short viewport (LINES=30) stays top-anchored (never clips); and the real content height is pinned as an upper bound so a panel growing past it turns the gate red before it could clip. - Note (unstamped): owner-directed and recorded, but not
[VOUCHED]— challengeable until @hyf0 stamps it. The~128cap width is a taste knob, easy to tune.
The 2026-08-02 migration adopts the current templates/vite boundary while preserving machud's
product and verification contracts:
vite.config.tsis development-only:unplugin-vue/vitecompiles SFCs and@vue-tui/vitesupplies terminal HMR.tsdown.config.tsis production-only:unplugin-vue/rolldowncompiles SFCs and bundles the Node application plus dependencies into executabledist/main.mjs. Bundled libraries stay indevDependencies; generated third-party notices ship beside the bin.vitest.config.tsloads only the Vue transform. Component tests mount real SFCs with matched@vue-tui/testing@0.3.0; no DOM environment, Vite+ compatibility pin, or synchronous-render workaround remains.- Runtime 0.3 owns fullscreen lifecycle, tagged input events, root layout size, explicit string-render dimensions, and the legal terminal-color type. machud uses those contracts directly rather than retaining 0.1 compatibility branches.
pnpm verifyremains the authoritative strengthen-only gate and tests live resize, Ctrl+C restoration, the standalone file in an empty directory, and the installed tarball.
This supersedes D17's implementation constraints, not its reason for having component tests.