| meta |
|
||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| colors |
|
||||||||||||||||||||||||||||||||||
| color_tier |
|
||||||||||||||||||||||||||||||||||
| glyphs |
|
||||||||||||||||||||||||||||||||||
| space |
|
||||||||||||||||||||||||||||||||||
| weight |
|
||||||||||||||||||||||||||||||||||
| components |
|
What machud should look and feel like, written down so it can be built and judged
without re-litigating taste every time. Correctness is machine-checked by pnpm verify;
this document is the anchor for the part a gate can't fully check — the beautiful in
"beautiful, zero-config." Changing anything here is a [VOUCHED]-level decision: ship nothing
that contradicts this file without the owner's say-so.
Mostly SHIPPED. The staged RD0–RD6 redesign has landed — theme.ts carries the fixed Vivid Curated palette and the D18 layout is the shipped 3-tier hierarchy. A few per-module details may still be TARGET; until a section is tagged SHIPPED, do not treat its prose as a passing invariant.
A calm, green-forward instrument that is cool, not loud. machud takes over the terminal like btop, but its visual language is refined maximalism: rich, alive, and striking — through gradient meters, high-resolution braille graphs, and big hero numbers — yet muted, low-contrast, and easy on the eyes. Cool is the goal; harsh is the failure mode.
Drama comes from FORM first — then a bold, confined colour layer (D9 amended 2026-07-05, Vivid Curated). When a frame feels flat, add data density, hierarchy, or resolution before reaching for colour; the vivid identity hues are confined accents, never a flood. machud's identity is its composition (hero block number + gradient meter + braille graph + P/E grid), not its palette; Everforest is a proven low-strain base, not a brand statement.
The seven principles are the whole spec in miniature.
- Glanceable. Every panel answers its core question in under a second — one hero metric, BIG, top-left, in a preattentive channel (size + position + accent). (Few, "5-second rule"; Ware, preattentive processing.)
- Color = identity + state, on DIFFERENT channels. Hue marks which module only in confined accents (CPU green; GPU magenta; Memory electric violet; Power electric cyan; Network emerald ▼ / azure ▲). State is carried by colour + intensity only when earned (amber/red for warning/bad; a bar bleeding toward warn-red), plus a text label where it helps (e.g. disk "FULL"). No accessibility / colour-independence layer — machud is a passive full-screen TUI (not screen-reader territory), and the owner ruled a11y out of scope (D14). (Few; Ware, preattentive attributes.)
- Cool by default, dramatic on alarm. Baseline is already handsome (neutral text + gradient meters + braille graphs + easing on value change). Healthy = quiet. Only an event (near-full, low battery / Low Power Mode, thermal pressure) earns escalation: brightness, the bar bleeding to warn/bad, and — only here — motion. Motion is alarm/transition-only, never a routine-state carrier (and it is invisible to a single-frame gate, so it is not a load-bearing accessibility channel). (Weiser, Calm Technology; Tufte, "smallest effective difference".)
- Space by value. Real estate ∝ worth, not democratic. CPU / GPU / Memory form the tier-1 pressure row; Network leads the moving-resource tier; Power sits before Disk; Status summarizes health below. (Few.)
- Small multiples + sparklines. Per-core load is a P/E-grouped grid the eye compares at a glance; history is a braille area graph. Maximize data-ink; delete chartjunk. (Tufte.)
- Consistency. Every panel's hero number lives in the same spot, same weight ramp; panels and bars align. Same things look the same. (Gestalt: similarity, alignment.)
- Stable & dense (owner feedback 2026-06-20). A panel's row structure is FIXED — an
optional value fills its slot with
—/on AC, it never appears/disappears (a value popping in or out must NOT change a panel's height or shift the row below it). Numeric columns right-align to a fixed width so a value's length never moves the column. And a large/hero panel must earn its space with real, dynamic, comparative data (history graphs, per-core grids, breakdowns) — a big box holding three numbers is a failure, not minimalism. - Mac-native, honest data. Show the zero-sudo Apple-Silicon signals others can't (P/E
cores, adapter PD wattage, memory pressure, thermal pressure). What needs
sudois—, never faked, never prompted. (Tufte "show the data"; decisions D2/D3.)
AMENDED 2026-07-05 — Vivid Curated (D9, [VOUCHED @hyf0]); slots rotated 2026-07-06. The "muted through colour" rule is superseded. Neutral text and frames support the module identity layer is a bold, saturated, validated palette — the original green (CPU), magenta (GPU), electric violet (Memory), emerald ▼ / azure ▲ (Network), electric cyan (Power, 电). Warm (amber/red) stays event-only; Disk & Status stay neutral until they alarm. The
colorstokens above are the authoritative hex (mirrored in theme.ts, pinned by verify). Read the older "muted / never raise chroma / smallest effective difference" lines in this section as historical rationale, not live rules.
The ground is the user's terminal background; machud never paints or switches it. The fixed
foreground palette above is the screenshot palette selected by the owner because it remains readable
on both light and dark terminal backgrounds. Its primary accent is Everforest green (#8da101), and
green-forward remains the identity. src/theme.ts mirrors these tokens, and verify.mjs pins the full
key/value map so the spec and runtime cannot silently diverge.
Rules (Refactoring UI, Few, Tufte's smallest effective difference):
- Never paint a background. Preserve the user's terminal surface; use
#5c6a72for strong neutral text. - Vivid Curated hierarchy (D9 amended 2026-07-06): CPU (left-top entry point) carries the original Everforest green in title, hero number, and graph; GPU hot magenta; Memory electric violet; Network emerald ▼ download / azure ▲ upload (stacked ▲ up over ▼ down); Power electric cyan (电). Disk and Status stay neutral until they alarm (Disk is the low-drama one — it earns nothing from a permanent hue). Warm (amber/red) escalation is event-only. Identity hues live only on title / hero / graph / meter — they identify panels, never flood the neutral data field.
- Warm colours are event-only. Yellow/orange/red communicate warning/bad states (disk near-full, low battery, Low Power Mode, thermal pressure), never ordinary module identity. A normal charged Power panel must not be orange; a normal Disk panel must not be yellow.
- Per-module hue is confined to small ink — primarily a panel title and supporting labels where they clarify identity. D18 keeps CPU as the left-side green anchor, GPU as the middle magenta compute peer, and Memory as the far-right violet anchor; warm hues are state events only. Panel bodies stay mostly neutral grey + the shared green accent, so the 7 module hues never become a rainbow. The "loud 10%" of 60-30-10 is this confined accent layer, nothing more.
- Low contrast on purpose. Use the weakest distinction that still reads.
- No high-saturation complementary pairs on dark (they vibrate — chromostereopsis). Gradients ramp within one hue's luminance.
- Truecolor is an ENHANCEMENT, not a guarantee (D11). The gradient look needs 24-bit color; macOS's default Terminal.app is 256-color. Detect chalk level and degrade: 256 → solid accent (no gradient), 16 → muted named ANSI. Never let a muted hex snap to a saturated basic color (the neon the Don'ts forbid). Judge "cool but refined" through the real chalk path at level 2/1, not the raw-truecolor prototype.
- One foreground palette on every terminal background (D8/D16). Do not infer macOS appearance, add a theme key, or maintain parallel light/dark token sets.
A fixed-width grid has no fonts, so the character set and ANSI weight are the typography.
- Round borders (
╭─╮) — calm; heavy/double frames read as loud, not the default. - Hierarchy by size → position → weight → color. Hero number is a 5-row block figure;
secondary values are normal weight and
dim. - Gradients are same-hue luminance ramps — gentle, never a hue clash.
⇡ / ⇣for charge direction (width-1; never⚡, a double-width emoji that breaks alignment and tofus on glyph-poor terminals).—= unavailable.
A single curated wide layout (D1: no config, no focus/expand) arranged as a 3-tier hierarchy — important up top, minor compressed below:
- Tier 1 — compute-pressure heroes: CPU, GPU, Memory. CPU opens the row on the left, GPU sits in the middle for direct compute-pressure comparison, and Memory anchors the far right. CPU now carries the largest panel width; Memory keeps the second-largest width; GPU remains the smallest first-tier panel. GPU gets a large percentage, but earns first-tier space through history/trend, renderer load, VRAM, and compact avg/peak context — not fake CPU-style process attribution. Tier-1 widths follow the old 7/2/1 weight ladder reassigned as CPU > Memory > GPU. Memory and CPU both earn their width with process lists; GPU has no reliable zero-sudo process attribution, so it should not occupy peer width.
- Tier 2 — moving resources: Network, Power, Disk. Network owns the left slot because it changes most frequently. Power comes before Disk (amended 2026-07-05): its electric-cyan identity (电) makes it the second-row hero, while Disk trails as the low-drama capacity readout. Position is stable; danger states escalate through colour/intensity/text, not by moving panels.
- Tier 3 — health summary: Status.
This replaces
SENSORS: the panel shows thermal pressure / CPU cap / battery pack temperature, not full sensor coverage such as die temps or fan RPM.
╭──────────── CPU ────────────╮ ╭──── GPU ────╮ ╭──────── MEMORY ───────╮
╰─────────────────────────────╯ ╰─────────────╯ ╰───────────────────────╯
╭──────────── NETWORK ────────────╮ ╭──── POWER ────╮ ╭── DISK ──╮
╰─────────────────────────────────╯ ╰───────────────╯ ╰──────────╯
╭──────────────────────────── STATUS ────────────────────────────╮
╰────────────────────────────────────────────────────────────────╯
- 1-cell gutter inside every panel; 1-cell gap between panels.
- Framed, not full-bleed (D19): the dashboard caps its content width (~128 cols) and centres in the viewport — horizontally always, vertically when the terminal is taller than the content. Leftover space on a large or oddly-shaped terminal becomes balanced margin (a composed block), never a dead gap dumped at the bottom or full-width-stretched sparse panels. Panels keep their curated size; they are deliberately NOT grown to fill (that only inflates the empty space inside each panel).
- Placement invariants: verify must assert the D18 order: CPU before GPU before Memory in tier 1; Network before Power before Disk in tier 2; Status below them. The old shared ~60% divider and Battery-third-row-left invariant were intermediate RD6 slices and no longer describe the target.
- Alignment is non-negotiable (in the wide layout, where bars render): every bar in a panel shares one label-column width and one bar width, so left edges, bar ends, and value columns form clean vertical rules. Pad labels to a fixed column; never let a label's length decide where its bar starts. (Machine-checked by verify.mjs RD0b.)
- Depth from spacing and structure — a dim frame and one-cell gutters separate panels without painting over the terminal background.
Scoped to TWO tiers, not a 5-breakpoint ladder (that is a scope bomb for an unattended loop):
- Wide (default): the full D18 3-tier hierarchy above.
- Narrow / watch-face: a single-column fallback; at the smallest size, just the hero numbers (CPU% / GPU% / MEM%, plus compact resource rows as width allows). The hierarchy IS the degradation order — the least-important tier drops first, hero last. (Marcotte responsive grids; Wroblewski mobile-first; Walton content choreography.)
- Auto-adapt to viewport — not user config, still zero-config (D1).
- Width-seam contract:
useLayoutSize()exposes Runtime's reactive root layout, andApp.vuebinds that width into the responsive branch. The--once/verify path has no TTY width, so the gate drivesCOLUMNSinto bothrenderToString({ width })and the snapshot prop; the breakpoint asserted at COLUMNS=40/120 is the one the code branches on. - Gate assertions (RD0b/RD5/D18): widest visible line ≤ COLUMNS at each width; hero present at wide / absent at watch-face; D18 order at wide; narrow labeled rows at COLUMNS=40.
-
CPU — tier-1 compute hero (adjacent to GPU — must be DENSE, not boring). Fill the hero space with: the
BigNumberoverall %; a tall braille area history graph (flowing, the btop look); the per-core grid — each of the 12 cores its own mini-bar, P and E clusters grouped + labelled, coloured by load (the small-multiples + the per-core data); per-cluster averages; and a supporting line (top CPU process / load avg). Apple-Silicon only for the P/E split: detect cluster count viahw.nperflevels(Intel = 1) —cpu.tsreadshw.perflevel0/1.logicalcpuand treats a missingperflevel1aseCount=0; on a single cluster render ONE unlabelled cluster, never0P+0Eor all-P. Frequency needssudo→ omitted. (Two short bars + a number in a big box is the failure mode Principle 8 forbids.)Same lens for the others (owner: 举一反三): MEM → a wired/compressed/app/cache breakdown bar
- history graph + a fixed-height top-process list; GPU → util + a history graph (half-empty
today); DISK → R/W I/O history sparkline; POWER → the
powerrow always present (—/on ACoff-discharge — this fixes the height jump) + optional charge history; STATUS → compress (sudo took its content) or add a thermal trend. Every panel: fixed rows, fixed-width numeric columns.
- history graph + a fixed-height top-process list; GPU → util + a history graph (half-empty
today); DISK → R/W I/O history sparkline; POWER → the
-
MEM — tier-1 right hero. Used % + swap, plus the real macOS memory-pressure level from
sysctl kern.memorystatus_vm_pressure_level(1→Normal / 2→Elevated / 4→High) — the Mac-native truth Activity Monitor leads with. (The current usedPct heuristic is only a fallback when the sysctl is empty.) Top processes by RSS as support. -
NETWORK — tier-2 left lead. Adaptive human units (B/KB/MB/GB·s); ▼ down / ▲ up rates with sparklines; interface name. No IP address — recorded waiver (D12): a LAN IP is low-value and leaks in the screenshots machud is built to be; show interface + rates instead.
-
GPU — tier-1 compute hero. Utilization % as a large number + history/trend + renderer load + VRAM + compact avg/peak context. GPU has no reliable zero-sudo per-process attribution; do not invent one. Its first-tier evidence is saturation over time, not "which process caused it."
-
POWER — tier-2 middle compact box (Battery-facing Mac-exclusive data highlight). Charge % +
⇡/⇣charge-state glyph + health % + cycles. Differentiators: Low Power Mode frompmset -g custom; zero-sudo system watts estimate fromAppleSmartBattery.PowerTelemetryData; adapter max wattage, detected LIVE, never hardcoded (AdapterDetails.Watts; varies by cable; show only whenExternalConnected, else—) + real-time charge power =Voltage(mV) × Amperage(mA) / 1e6W. Unsigned-int trap: ioreg returnsAmperageas an unsigned 64-bit value, so reinterpret as signed (a = raw >= 2**63 ? raw - 2**64 : raw) before the sign test — only thena < 0= discharging. (battery.ts's(-?\d+)parse does NOT handle this — RD2 fixes it and injects the wraparound value e.g.18446744073709551179= −437 mA, via RD0c's injection mechanism, so the gate proves the reinterpretation.) When|a| ≈ 0while charged, show "charged", not "0W". Charge wattage is battery flow, whilePowerTelemetryDatasystem watts is a useful estimate of whole-system draw, not powermetrics-grade per-rail power. Verify can't see on-battery/charging transitions on one host → fixture-tested in RD2 (via RD0c's mechanism) + manual review of the sign path. -
DISK — tier-2 right. Used % + free, compact. A state signifier that is earned: neutral when roomy;
levelColor-driven bar + aNEAR FULL/FULLtext token at ≥85% / ≥95%. Normal roomy Disk must not borrow the CPU/brand green. -
STATUS — tier-3 health summary.
pmsetthermal pressure (neutral when nominal; warn/bad when pressure rises) + battery pack temp. Die temps / fan RPM needsudo→ omitted (D2).
Do
- Make one number the obvious hero of each panel.
- Align every bar in a panel to a shared label column + bar width (clean vertical rules).
- Use gradient meters / braille graphs that encode data; degrade gradients to a solid accent below truecolor.
- Detect adapter wattage, units, core counts, terminal color level, and viewport width live.
- Show
—when data honestly isn't available zero-sudo; record an explicit waiver before dropping a previously-live metric (e.g. the LAN IP, D12).
Don't
- Don't fix "flat" with saturation or contrast — add form/data/hierarchy instead.
- Don't use the
⚡emoji, pure black/white, or complementary hue clashes. - Don't spend per-module hue on panel bodies — confine it to title/border/hero number.
- Don't treat motion as a routine-state channel (alarm/transition only).
- Don't ask for
sudo, ever — not even opt-in (D2). Don't give Disk/Status hero space. - Don't add theme switching or infer macOS appearance; the terminal owns its background.
- Tufte, Visual Display / Envisioning Information — data-ink, sparklines, small multiples, smallest effective difference.
- Few, Information Dashboard Design — glanceability, muted-by-default, color as scarce.
- Ware, Information Visualization — preattentive attributes; chromostereopsis; redundant encoding.
- Wathan & Schoger, Refactoring UI — no pure black/white, reduce saturation, accent sparingly.
- Rams, Ten Principles — "Less, but better"; design is unobtrusive.
- Weiser, Calm Technology — inform from the periphery; demand attention only on events.
- Marcotte Responsive Web Design / Wroblewski Mobile First / Walton Content Choreography — the responsive model.
- Everforest / Nord / Solarized palette methodologies — engineered low-eye-strain color.
- 60-30-10 — keep the loud 10% actually 10%.