// STORAGESHOWER — ENGINEERING REPORT

Cyberpunk disk usage TUI in Rust · ratatui + crossterm · live IOKit / /proc/diskstats I/O · SMART probes · NFS latency · drill-down explorer · 30 builtin themes · per-channel theme editor

Docs

>_EXECUTIVE SUMMARY

storageshower is a single-binary TUI for disk usage monitoring written in Rust on top of ratatui + crossterm. It paints per-mount usage bars in four styles across 30 builtin cyberpunk palettes, refreshes telemetry (load avg, memory, CPU, swap, processes, uptime, network, battery) on a background thread behind an Arc<Mutex<>>, probes per-device SMART status and per-mount disk I/O throughput against IOKit on macOS and /proc/diskstats on Linux, samples network-filesystem latency with a timed read_dir, drills into mounts with a recursive directory explorer, and ships a live per-channel theme editor that round-trips to a TOML config. 13,635 Rust source lines + 49,922 integration test lines across 1,336 test files; 3,692 test functions total; 8 production + 4 dev direct dependencies (269 in the resolved tree); 252 commits since 2026-03-26.

13,635
Rust Src Lines
49,922
Test Src Lines
30
Builtin Themes
3,692
Test Functions
1,336
Test Files
13
Source Files
8
Direct Deps
269
Resolved Crates
252
Git Commits
v0.28.9
Current Release

Source distribution — 63,901 Rust lines

49,922 tests / 13,635 src / 344 bench · 78.1% tests

Tests: 1,336 files in tests/. Src: 13 modules in src/. Bench: 1 file (benches/bench.rs, 344 lines, criterion-driven). Test:src ratio is 3.66:1.


#SOURCE LAYOUT

Thirteen modules under src/. Largest: ui.rs (rendering) and keys.rs (keyboard dispatch). system.rs hosts every platform probe (IOKit / /proc/diskstats / SMART / network FS latency / sysinfo). app.rs owns the App state machine and refresh loop.

Module Total lines Prod lines Inline test lines Role
src/ui.rs3,8643,449415ratatui widgets: disk list, drill-down, header / footer / tooltips, theme chooser, theme editor, popup chrome, palette tables
src/keys.rs1,9326871,245Keyboard event dispatch: navigation, sort, filter, drill-down, theme editor, threshold cycles, bookmarks, export
src/cli.rs1,5444651,079clap derive: every flag, --no-flag inverses, --list-colors, --export-theme, config merge
src/system.rs1,511778733Platform probes: IOKit byte counters (macOS), /proc/diskstats (Linux), SMART, NFS latency, sysinfo wrappers, mount discovery
src/app.rs1,325462863App state, refresh thread, alert tracking, bookmark set, filter buffer, view mode (Disks ↔ DrillDown)
src/mouse.rs923235688Mouse event router: hit-testing rows / headers / column separators / popup / theme rows, drag-to-resize, scroll-wheel
src/prefs.rs685108577TOML config schema, load / save, custom-theme merge, default storageshower.default.conf generator
src/helpers.rs68369614Pure helpers: byte-pretty-printing (human / gib / mib / bytes), mount-name compaction, gradient interpolation
src/types.rs679263416ColorMode (30 variants), ThemeColors, UnitMode, SmartHealth, DiskEntry, DirEntry, SysStats, HoverZone, ViewMode
src/columns.rs23449185Column layout: mount / used / size / pct / bar widths, drag-resize math
src/testutil.rs1359243Shared test fixtures: synthetic DiskEntry / App builders, temp-config helpers
src/main.rs1061060Entry point: terminal setup / teardown, App bootstrap, event loop, signal handling
src/lib.rs14104Module declarations + crate-level lint allows; binary & integration tests both consume the library
Total13,6356,7736,862Inline tests ≈ production code (50/50 split inside src/); integration tests (49,922 lines) sit on top

