>_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.
Source distribution — 63,901 Rust lines
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.rs | 3,864 | 3,449 | 415 | ratatui widgets: disk list, drill-down, header / footer / tooltips, theme chooser, theme editor, popup chrome, palette tables |
src/keys.rs | 1,932 | 687 | 1,245 | Keyboard event dispatch: navigation, sort, filter, drill-down, theme editor, threshold cycles, bookmarks, export |
src/cli.rs | 1,544 | 465 | 1,079 | clap derive: every flag, --no-flag inverses, --list-colors, --export-theme, config merge |
src/system.rs | 1,511 | 778 | 733 | Platform probes: IOKit byte counters (macOS), /proc/diskstats (Linux), SMART, NFS latency, sysinfo wrappers, mount discovery |
src/app.rs | 1,325 | 462 | 863 | App state, refresh thread, alert tracking, bookmark set, filter buffer, view mode (Disks ↔ DrillDown) |
src/mouse.rs | 923 | 235 | 688 | Mouse event router: hit-testing rows / headers / column separators / popup / theme rows, drag-to-resize, scroll-wheel |
src/prefs.rs | 685 | 108 | 577 | TOML config schema, load / save, custom-theme merge, default storageshower.default.conf generator |
src/helpers.rs | 683 | 69 | 614 | Pure helpers: byte-pretty-printing (human / gib / mib / bytes), mount-name compaction, gradient interpolation |
src/types.rs | 679 | 263 | 416 | ColorMode (30 variants), ThemeColors, UnitMode, SmartHealth, DiskEntry, DirEntry, SysStats, HoverZone, ViewMode |
src/columns.rs | 234 | 49 | 185 | Column layout: mount / used / size / pct / bar widths, drag-resize math |
src/testutil.rs | 135 | 92 | 43 | Shared test fixtures: synthetic DiskEntry / App builders, temp-config helpers |
src/main.rs | 106 | 106 | 0 | Entry point: terminal setup / teardown, App bootstrap, event loop, signal handling |
src/lib.rs | 14 | 10 | 4 | Module declarations + crate-level lint allows; binary & integration tests both consume the library |
| Total | 13,635 | 6,773 | 6,862 | Inline 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.
Inline coverage by module (counted as #[test] attributes inside each #[cfg(test)] mod tests):
| Module | Inline #[test] fns |
|---|---|
src/keys.rs | 120 |
src/cli.rs | 109 |
src/helpers.rs | 98 |
src/app.rs | 57 |
src/system.rs | 45 |
src/ui.rs | 41 |
src/types.rs | 40 |
src/mouse.rs | 27 |
src/prefs.rs | 26 |
src/columns.rs | 23 |
src/testutil.rs | 5 |
src/main.rs | 0 |
| Total inline | 591 |
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::Disks → ViewMode::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 key | Display name | CLI key | Display name |
|---|---|---|---|
default | Neon Sprawl | cyberfrost | Cyber Frost |
green | Acid Rain | plasmacore | Plasma Core |
blue | Ice Breaker | steelnerve | Steel Nerve |
purple | Synth Wave | darksignal | Dark Signal |
amber | Rust Belt | glitchpop | Glitch Pop |
cyan | Ghost Wire | holoshift | Holo Shift |
red | Red Sector | nightcity | Night City |
sakura | Sakura Den | deepnet | Deep Net |
matrix | Data Stream | lasergrid | Laser Grid |
sunset | Solar Flare | quantumflux | Quantum Flux |
neonnoir | Neon Noir | biohazard | Bio Hazard |
chromeheart | Chrome Heart | darkwave | Darkwave |
bladerunner | Blade Runner | overlock | Overlock |
voidwalker | Void Walker | megacorp | Megacorp |
toxicwaste | Toxic Waste | zaibatsu | Zaibatsu |
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).
| Crate | Version | Role |
|---|---|---|
ratatui | 0.30 | TUI framework: widgets, layout, terminal backend abstraction |
crossterm | 0.29 | Cross-platform terminal control: events, raw mode, mouse, alt screen |
sysinfo | 0.38 | OS-agnostic disk / mem / CPU / proc / load_avg / hostname / uptime |
clap | 4 (derive) | CLI parsing with derive macros, value-enum for UnitMode |
serde | 1 (derive) | Config + theme schema serialization (ThemeColors, UnitMode, etc.) |
toml | 1.1 | Config file format: parse ~/.storageshower.conf, emit --export-theme output |
dirs | 6 | Cross-platform home directory lookup for the default config path |
libc | 0.2 | Unix syscalls: getmntinfo, statfs, time, TTY detection, getifaddrs |
| — dev-dependencies — | ||
criterion | 0.8 (html_reports) | Bench framework: benches/bench.rs measures bar rendering, scan, palette resolve |
serde_json | 1 | JSON round-trip tests for ThemeColors and config snapshots |
tempfile | 3 | Per-test isolated config dirs for integration tests |
toml | 1.1 | Dev-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)].
| Module | Visibility | Surface |
|---|---|---|
app | pub | App state, refresh loop, view-mode transitions, alert tracking |
cli | pub | clap::Parser-derived Args, config merge, --list-colors / --export-theme handlers |
columns | pub | Column layout struct + resize math (mount / used / size / pct / bar widths) |
helpers | pub | Byte formatters, mount-name compaction, gradient interpolation, color helpers |
prefs | pub | Prefs TOML schema, load / save, custom-theme merge |
system | pub | Mount enumeration, disk I/O byte counters, SMART, NFS latency, sysinfo wrapper |
types | pub | ColorMode, ThemeColors, UnitMode, SmartHealth, DiskEntry, DirEntry, SysStats, HoverZone, ViewMode |
ui | pub | palette(), palette_for_prefs(), every render fn for ratatui widgets |
keys | private | Keyboard event dispatch — consumed only by the binary; integration tests drive via the CLI surface |
mouse | private | Mouse event router — same reasoning as keys |
testutil | pub(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::Disks → ViewMode::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 group | Covers | Representative time |
|---|---|---|
format_bytes | Human / GiB / MiB / Bytes pretty-printing | ~20–69 ns |
format_uptime | Duration-to-string (minutes … days) | ~21–39 ns |
truncate_mount | Mount-name compaction at widths 8 / 32 | ~27–97 ns |
mount_col_width / right_col_width_static | Column-width layout math | ~540–590 ps |
palette / gradient_color_at | Theme channel resolve + gradient lerp | ~0.7–2.4 ns |
epoch_to_local / chrono_now | Epoch-to-local clock conversion | ~300–427 ns |
collect | collect_disk_entries / collect_sys_stats | ~3.2–3.8 µs |
prefs_serde | TOML serialize / deserialize of Prefs | ~4.6–5.3 µs |
sort_disks | by_name / by_pct / by_size at 10 / 50 / 200 | ~242 ns–7.9 µs |
filter_disks | Substring match / no-match at 10 / 50 / 200 | ~151 ns–5.9 µs |
format_all_disks | Full 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.
| Job | Command | Runners |
|---|---|---|
Check | cargo check --locked --all-targets | ubuntu + macos |
Test | cargo test --locked --lib, --tests, --doc | ubuntu + macos |
Format | cargo fmt --all --check | ubuntu |
Clippy | cargo clippy --locked --all-targets -- -D warnings | ubuntu |
Doc | cargo 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
- Source — github.com/MenkeTechnologies/storageshower
- Crate — crates.io/crates/storageshower
- Homebrew —
brew tap MenkeTechnologies/menketech - API docs — docs.rs/storageshower
- Issues — github.com/MenkeTechnologies/storageshower/issues
- Docs hub — index.html (install, keys, CLI flags, themes)