| 82 |
82 |
|
- **Live validation.** Type errors, out-of-range values, missing required keys, and unknown keys all surface in the form as inline diagnostics rather than on save.
|
| 83 |
83 |
|
- **Documentation inline.** Schema entries carry a description string that shows in the form as help text next to the field.
|
| 84 |
84 |
|
|
|
85 |
+ |
### View architecture
|
|
86 |
+ |
|
|
87 |
+ |
Settled with the crate/binary split the rest of the console already follows (see [COMPONENT-LIBRARY.md](COMPONENT-LIBRARY.md)): `alloy_tui` renders dumb themed field widgets and form chrome, and the binary owns the schema parser, the `toml_edit` document, validation, presets, and save. The pieces:
|
|
88 |
+ |
|
|
89 |
+ |
- **Values live in a `toml_edit::DocumentMut`**, loaded from the target file. That document is the single source of truth and the thing edits are written into in place, which is what makes the roundtrip-safe promise structural rather than remembered. Fields display their schema `default` when the file omits the key, but the key is not materialized on disk until the user edits it, so a minimal config stays minimal.
|
|
90 |
+ |
- **Editing is modal per field.** `mode: Navigate | Editing { row, buffer, original }`. In Navigate the reserved keymap holds (Tab / j-k move, Enter activates, Space folds a section or flips a bool, Ctrl-S saves). Enter on a string, number, or color field opens Editing, where the field owns every key. This is the case the classifier already documents: a view holding an active text input must not treat `q`, `/`, or `:` as reserved. Esc discards the buffer, Enter validates and commits; an invalid value stays in edit with its diagnostic shown rather than being written. Enums do not free-type: Enter opens a pick overlay (the `AlloyModal` + `AlloyList` idiom) listing each value's label and description and committing the raw value. Bools toggle in place.
|
|
91 |
+ |
- **Sections are collapsible.** One pane per schema `[[section]]`, folded with Space on its header, so the 25-slot colors group defaults to a single collapsed row rather than dominating the form. The binary flattens the visible rows each frame (section headers plus the fields of open sections); the `Cursor` rides that.
|
|
92 |
+ |
- **Presets apply atomically.** A `[[preset]]` writes its whole `values` map into the document in one pass: one dirty increment, one undo entry. Undo is single-level in v1. Applying logs as `apply preset "..."`.
|
|
93 |
+ |
- **Save and quit.** Ctrl-S serializes the document back to the target path, logged as `write <path>` (the same `Effect::Write` shape the `alloy pkg` export wrapper established). Quitting with unsaved edits confirms through `AlloyModal`, reusing the Cancel-that-is-not-Quit machinery `alloy pkg` forced into the shell.
|
|
94 |
+ |
- **List-of-tables is read-only in v1.** `type = "list"` records (rio's `bindings.keys`) render through a display-only `AlloyTable`; add, remove, and cell-edit route to the text-edit fallback. Cheap to defer because rio ships bindings empty (Sway owns the global binds). Full table editing is v1.1.
|
|
95 |
+ |
- **Fallback.** No schema, an unknown `schema_version`, or an unknown field type routes the whole file to the syntax-highlighted text-edit pane with a diagnostic explaining why. KDL configs take this route until the open roundtrip-safe-KDL question below resolves.
|
|
96 |
+ |
|
| 85 |
97 |
|
## Schema strategy
|
| 86 |
98 |
|
|
| 87 |
99 |
|
Three options considered. Committing to option 2 with 3 as opportunistic input.
|
| 133 |
145 |
|
Contents (v1). Shipped:
|
| 134 |
146 |
|
- Themed `ratatui` widget wrappers: `AlloyBlock`, `AlloyList`, `AlloyStatusBar`, `AlloyLog`.
|
| 135 |
147 |
|
- `AlloyConnector`, which draws the link between two panes. Added for `alloy audio`'s stream-to-device pairing and not in the original roster.
|
|
148 |
+ |
- `AlloyTabs` and `AlloyModal`, forced in by `alloy pkg`'s three-tab view and its first destructive-action confirm.
|
| 136 |
149 |
|
- `keys`: the reserved keymap (Tab, Shift-Tab, Enter, Esc, Ctrl-S, q, plus `?`, `/`, `:`) and the classifier apps match against.
|
| 137 |
150 |
|
- `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.
|
| 138 |
151 |
|
- Footer chrome and the theme layer, ported from mountaineer-sysop's `sysop-tui` and retinted from a runtime theme rather than a const palette.
|
| 139 |
152 |
|
|
| 140 |
153 |
|
Still to come, with `alloy config`:
|
| 141 |
|
- |
- `AlloyForm`, `AlloyTable`.
|
| 142 |
|
- |
- Form-field widgets driven by the schema types above (`ColorField`, `EnumField`, `RangedNumberField`, etc.).
|
|
154 |
+ |
- `AlloyForm` (the collapsible-section form chrome) and a display-only `AlloyTable`.
|
|
155 |
+ |
- 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.
|
| 143 |
156 |
|
|
| 144 |
157 |
|
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.
|
| 145 |
158 |
|
|
| 157 |
170 |
|
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 config` 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.
|
| 158 |
171 |
|
|
| 159 |
172 |
|
- **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.
|
| 160 |
|
- |
- **Next.** `alloy config`, 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.
|
|
173 |
+ |
- **Next.** `alloy config`, 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, (2) `toml_edit` bind layer, both pure and testable against `rio.toml.schema`; (3) `AlloyForm` + `AlloyField` + `TextField` promotion as the `alloy_tui` 1.2 release; (4) the view wiring navigate and edit; (5) collapsible sections, presets, live diagnostics; (6) save, dirty tracking, quit-confirm, text-edit fallback; (7) `AlloyTable` read-only.
|
| 161 |
174 |
|
- **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.
|
| 162 |
175 |
|
- **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.
|
| 163 |
176 |
|
- **v1.x.** Additional adopted-tool schemas as the v0 stack grows. (`alloy hinged` was shelved with the FW12 tablet flow in the pivot.)
|