Inline-test rows count lines from each module's #[cfg(test)] mod tests block to EOF. main.rs and lib.rs have no inline tests by design; all behavioral coverage of the binary lives in tests/.


*TEST SURFACE

1,336 integration test files in tests/ plus inline #[cfg(test)] modules in every source file. 3,692 total #[test] functions. Integration tests dominate (3,101 of 3,692) and target whole-binary behavior: CLI parsing, config merge, scan output, drill-down sorting, threshold transitions, palette equivalence.

3,692
Test Functions
3,101
Integration
591
Inline
1,336
Test Files
49,922
Test LOC
3.66:1
Test : Src

Inline coverage by module (counted as #[test] attributes inside each #[cfg(test)] mod tests):

ModuleInline #[test] fns
src/keys.rs120
src/cli.rs109
src/helpers.rs98
src/app.rs57
src/system.rs45
src/ui.rs41
src/types.rs40
src/mouse.rs27
src/prefs.rs26
src/columns.rs23
src/testutil.rs5
src/main.rs0
Total inline591

Integration test files follow a verbose naming convention — each file documents the scenario in its filename so failing tests are self-describing in CI output:

tests/cli_apply_header_no_bars_no_local_integration.rs
tests/cli_binary_void_walker_deep_net_integration.rs
tests/helpers_format_bytes_72_kib_human_integration.rs
tests/scan_directory_two_hundred_sixty_nine_files_integration.rs
… (1,332 more)

Test fixtures live in src/testutil.rs behind pub(crate) and are exposed only when #[cfg(test)]. End-to-end CLI tests spawn the compiled binary under tempfile-managed config dirs and assert against stdout / stderr / exit code rather than poking internal state.


@SUBSYSTEM BREAKDOWN

Eleven feature subsystems sit between the event loop and the renderer. Every probe is cancellable and bounded so the UI thread never blocks — slow filesystems return None after the deadline and rerun next tick.

Render engine

Per-mount usage bar in four glyph styles. Cycles via b. Theme palette consumed as (blue, green, purple, light_purple, royal, dark_purple) from ui::palette(); mapped to ratatui Colors per channel.

  • Gradient: ████▓▓▒▒░░ with interpolated color along the fill
  • Solid / thin / ascii fall back for low-color terminals or muscle-memory aesthetics

Telemetry core

Background thread refreshes SysStats every 3 s through an Arc<Mutex<SysStats>>. Captured fields: hostname, load_avg (1/5/15), mem used/total, swap used/total, CPU count, process count, kernel, arch, uptime, OS name + version. UI lock-and-copy on each frame; never blocks render.

Alert subsystem

Tracks (mount, was_above_threshold) per disk between ticks. Transitions into warn / crit fire: terminal bell, 2 s pulsing red border flash, dark-red row highlight, status-bar message. Transitions out silently clear. Thresholds default 70 % / 90 %, cycle via t / z.

Drill-down explorer

Enter on a mount swaps ViewMode::DisksViewMode::DrillDown. Recursive size scan runs on a worker thread; entries paint as they're computed with a progress bar. Sort by size or name, gradient bars relative to the largest entry, breadcrumb up via Backspace.

Live disk I/O

Per-tick delta of byte counters → (read_rate, write_rate) overlaid on each row as ▲1.2M/s ▼500K/s. macOS: IOKit IOBlockStorageDriver via IOServiceMatching, mounts mapped via getmntinfo. Linux: /proc/diskstats sector counters (× 512 bytes), mapped via /proc/mounts.

SMART health

Per-base-device SMART probe cached and propagated to every mount sharing the device. macOS: parse diskutil info <dev> SMART Status: line. Linux: read /sys/block/<dev>/device/state. Green verified, red failing, neutral for unknown.

Network FS latency

Detects mount types: nfs, nfs4, cifs, smbfs, afp, ncp, fuse.sshfs, fuse.rclone, fuse.s3fs, 9p, afs. Probes with a 2 s deadlined read_dir. Color-coded badge: green < 50 ms, yellow 50–200 ms, red > 200 ms or timeout. Cached per mount so the probe never re-runs more than once per refresh interval.

Filter mode

/ enters case-insensitive substring filter. Buffer supports Ctrl+a/e/b/f/w/u/k/h, Backspace, Delete, Home/End, arrow keys. Enter commits, Esc cancels, 0 clears.

Theme chooser & editor

c pops a chooser with mouse + scroll preview; clicking outside reverts. C opens a per-channel editor with j/k to select channel and h/l (±1) or H/L (±10) to adjust. Enter saves to ~/.storageshower.conf after a name prompt.

Mouse router

Hit-tests every visible region per event: disk rows, column headers, column separators (drag), title segments, footer segments, popup rows, theme grid. Right-click pops a verbose tooltip (capacity, rank, headroom, SMART, I/O). Scroll wheel walks selection in the active view.

Bookmark / export

B toggles bookmark on the selected mount (persisted in the bookmarks config array, sorted ahead of everything else). e/E exports the current disk matrix (mount, percent, used, total) to ~/.storageshower.export.txt. o/O opens the mount in the system file manager. y/Y copies the path.


%THEME SYSTEM

30 builtin palettes defined as ColorMode variants in src/types.rs. Each maps to a six-channel ThemeColors (blue, green, purple, light_purple, royal, dark_purple) consumed by ui::palette() and ui::palette_for_prefs(). User themes ship as themes/*.toml and merge into the user's ~/.storageshower.conf as [custom_themes.<name>] blocks, activated via active_theme or --theme.

CLI keyDisplay nameCLI keyDisplay name
defaultNeon SprawlcyberfrostCyber Frost
greenAcid RainplasmacorePlasma Core
blueIce BreakersteelnerveSteel Nerve
purpleSynth WavedarksignalDark Signal
amberRust BeltglitchpopGlitch Pop
cyanGhost WireholoshiftHolo Shift
redRed SectornightcityNight City
sakuraSakura DendeepnetDeep Net
matrixData StreamlasergridLaser Grid
sunsetSolar FlarequantumfluxQuantum Flux
neonnoirNeon NoirbiohazardBio Hazard
chromeheartChrome HeartdarkwaveDarkwave
bladerunnerBlade RunneroverlockOverlock
voidwalkerVoid WalkermegacorpMegacorp
toxicwasteToxic WastezaibatsuZaibatsu

The color_mode_next_cycles_full_palette_exactly test in src/types.rs pins the cycle order. Any new variant added to ColorMode::ALL bumps that test — an immune system against silently dropping a palette.


!PLATFORM PROBES

Every OS-touching code path lives in src/system.rs. The probes are cfg(target_os = "macos") / cfg(target_os = "linux") gated and return uniform types so the rest of the codebase (renderer, alert subsystem, drill-down) is platform-agnostic.

Mount enumeration

macOS: getmntinfo (BSD) yields struct statfs → mount path + device.
Linux: parse /proc/mounts.
Common: sysinfo::Disks for usage figures (used / total / fs name / kind).

Disk I/O byte counters

macOS: IOKit IOServiceMatching("IOBlockStorageDriver")Statistics dict → Bytes (Read) / Bytes (Write). Delta vs last tick ÷ elapsed = rate.
Linux: fields 6 + 10 from /proc/diskstats (sectors read / written) × 512 bytes.

SMART status

macOS: shell out to diskutil info /dev/disk0, regex SMART Status: line, map to Verified / Failing.
Linux: read /sys/block/<dev>/device/state, treat "running" as verified, everything else as unknown / failing.

Network FS latency

Detect mount type from the filesystem string (nfs, nfs4, cifs, smbfs, afp, ncp, fuse.sshfs, fuse.rclone, fuse.s3fs, 9p, afs). Spawn a worker thread, read_dir the mount, time to first entry. 2 s deadline; on timeout, return None — UI renders no badge.

Sysinfo wrapper

sysinfo crate refreshed on the 3-second background tick. Fields exposed via SysStats: hostname, load_avg, mem used/total, swap used/total, CPU count, process count, kernel, arch, uptime, OS name + version. Network IP, battery, TTY pulled from libc / IOKit / Linux /sys outside of sysinfo.

Local disk detection

HDD / SSD vs network / virtual decided from the DiskKind + filesystem type. --local-only filters out anything that isn't a physical disk; --no-virtual drops tmpfs / devfs / proc / sysfs etc.


+DEPENDENCIES

Eight direct production dependencies + four dev-dependencies. Cargo.lock resolves to 269 crates total — the bulk are transitive pulls from ratatui (signal handling, terminal backends) and sysinfo (per-OS shims).

CrateVersionRole
ratatui0.30TUI framework: widgets, layout, terminal backend abstraction
crossterm0.29Cross-platform terminal control: events, raw mode, mouse, alt screen
sysinfo0.38OS-agnostic disk / mem / CPU / proc / load_avg / hostname / uptime
clap4 (derive)CLI parsing with derive macros, value-enum for UnitMode
serde1 (derive)Config + theme schema serialization (ThemeColors, UnitMode, etc.)
toml1.1Config file format: parse ~/.storageshower.conf, emit --export-theme output
dirs6Cross-platform home directory lookup for the default config path
libc0.2Unix syscalls: getmntinfo, statfs, time, TTY detection, getifaddrs
— dev-dependencies —
criterion0.8 (html_reports)Bench framework: benches/bench.rs measures bar rendering, scan, palette resolve
serde_json1JSON round-trip tests for ThemeColors and config snapshots
tempfile3Per-test isolated config dirs for integration tests
toml1.1Dev-side roundtrip checks of the on-disk config format

Release profile: opt-level = 3, lto = true, strip = true. Rust 2024 edition; MSRV ≥ 1.85. CI matrix targets x86_64-apple-darwin (macos-latest), aarch64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu.


;PUBLIC API SURFACE

src/lib.rs exposes the library so integration tests and the binary share a single compilation unit. Eight pub mods; keys and mouse stay private; testutil is pub(crate) behind #[cfg(test)].

ModuleVisibilitySurface
apppubApp state, refresh loop, view-mode transitions, alert tracking
clipubclap::Parser-derived Args, config merge, --list-colors / --export-theme handlers
columnspubColumn layout struct + resize math (mount / used / size / pct / bar widths)
helperspubByte formatters, mount-name compaction, gradient interpolation, color helpers
prefspubPrefs TOML schema, load / save, custom-theme merge
systempubMount enumeration, disk I/O byte counters, SMART, NFS latency, sysinfo wrapper
typespubColorMode, ThemeColors, UnitMode, SmartHealth, DiskEntry, DirEntry, SysStats, HoverZone, ViewMode
uipubpalette(), palette_for_prefs(), every render fn for ratatui widgets
keysprivateKeyboard event dispatch — consumed only by the binary; integration tests drive via the CLI surface
mouseprivateMouse event router — same reasoning as keys
testutilpub(crate), #[cfg(test)]Shared synthetic DiskEntry / App builders

?KEY DESIGN DECISIONS

Background telemetry, foreground render

Sysinfo refresh runs on a dedicated thread behind Arc<Mutex<SysStats>>. The render path takes the lock only long enough to .clone() into a local. No probe ever blocks the UI thread — slow filesystems return None after a deadline and the UI paints the last known good value.

30 builtin themes, not 6

Each palette is a hand-tuned six-channel struct, not a hue rotation. Letting the user cycle by feel beats a single palette knob. The color_mode_next_cycles_full_palette_exactly test pins the cycle so adding a variant is a forced choice, not an accident.

Verbose test filenames

tests/scan_directory_two_hundred_sixty_nine_files_integration.rs reads worse than scan_269.rs but is self-describing in CI output. 1,336 such files mean grep-ability matters more than typing.

Every flag has a --no-flag inverse

Saved config is the baseline; one-off invocations need to be able to force-override in either direction without rewriting the file. --no-tooltips defeats a show_tooltips = true config without saving over it.

Probe results cached, never re-fetched mid-tick

SMART status, I/O byte counters, NFS latency all cache per refresh interval. Re-asking IOKit or shelling to diskutil on every paint would tank the render loop — refresh is a tick boundary, not a per-frame call.

Drill-down is a separate ViewMode

Rather than overlay the directory explorer on the disk list, drill-down swaps ViewMode::DisksViewMode::DrillDown. Cleaner mouse hit-tests, simpler scroll-wheel semantics, no z-order ambiguity. Esc restores the disk view; Backspace walks up the breadcrumb.

Inline tests ≈ production code

50 / 50 split inside src/ (6,773 prod vs 6,862 test lines). Each module owns its inline coverage; cross-module behavior lives in tests/. Net 49,922 integration lines on top of 13,635 src.

No async runtime

Threaded refresh + bounded blocking probes (with deadlines) cover the workload without dragging in tokio. The binary stays small (release-stripped LTO), startup is instant, and the dependency tree stops at 269 crates instead of 400+.


~BENCHMARK SUITE

benches/bench.rs (344 lines, harness = false) drives a Criterion suite over the hot paths: byte formatting, uptime formatting, mount-name truncation, column-width math, palette resolve, gradient interpolation, time conversion, disk / sys-stat collection, config serde, and the sort / filter / render pipelines at 10 / 50 / 200 disks. Run with cargo bench; HTML reports land in target/criterion/.

Benchmark groupCoversRepresentative time
format_bytesHuman / GiB / MiB / Bytes pretty-printing~20–69 ns
format_uptimeDuration-to-string (minutes … days)~21–39 ns
truncate_mountMount-name compaction at widths 8 / 32~27–97 ns
mount_col_width / right_col_width_staticColumn-width layout math~540–590 ps
palette / gradient_color_atTheme channel resolve + gradient lerp~0.7–2.4 ns
epoch_to_local / chrono_nowEpoch-to-local clock conversion~300–427 ns
collectcollect_disk_entries / collect_sys_stats~3.2–3.8 µs
prefs_serdeTOML serialize / deserialize of Prefs~4.6–5.3 µs
sort_disksby_name / by_pct / by_size at 10 / 50 / 200~242 ns–7.9 µs
filter_disksSubstring match / no-match at 10 / 50 / 200~151 ns–5.9 µs
format_all_disksFull render pass at 10 / 50 / 200~1.5–15.7 µs

Times are Criterion-measured on Apple Silicon (M-series) and reproduced verbatim from the README; absolute figures vary by hardware. The point is the shape: every per-frame path is sub-microsecond, every per-tick path is single-digit microseconds, so the render loop is never the bottleneck.


=CI & RELEASE

GitHub Actions gates every push, pull request, and merge-queue batch to main. All jobs run --locked so CI resolves the exact dependency graph committed in Cargo.lock; concurrent runs for a branch are cancelled when a newer commit lands.

JobCommandRunners
Checkcargo check --locked --all-targetsubuntu + macos
Testcargo test --locked --lib, --tests, --docubuntu + macos
Formatcargo fmt --all --checkubuntu
Clippycargo clippy --locked --all-targets -- -D warningsubuntu
Doccargo doc --locked --no-deps (RUSTDOCFLAGS=-D warnings)ubuntu

The release workflow builds on tag push for six targets — aarch64-apple-darwin, x86_64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu (cross-compiled), and static x86_64 / aarch64 musl variants — then publishes a GitHub release and regenerates the Homebrew formula in MenkeTechnologies/homebrew-menketech. To match CI locally before pushing:

cargo fmt --all --check \
  && cargo clippy --locked --all-targets -- -D warnings \
  && cargo test --locked --lib \
  && cargo test --locked --tests \
  && cargo test --locked --doc

.REPOSITORY