Every widget of the base pack is its own crate (xtop-widget-<name>),
sharing the engine modules in xtop-widget-core (see README.md for the
layout). Both packs draw inside a standard frame: title, Borders::ALL,
the border set resolved from the per-widget border configuration through the
canonical xtop_widget_api::glyph::border_for mapping, and the theme's
fg/bg colors. With the default config (WidgetBorders::Native) every widget
uses ratatui's standard single-line box-drawing frame (border::PLAIN) —
the per-widget border look is a configuration choice, not a pack choice.
Every layout node that names a widget may carry an options JSON object. Two
keys are recognized by every widget of both packs, with a fixed
precedence — layout-node option > style-config value > contract default:
| Key | Values | Effect |
|---|---|---|
charset |
"braille" | "dot" | "block" | "half_block" | "bar" |
Chart glyphs of the widget (contract enum serde names). When the node sets nothing, the style config (state.charset(widget), global or per-widget style.widgets.<name>) wins; the config default is braille. |
borders |
"native" | "rounded" | "double" | "plain" | "ascii" |
The widget frame border set. When the node sets nothing, the style config (state.borders(widget)) wins; the config default is native. |
The resolution runs once per widget render in the shared helper
(xtop-widget-core/src/util.rs
resolved_charset/resolved_borders, consumed by every widget crate and by
the blocks pack). A malformed or unknown option value is ignored — the
config value then applies.
The tables below are the complete list of recognized keys. Rules:
- Unknown keys and malformed values are ignored (the documented default applies); a widget never breaks rendering on a bad option.
- When an option filters items (cores, interfaces, mounts) and nothing matches, the widget falls back to all items so it never goes blank.
- All colors come from the theme palette through the canonical
xtop_widget_api::glyph::to_color; palette indices are semantic roles (DR-UX3). The role table lives at the top ofxtop-widget-core/src/util.rsand mirrors the kernel'sdocs/customization.mdrole table: 0 bg, 1 alert, 2 good, 3 warn, 4 read/download (RX), 5 write/upload (TX), 6 accent, 7 fg, 8 dim/separators, 9–15 the multi-series ramp.
| Key | Values | Default | Effect |
|---|---|---|---|
chart |
"average" | "per-core" |
"average" |
History area: the machine-wide average, or the per-core view — each column takes the color of the shown core that peaks there, from the theme's series ramp (slots 9..15 cycling). |
cores |
"all" | subset spec |
"all" |
Restrict the shown cores: "0,2,4-7" (ids/ranges, ascending). Applies to the core rows and the per-core chart. |
show_freq |
bool |
false |
Append the per-core frequency (dim, right-aligned, 2.40GHz) whenever a shown core reports one. The model carries CpuInfo.frequency (MHz); rows degrade gracefully when all frequencies are 0. |
show_temp |
bool | "auto" |
"auto" |
Per-core temperature cell (47°, rightmost, bold) and the per-core heat braille mark: "auto" shows them whenever the snapshot carries a per-core temperature (CpuInfo.temp_c, Linux); true forces them on, false hides them. Temperatures are never fabricated: with no Some temperature anywhere the cells stay hidden even under true. |
Rendering (UX9.5): one normalized row per core — label cell (fixed
width), percent cell (right-aligned, role-colored), optional frequency
cell (dim), then the gradient bar filling the remainder of the column, and
on the right the per-core heat mark (one braille/block glyph whose
height is the temperature's share of the 80 °C anchor, colored by the heat
ramp) plus the optional temperature cell (47°, ramp-colored, rightmost)
when the row reports a temperature. Two columns are used only when each
column keeps its minimum row width (label + percent + bar ≥ 4 cells, plus
the extras when enabled); narrower areas fall back to one column. When both
extras would starve the bar, the temperature block yields first, then the
frequency. Rows that do not fit the height are clipped (the top cores win,
column-major order).
Title (UX9.5): the CPU model name (sanitized, truncated) is appended in
parentheses when the kernel reports one — CPU (AMD Ryzen 7 5800X …) — Max 48°C — together with the existing max-temperature suffix; without a model
the classic CPU (Max: 47.5°C) stays byte-identical. Long titles are cut
to the area width with ….
Unified usage+temp+power row (UX9.5): when the grid leaves at least two
rows, the first leftover row is a one-line composite gauge:
usage 42% ▂▄▆▆ temp 47° ▅▆▅▆ power 38.4W ▂▃▄▅ — word tokens with real
values interleaved with colored portions (the row is its own legend, so no
separate key is needed). Every portion is an honest share: usage of its
100% scale (gauge gradient), temp of the 80 °C hot anchor (heat ramp),
power of a documented 200 W display ceiling (gauge gradient, warn at 50 %,
alert at 90 % of the ceiling). Segments appear only for data that is
Some: the temp segment needs at least one per-core temperature (and the
show_temp display preference), the power segment the kernel's package
readout (SystemInfo.package_power_w — RAPL on Linux). With only usage
data the row is the classic average bar (Avg: NN% + gradient bar). When
no chartable history exists the numeric summary line trails the package
power (Avg: 42% Pkg 38.4W) so power is never hidden. The row never draws
empty garbage: no segments are fabricated.
History spec line (UX9.5): the anonymous bottom braille history row now
self-describes — its divider carries the dim label history: cpu % when
the width allows.
Below the rows the history area uses the chart engine (see "Chart engine"
below). When the engine cannot draw (area narrower than 12 columns, or
fewer than two history samples) the leftover row shows a compact numeric
summary instead of garbage: Avg: NN% (with the shown/total core counts
when a cores subset hides cores, and the Pkg readout above).
{ "chart": "per-core", "cores": "0,2,4-7", "show_freq": true, "show_temp": "auto" }| Key | Values | Default | Effect |
|---|---|---|---|
cpu |
"core" | "total" | "both" |
"core" |
CPU column basis. core = fraction of one logical core, cells like 12.5%. total = cpu_usage / logical_core_count() (the share of the whole machine's CPU), machine-share style: one decimal below 10 (0.7), an integer at/above 10 (34). both shows the two columns side by side (CPU per-core + CPU% total). The underlying per-process cpu_usage never changes. |
columns.memory |
bool |
true |
Show the Mem column (the column also drops automatically when the area is too narrow). |
columns.user |
bool |
true |
Show the User column (same auto-drop rule). |
columns.cmd |
bool |
true |
Show the Command column (same auto-drop rule). |
zebra |
bool |
true |
Alternate dim backgrounds on odd rows. |
Fixed layout (UX7.3 + UX9.4): PID (right-aligned, 7), Name (left-aligned,
flexible, truncated with …), the cpu spark (see below), CPU% (right,
6; two columns under both), Mem (right, 10), User (left, 9) and Command
(left, flexible) — separated by dim │. The Name and Command columns share
the row's flexible width (Name keeps at most 24 chars, Command the rest);
numeric columns are right-aligned; the header row is accent-bold; the sort
marker (▼/▲ from process_sort_desc) renders only in the sorted column
header cell. Columns drop right-to-left as the area narrows: Command, then
User, then Mem, then the total-basis CPU column, then the spark; below the
minimum only PID | CPU% remains. Rows are single logical lines — nothing
wraps or collides.
User names (UX9.4): the User column shows the resolved login name via
state.uid_to_name(uid) (the kernel reads /etc/passwd); when the kernel
has no mapping (unknown or non-local account) the numeric uid is shown —
names are a display mapping, never fabricated.
Command (UX9.4): each row shows the full command line — the kernel's
argument vector (cmd_full, joined) when populated, falling back to the
single cmd, then the executable path, then ? — next to the short
program name.
Cpu spark (UX9.4): a small per-row braille spark (4 cells, degrading to
2 on narrow rows) of the process's recent CPU samples
(state.process_cpu_history(pid), oldest → newest, one glyph per bucket,
bucket peaks preserved). Each cell is colored by usage through the heat
ramp — idle processes paint small low glyphs, hammering ones full ⣿
cells in the alert color. Braille charset paints braille glyphs
(⣀⣰⣶⣿), the block charsets the 8-level block ramp (the charset
option applies). An empty history (freshly seen pid) draws a dim ·
placeholder — samples are never fabricated.
Viewport scroll. The kernel's process_view() returns the full sorted
list (search-filtered); selection is anchored by PID. The widget derives the
selected index from the PID's position in that list and renders a window
around it — the selection is always visible, the window starts at row 0
while the selection is near the top, and jumps keep roughly half a screen of
context. Every process in the list is reachable with the existing up/down
keys; a dim scrollbar (accent thumb) appears at the right edge when the list
is longer than the area. No kernel change was needed (the kernel already
moves the selection over the full sorted list).
Search (kernel pre-filter): the query substring in the Name column is highlighted (accent background, bold). Selection keeps the accent row style; kill-confirmation highlighting would need a "pending kill" flag on the widget contract, which does not exist — flagged as a kernel-side dependency.
{ "cpu": "both", "columns": { "memory": true, "user": true, "cmd": true }, "zebra": true }| Key | Values | Default | Effect |
|---|---|---|---|
ifaces |
"all" | ["eth0", ...] |
"all" |
Which interfaces the rows and the aggregate lines cover. |
Rows are single logical lines (never wrapped). Width tiers (inner width):
>= 60 per-interface rows with name, activity bar (scaled to the fastest
visible interface, colored by its dominant direction), RX and TX rates in
their direction roles, and dim cumulative totals; >= 41 drops the totals;
>= 26 also drops the bar; below 26 the widget shows two aggregate lines
(RX rate tot bytes / TX …) over the selection, truncated with …. A dim
+N more hint replaces the tail when the interface list overflows the box.
The machine-wide RX/TX history chart (the contract only tracks one net history per direction, never per-interface) is drawn below the rows when the box is at least 16 columns wide and both histories carry ≥ 2 samples; it uses the chart engine with the fixed RX role (4) / TX role (5) coloring and consumes every leftover row. While the history is still empty the widget expands the interface rows to the full box and — when the list is short — appends aggregate live RX/TX lines so no dead rows sit between the content and the frame.
{ "ifaces": ["eth0", "wlan0"] }| Key | Values | Default | Effect |
|---|---|---|---|
disks |
"all" | ["/", "/boot"] |
"all" |
Which mounts to show (matched by exact mount_point). |
One row per mount, never wrapped. Wide rows (UX9.6) are
mount …bar… NN% used 50 GB · free 200 GB — the used AND free amounts
(free = total_space − used_space, honest: the kernel maps
used_space exactly that way); the free amount is colored by the used
gauge ramp (plenty of free space reads good, a nearly full disk reads
alert) and, on very wide rows, a free-share braille bar trails the
detail (height = the current free share, color = the same headroom ramp —
DiskInfo carries no capacity history, so there is no time series to
spark; the bar is an honest instantaneous braille readout). Below the wide
width the tiers degrade: mount NN% plus the bar (the label yields space
to the bar), below 11 columns plain mount NN% text. When the box gives
every mount at least two rows (height/n >= 2, height >= 4, width >= 18)
each mount renders as a meter block instead — mount line (label +
right-aligned percent + used/free amounts), then the U used bar, plus the
A available bar (DiskInfo.available_space, mirrored through the used
ramp inverted) when a third row exists — so the per-disk bars scale with
the box height. Only capacity metrics live here — per-device I/O speeds are
in disk_io (which has no capacity fields in the model, so the free
indicators belong to this widget only).
{ "disks": ["/", "/boot"] }| Key | Values | Default | Effect |
|---|---|---|---|
sections |
["memory", "available", "swap"] |
all three | Which meter rows to draw (RAM, then the available row, then swap). An empty/unknown list keeps all three. |
One meter row per section, never wrapped: label (bold), a right-aligned
percent cell (role-colored), then the gradient bar. Wide rows show the
used AND free amounts (UX9.6): RAM 50% ██████ used 8.0 GB · free 7.0 GB ⣿⣶⣰ — the free amount (kernel MemoryInfo.free for RAM, swap.free
for swap) is colored through the headroom ramp (good while there is plenty
of headroom, warn/alert as the machine tightens), and the RAM row trails a
braille spark of the free share over time derived honestly from the
kernel's used-percent history (free = 100 − used per sample; the kernel
only tracks the used percent). Each spark cell is colored by the scarcity
of the free share, so a cramping machine paints low red glyphs. The
available (AVL) row renders only when the snapshot can derive it
(MemoryInfo.available, nonzero total) — its bar fills with the
available share, colored by the same gauge ramp inverted
(gauge(100 − avail%)), so it turns alert-red when the machine runs out of
headroom; swap turns alert-red past the memory threshold. Below 10 inner
columns the whole widget collapses to one summary line
(RAM 50% AVL 50% SWP 13%). The RAM history chart is drawn below the rows
when the area is at least 14 columns wide and history has ≥ 2 samples; the
chart consumes every leftover row (see "Chart engine") — the chart is
the RAM history, never per-section.
{ "sections": ["memory", "available", "swap"] }One single-line row per device, never wrapped: wide rows show name, read
and write rates (direction roles 4/5) plus small rate bars scaled to the
fastest device in the view; below the compact width rows fall back to
name R rate W rate with space-less units on very narrow boxes, truncated
with … as a last resort. No disk I/O data when the snapshot carries no
device.
When the box is at least 16 columns wide and the contract tracks the
machine-wide aggregate disk histories (disk_read_history() /
disk_write_history(), both ≥ 2 samples), the rows reserve two text rows
and the leftover rows host the dual read/write chart — reads role 4, writes
role 5, y-axis the visible peak of both series (same geometry as the
network chart; rows are per-device while the histories are machine-wide
aggregates, exactly like network rows vs its aggregate history). A dim
+N more hint replaces the device tail that does not fit. With no
histories the rows simply expand to the whole box (text-only fallback).
DiskIOInfo exposes rates and byte counters only — no capacity — so the
used/free indicators of UX9.6 live in storage (per-mount DiskInfo).
Compact always-filled panel of aggregate numbers; no widget-specific
options (glyph keys only). Content rows (each a single logical line,
truncated with …, never wrapped):
Load 2.81 2.30 2.42— values colored by their share of the logical cores (logical_core_count()), same gauge rule as the header.CPUgauge row — machine-average usage: percent cell, role-colored gradient bar, dim core count on wide rows.Memgauge row — used percent + bar;used/totaldetail on wide rows.Procs 264 Run 2 Sleep 211 …— the snapshot's process count plus per-state counts (buckets by case-insensitive substring ofProcessInfo.state: Run/Sleep/Zombie/Idle/Stop, anything else folds into "Other"); when every state string is empty only the total shows. Counts never fabricate a bucket.Uptime 0d 7h 27m 9s.
Boxes taller than the five content rows draw the load-average history chart
(load_history()) into the leftover rows — auto-scaled to the visible
window peak (a trend view, good-role colored, the same scale and color as
the inline sparkline); with too little height for the chart (or no
history) the load row trails an inline block-ramp sparkline when width
allows. At height 4 the widget shows the top content rows and still fills
the box.
Per-core temperature panel; no widget-specific options (glyph keys only).
When any core exposes CpuInfo.temp_c (Linux), the widget renders a
column-major grid of CPU0 47° cells — every value colored by the
temperature ramp (interpolated between the theme's good/warn/alert roles,
see "Temperature ramp") with a #/gradient bar scaled to the 80 °C anchor
on single-column rows; the title carries the max temperature
(snapshot().cpu_temp, falling back to the max per-core value). Rows
overflowing the height are clipped silently (like the cpu grid).
When no temperature data exists anywhere (temp_c is None on every
core — macOS, Windows, sensor-less hosts), the box renders the honest line
no temperature data plus the load averages (role-colored as in the
header) — never empty, never fabricated. Leftover rows below the grid (or
the empty-state lines) host the load-average history chart when the kernel
tracks it and the width allows.
No widget-specific options are recognized (unknown keys are ignored;
defaults apply). The glyph keys charset/borders from the table above
apply to every widget, including these.
xtop-widget-core/src/chart.rs implements the per-cell colored plotter used
by the cpu/memory/network/disk_io/summary/sensors history areas and the
one-row spark helpers (spark_cells/spark_glyph/spark_levels) used by
the per-row braille sparks (processes cpu sparks, memory free sparks,
storage free braille bars). Model:
- A series is a list of
(x, y)samples (x-ordered, evenly spaced — the contract histories are). Columns sample a piecewise-linear interpolant at their center index; samples sharing a column also contribute their maximum (peak preserving). - Columns fill from the zero baseline at vertical resolution:
braille= 4 sub-rows per text row (dots⣀ ⣰ ⣶ ⣿for 1–4 lit sub-rows),block/half_block= 8 sub-rows per text row via the block ramp▁▂▃▄▅▆▇█. A chart area of height H renders H text rows — the engine never collapses: the widgets hand it the whole leftover rect, so a 100×34-style memory/network/disk_io box with five leftover rows draws a five-row braille plot (a 25% value lights the bottom row(s) fully plus a partial top cell). A height-1 plot is a sparkline and always uses the 8-level block ramp — one text row cannot host more than 4 braille levels, which is exactly the cramped one-line braille this engine replaces. - One-row mini sparks are different: discrete samples map to discrete
cells, so braille charsets paint braille glyphs (
⣀⣰⣶⣿— one braille cell already carries 4 sub-levels) and block charsets the 8-level ramp. Per-cell colors come from a caller-supplied role mapping (heat rules for usage, scarcity rules for free shares). Series shorter than the cell count paint their samples only (the remaining cells stay empty). dot/barcharsets keep the classic ratatuiChartpath (marker via the canonicalmarker_for), including its axis labels — the engine never draws them.
Color rule (deterministic, per cell):
- Fixed-role series (cpu per-core, network RX/TX, disk_io R/W): the cell takes the role color of the series whose top-most lit sub-row is highest; ties resolve to the first listed series (network passes RX before TX, so ties read as RX; disk_io passes reads before writes).
- Heat (single average/RAM/load series): the top-most lit sub-row maps to
gauge_gradient(level/total_subrows * 100, alert_at)on the same role slots the gauges use — cells under 50% of the axis aregood, 50% to the alert thresholdwarn, at/over italert. CPU usesalerts().cpu_high, RAMalerts().mem_high; summary/sensors load charts use the logical-core count as the axis andalerts().cpu_highas the threshold (the same semantics as the header load coloring); network/disk charts are always role-colored.
History areas show a dim ─ divider when at least three rows are available
and the plot below is at least two rows tall (the cpu divider carries the
dim history: cpu % label when the width allows — UX9.5). Chart minimum
widths: cpu 12, memory 14, network 16, disk_io 16, summary/sensors load 12
columns — narrower areas fall back to the numeric summary described per
widget.
Temperature UI (the cpu show_temp cell + per-core heat marks, the sensors
widget grid, the cpu unified-bar temp segment) is colored by a ramp derived
from the theme's own gauge roles — no new palette slot is invented, so
the role table stays consistent and low-contrast role colors lifted by the
kernel at theme load propagate into the ramp. Endpoints (documented in
xtop-widget-core/src/util.rs):
- at/under 45 °C the ramp is the
goodrole color (slot 2), - at 60 °C it passes the
warnrole color (slot 3), - at/over 80 °C it is the
alertrole color (slot 1),
with the intermediate colors channel-interpolated between the role colors
(util::temp_color).
registry() registers 11 names: header, cpu, memory, storage,
network, processes, disk_io, battery, gpu, summary, sensors.
header, cpu, memory, storage, network, processes, disk_io,
summary and sensors are the names default kernel layouts reference;
battery and gpu are not part of any default layout and are reachable
through the kernel's fullscreen mode (FullScreenWidget::Battery/Gpu map
to the names "battery"/"gpu" in the kernel's ui/screen.rs). All
renderers return early when the snapshot is None (pre-first-tick), so
every widget is safe on an empty state.
| Name | Draws | Data from | Options |
|---|---|---|---|
header |
One summary line (area.width >= 80) or two: color-coded segments — host (bold fg) | theme (accent) | layout (ramp slot 9) | uptime | load averages colored by their share of the logical cores; appends [Full: …] and [/] Search markers when active. Block belongs to a Paragraph. |
sys_info().hostname, layout_name(), snapshot().uptime/load_avg, fullscreen_label(), is_searching(), logical_core_count() |
glyph keys only |
cpu |
One row per core (label/percent/freq/heat-mark/temp/bar, see above), 2 columns when wide enough; the unified usage+temp+power row and the labeled history chart below when the area allows. Title: model + max temperature. | snapshot().cpus (incl. temp_c), sys_info().cpu_model/package_power_w, cpu_history(), alerts().cpu_high |
chart, cores, show_freq, show_temp, glyph keys |
memory |
RAM/AVL/SWP meter rows (used/free amounts + RAM free-share spark on wide rows) plus the RAM history chart when the area is wide and tall enough; title gains a ⚠ marker over the memory threshold. | snapshot().memory/swap, mem_history(), alerts().mem_high |
sections, glyph keys |
storage |
One single-line row per mounted disk (label, percent, gradient bar; used/free amounts on wide rows + free braille bar when very wide); tall boxes render three-line meter blocks per disk (mount + U used bar + A available bar) so per-disk bars scale with the box height. |
snapshot().disks, alerts().disk_high |
disks, glyph keys |
network |
Per-interface single-line rate rows (or aggregate RX/TX lines when narrow); RX/TX history chart below when wide and tall enough (aggregate live lines fill the gap while the history is empty). | snapshot().networks, net_rx_history(), net_tx_history() |
ifaces, glyph keys |
processes |
Fixed-column table with PID / Name / cpu-spark / CPU% / Mem / User / Command, dim │ separators, accent header with the sort marker on the sorted column only, zebra rows, selection row, viewport scroll window with a right-edge scrollbar; user names resolved through the kernel uid map, commands from the full argv, per-process cpu braille sparks. |
process_view(), uid_to_name(), process_cpu_history(), process_sort_label(), process_sort_desc(), search_query() |
cpu, columns, zebra, glyph keys |
disk_io |
One single-line row per device: name, R/W rates in the direction roles (wide rows add rate bars); dual read/write history chart in the leftover rows when the contract tracks the aggregate disk histories; "No disk I/O data" when empty. | snapshot().disk_io, disk_read_history(), disk_write_history() |
glyph keys only |
battery |
One gauge per battery: name, %, state, minutes to full/empty when applicable; "No battery data available" when empty. | snapshot().batteries |
glyph keys only |
gpu |
One gauge per GPU: name, %, memory used/total, temperature; "No GPU data available" when empty. | snapshot().gpus |
glyph keys only |
summary |
Load averages (role-colored by core share) + CPU/Mem gauges + process counts + uptime; load-average history chart in leftover rows (inline sparkline on the load row at small heights). | snapshot().load_avg/uptime/processes/memory/cpus, load_history(), logical_core_count() |
glyph keys only |
sensors |
Per-core temperature grid colored by the temperature ramp (title carries the max); "no temperature data" + load averages when no temp_c exists anywhere; load chart fills leftover rows. |
snapshot().cpus (temp_c) /load_avg, cpu_temp, load_history() |
glyph keys only |
Histories are drawn by the chart engine described above (braille/block/
half-block charsets, per-cell colors), with the classic ratatui Chart path
kept for the dot/bar charsets.
Registers cpu, memory, processes, network, storage, disk_io,
summary and sensors; every other name falls back to the base pack
(kernel contract, see docs/authoring.md). Enable with the kernel's
widget-blocks feature and select the pack per widget or globally. The
blocks pack consumes the same shared engine as the widget crates
(xtop-widget-core): palette roles, option parsers, glyph resolution, the
chart engine and the spark helpers are canonical — the pack keeps its
ASCII identity (# fills) and its row/table composition in its own code.
How it differs from the base pack:
- ASCII identity: bars and fills are
#characters instead of block glyphs; the frame and chart glyphs still follow the resolved charset and borders exactly like the base pack. Per-row braille sparks and heat marks use the same glyph helpers as the base pack (charset-resolved). cpu— titled "CPU BLOCKS" (+ the model and max temp): one single-line row per core (label, percent, optional frequency, per-core heat mark, temperature and#bar), honoringcores,show_freq,show_temp; when the grid leaves a row, the unified usage+temp+power line (#portions, UX9.5 parity) is drawn below it.memory— titled "Memory (blocks)": RAM/AVL/SWP meter rows with#bars, used/free amounts on wide rows and the RAM free-share braille spark, plus the RAM history chart through the engine (braille default;block/half_blockglyph sets reachable per widget), honoringsections.processes— titled "Processes (blocks)": an ASCII|-separated table (PID / Name / cpu-spark / CPU… / Mem / User / Command) with the base pack's column-drop policy, resolved user names, full command lines, per-process cpu braille sparks, sort marker on the sorted column, zebra rows and the same viewport scroll window; honoringcpu,columns,zebra.network— titled "Network (blocks)": per-interface#-bar rows (or aggregate lines when narrow) with the same width tiers and the dual RX/TX engine chart in the leftover rows, honoringifaces.storage— titled "Storage (blocks)": one#-fill row per mount with used/free amounts on wide rows (+ the free-share braille bar when very wide), honoringdisksand the disk alert threshold; tall boxes render the three-line per-disk meter blocks (mount+U+Abars).disk_io— titled "Disk I/O (blocks)": per-device R/W lines with#bars and compact units on narrow boxes plus the dual read/write engine chart in the leftover rows when the aggregate histories exist.summary— titled "Summary (blocks)": same content rows with#gauge bars and the load-average engine chart in the leftover rows.sensors— titled "Sensors (blocks)": per-core temperature grid with#heat bars (same ramp colors) and the same honest empty state.
- All widgets are defensive about small areas: chart sections only render
when the area is wide/tall enough, text rows are single logical lines
(truncated with
…where a tier cannot fit them), and list widgets stop adding rows when the area runs out. The smoke tests render every registered widget of both packs at 100x34, 100x30, 80x24, 60x20, 40x15 and 20x10 with empty and sampled state, assert every row stays inside the frame (no wrap detection) and that charts produce multi-row glyphs when the plot is at least two rows tall (seextop-widgetssrc/lib.rs,mod tests, andxtop-widget-blocks/src/lib.rs,mod tests; both test suites share thextop-widget-coretestkit — theWidgetStatedouble behind thetestkitcargo feature). - Widgets never mutate state and never touch kernel types; the rendering
inputs are the
WidgetStateview plus the per-tickSystemSnapshot. - CPU history data (
cpu_history()) is per-core and index-aligned withsnapshot().cpus; the per-core chart reads it through the shown cores'cpu_id. Network histories are machine-wide rates (bytes/s), so the network chart's y-axis is the visible maximum of both series. The same shape applies to the aggregate disk histories (disk_read_history()/disk_write_history(), bytes/s) andload_history()(1-minute load average) — all three are the additive UX8.3WidgetStatesurface with empty defaults, consumed only when non-empty. UX9.4 adds the uid→name map (uid_to_name, defaultNone) and the bounded per-process cpu history (process_cpu_history, default empty) to that additive surface. - Every color an amount, segment or spark paints is theme-driven through
the documented role/ramp rules; nothing new is invented when the data is
None— widgets show the honest fallback (numeric uid,?,·, hidden segment, classic bar) instead.