Skip to main content

max / alloy

docs: record what the form widgets actually shipped as The roster listed AlloyForm and AlloyField as still to come and did not mention AlloyPicker at all, since the overlay was expected to compose from what was already there. It could not, for the reason COMPONENT-LIBRARY.md now records. Also the three decisions the roster did not settle: one line per row and what that costs, sections as headers rather than boxes, and the unset flag.
Author: Max Johnson <me@maxj.phd> · 2026-07-24 18:46 UTC
Signed with PGP, not checked
Commit: dec79fca00e861e5ed5a8c3e3fe8ae6c35b56482
Parent: 8cb7f2a
2 files changed, +7 insertions, -5 deletions
@@ -126,7 +126,7 @@
126 126 f.render_widget(UrgencyTag { theme, kind: Urgency::High }, area);
127 127 ```
128 128
129 - The starter widget set (per [CONSOLE.md](CONSOLE.md)). Shipped: `AlloyBlock`, `AlloyList`, `AlloyStatusBar`, `AlloyLog`, `AlloyConnector`, `AlloyTabs`, and `AlloyModal`. Landing with `alloy settings`: `AlloyForm`, a single `AlloyField`, and a display-only `AlloyTable` (see the next section).
129 + The starter widget set (per [CONSOLE.md](CONSOLE.md)). Shipped: `AlloyBlock`, `AlloyList`, `AlloyStatusBar`, `AlloyLog`, `AlloyConnector`, `AlloyTabs`, and `AlloyModal`. Shipped with `alloy settings` as 1.2: `AlloyForm`, a single `AlloyField`, `AlloyPicker`, and a display-only `AlloyTable` (see the next section).
130 130
131 131 `AlloyList` scrolls statelessly: the first visible row is derived from the selection every frame rather than carried in a `ListState`. That is what keeps it immediate-mode, and it costs centered scrolling rather than minimal scrolling. Because anything drawing alongside a list has to agree with it about which rows are on screen, that derivation is public (`list_offset`, `list_row_y`) rather than reimplemented by callers; `AlloyConnector` uses it to find the row it points at.
132 132
M docs/CONSOLE.md +6 -4
@@ -166,9 +166,11 @@
166 166 - `FocusRing` for moving between panes, and `Cursor` for a selection over a list whose length changes underneath it. Two types rather than one: a focus ring wraps, because that is what Tab means, while a list cursor clamps and re-clamps when a refresh returns fewer rows.
167 167 - Footer chrome and the theme layer, ported from mountaineer-sysop's `sysop-tui` and retinted from a runtime theme rather than a const palette.
168 168
169 - Still to come, with `alloy settings`:
170 - - `AlloyForm` (the collapsible-section form chrome) and a display-only `AlloyTable`.
171 - - A single `AlloyField` widget carrying a `FieldKind` value-cell enum (Toggle / Text / Number / Enum / Color), not the five separate field widgets earlier drafts rostered. Those differed only in how the value cell paints once state and validation moved to the binary, and `AlloyForm`'s heterogeneous row list forces an enum regardless; the rationale is worked in full in [COMPONENT-LIBRARY.md](COMPONENT-LIBRARY.md). `TextField` promotes from the console binary's `field.rs` as the caret buffer it uses in edit mode. Ships as `alloy_tui` 1.2.
169 + Shipped with `alloy settings`, as `alloy_tui` 1.2:
170 + - `AlloyForm`, the form chrome, over rows that are one line each without exception. That is what lets it scroll through the same stateless `list_offset` an `AlloyList` does, and it is why the focused row's help and diagnostic render on a reserved line at the foot of the form rather than under the row. Sections are header rows and an indent, not nested boxes: a box inside a pane spends two columns a side per level to say what the fold marker already says.
171 + - A single `AlloyField` widget carrying a `FieldKind` value-cell enum (Toggle / Text / Number / Enum / Color), not the five separate field widgets earlier drafts rostered. Those differed only in how the value cell paints once state and validation moved to the binary, and `AlloyForm`'s heterogeneous row list forces an enum regardless; the rationale is worked in full in [COMPONENT-LIBRARY.md](COMPONENT-LIBRARY.md). It also carries an `unset` flag the roster did not have, which is what lets a row show the difference between a key set to 12 and a key absent from a file that defaults to 12.
172 + - `AlloyPicker`, the filterable overlay an enum opens, and a display-only `AlloyTable` for list-of-tables records. Nothing renders through the table yet; a form row is one line and a table is more than that, so it waits on the detail surface that will hold it.
173 + - `TextField` promoted from the console binary's `field.rs` as the caret buffer both the field and the picker's filter use.
172 174
173 175 Not in `alloy_tui`, contrary to earlier drafts: mock-or-real backend detection. It lives per view in the console binary, because what counts as "the real backend" is `nmcli` for one view and `pactl` for another. The pattern carries over from `sysop-tui`; the code does not.
174 176
@@ -186,7 +188,7 @@
186 188 The ordering below is the reverse of what this document originally planned, which put `alloy config` alone at v0.5 and every live-state subcommand at v1. The live-state views went first instead. They are small enough to carve one at a time, and each one forced a piece of shared machinery into existence against something real: the shell chrome and log pane from `net`, the second list and the `Cursor` from `audio`, the two-pane layout and `AlloyConnector` from `audio`'s routing, the background tick from watching streams appear. `alloy settings` needs the form widgets and the schema parser at once, and it is a better shape to build on a shell that has already carried three screens.
187 189
188 190 - **Shipped.** `alloy net`, `alloy audio`, `alloy mesh`, and all three `alloy pkg` tabs (`box`, plus `install` and `update` fronting `rpm-ostree status --json`). Plus the shell they share: frame, reserved keys, focus, command-log pane, background tick. `alloy pkg` forced three more pieces into it: `AlloyTabs`, a Cancel that views see before the shell claims it (a confirm needs a cancel that is not "exit the app"), and terminal suspend, so entering a box can hand the TTY to another interactive program.
189 - - **In progress.** `alloy settings`, with schemas for the v0-adopted TOML configs (rio, yazi, mako, and others; the sway config takes the text-edit fallback). The largest remaining piece: schema-DSL v1 parser, `toml_edit` roundtrip layer, and the form widgets together. The view architecture is settled (see "View architecture" above); build order is (1) schema parser and (2) the `Bind` seam with its `toml_edit` implementation, both pure and testable against `rio.toml.schema`, **both shipped**; (3) `AlloyForm` + `AlloyField` + `TextField` promotion as the `alloy_tui` 1.2 release, **shipped**; (4) tab chrome and the Applications tab, **shipped**; (5) per-field edit plus save and the quit-confirm, **shipped**, then the filterable pick overlay, which is what an enum row is still waiting on; (6) the System tab, time rows first, which is the smallest complete slice of the command side: one front, one enum, one bool, one display-only row; (7) the remaining System rows; (8) presets and live diagnostics, the collapsible sections having landed with the tab; (9) the text-edit fallback, save and dirty tracking having landed with the edit; (10) `AlloyTable` read-only.
191 + - **In progress.** `alloy settings`, with schemas for the v0-adopted TOML configs (rio, yazi, mako, and others; the sway config takes the text-edit fallback). The largest remaining piece: schema-DSL v1 parser, `toml_edit` roundtrip layer, and the form widgets together. The view architecture is settled (see "View architecture" above); build order is (1) schema parser and (2) the `Bind` seam with its `toml_edit` implementation, both pure and testable against `rio.toml.schema`, **both shipped**; (3) `AlloyForm` + `AlloyField` + `TextField` promotion as the `alloy_tui` 1.2 release, **shipped**; (4) tab chrome and the Applications tab, **shipped**; (5) per-field edit plus save and the quit-confirm, **shipped**, then the filterable pick overlay, **shipped**, which was the last field type that could not be changed; (6) the System tab, time rows first, which is the smallest complete slice of the command side: one front, one enum, one bool, one display-only row; (7) the remaining System rows; (8) presets and live diagnostics, the collapsible sections having landed with the tab; (9) the text-edit fallback, save and dirty tracking having landed with the edit; (10) `AlloyTable` read-only.
190 192 - **Blocked on the target machine.** `alloy display` fronts `swaymsg`/`wlr-randr`, neither of which exists on a non-sway development box, so its parser cannot be checked against real output here — the way the two box-parser bugs got written. The rpm-ostree tabs were in this category until their output was captured from a booted Alloy install in QEMU (2026-07-22); the `install` and `system` parsers are written against that real capture and re-checkable with `parses_this_machines_real_status` on any ostree box. `alloy display` still wants the image or real hardware.
191 193 - **Then.** `alloy sync`, `alloy theme`. `alloy theme` swaps the runtime theme in place (makeover consumer, no re-login). First-boot flow (see [CONTINUITY.md](CONTINUITY.md)) is a thin shim over `alloy mesh` and `alloy sync` enrollments.
192 194 - **v1.x.** Additional adopted-tool schemas as the v0 stack grows. (`alloy hinged` was shelved with the FW12 tablet flow in the pivot.)