Expand description
The rollup tree TUI: the front end the whole tool is for.
§What it is
The filesystem tree, pruned to paths that lead to something reclaimable, with every
ancestor carrying the bytes recoverable beneath it — collapsed by default and drilled
into on demand. A row’s number is not “how big is this directory” but “how much do I get
back by emptying this subtree”, so ~/repos/archived is one row worth 118 GB rather than
forty rows a reader has to recognise as related.
That is the thing neither reference implementation has. kondo’s own README calls it
“essentially rm -rf with a prompt”, which is a decision per hit, and hits are what scale:
one real home directory here holds 16,013 of them. npkill is better and still flat, so the
row a reader actually wants does not exist and has to be assembled from forty selections;
its range-select is npkill approximating a tree without having one.
§Three moving parts, and the channel between them
- The walker runs on its own thread and reports
Foundevents. Rows appear as it finds them, which is npkill’s good idea and is easier on a tree: a new claim updates ancestor totals in place instead of reordering a flat list. - The view (
state::View) holds everything a keystroke can change. It never touches the terminal or the filesystem, so every rule it has is a unit test. - The deleter runs a marked batch on a pool and reports each target as it finishes, which is why the cursor is anchored to a path: rows vanish under it.
All three meet on one channel, drained once per frame. The event loop blocks on the terminal with a short timeout rather than on the channel, so a scan that finds nothing for a second still repaints and a keystroke is never waiting behind a walk.
§The TUI prices what it shows, and the CLI does not
A default scan leaves claims crate::Size::Unmeasured, because pricing one means enumerating
the subtree the walk deliberately pruned at — 4.6 s against 55.8 s over one real ~/repos.
That is right for a listing you read once and wrong for a tree you steer by: unpriced, the
rollup has nothing to roll up, and the headline question has no answer at any depth.
So the TUI turns the breakdown on unless the command line has scoped it. It can afford to,
and #618 is why: prices are computed on a pool and arrive as separate events, so the rows
are on screen at 7.5 s while the numbers fill in behind them for the following minute. The
reader marks and deletes throughout. --breakdown-under <PATH> still means what it says,
for a reader who wants one subtree priced and the rest left alone — and a double click
is the same request made afterwards, on the one row in front of the reader.
§The pointer is generic, and that is the whole choice
The alternative on the table was OSC 8 hyperlinks, which only ever express “open this path”. A pointer gives row selection, click-to-expand, click-to-mark, the wheel and column-heading sorting from one hit test, and every later feature gets it for free. Three parts, mirroring the keyboard’s three:
render::hitresolves a cell to aSpotagainst the frame that was drawn. That is also where the routing lives — an overlay covers the screen it is over, so a press inside one cannot reach the tree, without anything restating the order.- [
Pointer] holds what one event cannot see about the events before it: what the press landed on, whether it has moved since, and whether one landed here a moment ago. keymap::pointerturns a gesture and a spot into anAction, off a table the help overlay reads too.
A Spot::Row names its row by identity and never by position, which matters more here
than in pua: rows re-sort as prices land and vanish as removals complete, and a press
resolved to a position and acted on a moment later would delete the wrong subtree.
Modules§
- chrome
- Everything the terminal shows that is not a cell of the frame.
- keymap
- What a keypress means: one table, and the chain that reads it.
- lens
- What is on screen: two independent axes, with presets on top.
- moving
- What is moving on the screen right now, and why each thing is allowed to.
- render
- Drawing one frame, and reading back where it put things.
- state
- The live view’s whole state: what is open, what is marked, and where the cursor is.
- treemap
- The treemap pane — a spike, and its escape hatch is
Mapsnot beingMaps::Can.
Structs§
- Options
- Everything the front end needs from the command line.
- Outcome
- What the run turns out to have been, for the exit status.