# krino-gui design Status: draft, 2026-09-15. The window layout (B), the rules editor layout (A) and sections 1–4 were agreed from mockups; sections 5–6 are the author's defaults, to be corrected before implementation. Companion to `docs/design.md` (the engine spec, "the spec" below), whose §12 anticipated this GUI. `krino-gui` is a GTK4 window for what `krino` does in a terminal: review a directory's plan and apply the chosen files, look back over runs and undo them, and write rules. It adds no sorting behaviour of its own: every decision is the engine's, and a run made in the GUI is the same run, in the same log, that `krino log` and `krino undo` see. ## 1. Architecture ### 1.1 Where the code lives - A nested Go module `krino/gui` in `gui/`, with its own `go.mod` (`github.com/diamondburned/gotk4/pkg` v0.4.1, and `replace krino => ../`), building one binary, `krino-gui`. It imports `krino/internal/...`, which Go allows because the import path is under `krino` (verified with a probe module, 2026-09-15). - krino's own module, `make ci` and its dependency gate are unchanged: `go list ./...` at the root does not include `gui/`. - `make gui` builds `krino-gui` with `CGO_ENABLED=1`; `make gui-ci` runs gofmt, vet and the GUI module's tests; `make install-gui` installs the binary beside `krino`. GTK 4 development files and a C compiler are needed (present on the development laptop: GTK 4.18.6). - Platform: Linux with X11 on the development laptop. Other platforms are not built or tested in version 1. ### 1.2 Layers 1. `gui/internal/model` — plain Go, no GTK. The state of each tab and the operations on it (scan, select, apply, runs, undo, load and edit rules, check, test on file, save). It calls the engine and is where all logic and all tests live. 2. `gui/internal/ui` — GTK4 widgets. Renders model state, forwards user actions, runs model operations off the main loop and delivers results with `glib.IdleAdd`. No decisions are made here. 3. `gui/cmd/krino-gui` — flags (`-c FILE`, `--version`) and startup. ### 1.3 Changes to krino itself Made first, each tested through the CLI's existing tests as well as new ones: - **Session.** The directory orchestration now in `cmd/krino/sort.go` — lock per directory, opening the log and choosing the run id, planning with run-wide claims, applying the approved set, keeping only where files ended up as claims afterwards (spec §7.4) — moves into `internal/engine` as a `Session` used by both `cmd/krino` and the GUI. The CLI's behaviour and output do not change. Undo gets the same treatment (locks of the directories involved, log, run id). - **Load with overrides.** `engine.LoadWith(mainFile, overrides map[string][]byte, names...)` loads the configuration with some files' text replaced in memory, so unsaved editor text is checked exactly as a run would read it. - **Explain with outcomes.** `Explain` returns, per matching rule, the capture values; `ExplainWithChain` also returns the chain this file alone would get (each step's action, destination after placeholders and conflicts with what is on disk, skip reason), so the test pane shows `{1}=2026` and `→ Pictures/Screenshots/2026-09/`. A clash with another file of the same plan is resolved in the Plan tab, not here; the CLI's `explain` does not ask for the chain, since building it can read files. - **Printer and splice.** `internal/config` gains a printer for rule and exclude forms (two-space indentation as in `examples/`) and a splice that replaces one form's byte range (`sexp.Node.Pos`/`End`) and nothing else. `config.Rule` and `config.Exclude` record their form's end position. ## 2. Window One window, three tabs across the top: **Plan**, **History & undo**, **Rules** (layout B of the mockups). A status bar at the bottom shows the current operation, its progress and Cancel. Text from files, logs and tools is shown with plain-text setters only (never Pango markup), and control, bidirectional and line-separator characters are escaped as the CLI's `display` does (spec §15.1), so a hostile name cannot reorder or restyle what is shown. ## 3. Plan tab - A directory picker in the toolbar lists `include`; **Scan** plans it. Scanning takes the directory's lock, as a terminal review does; the lock is held while the plan is shown and released after Apply, on choosing another directory, or on closing the window. A held lock is reported in the tab, not waited for. While a plan is open, `krino -y` of that directory fails immediately, as with a terminal review left open. - The table (a virtualised `GtkColumnView`): checkbox, file, action(s), destination, rule. Rows start checked (as `[a]`); Select all / None. A file whose steps are all skipped shows its skip reason and has no checkbox. Files with a "content unreadable" or duplicate-check warning that no rule could decide are listed under "needs attention", never checkable. - A row's context menu: **Trash instead** and **Delete permanently instead…** (confirmation dialog naming the file), replacing that file's steps with the one step, as `t` and `d`/`y` do in review. - The right pane: the selected file's explanation — every exclude and rule with each test's yes/no/?, captures, and the planned chain. - **Apply N selected** runs in the background. Checked files are applied, unchecked ones logged as declined (as choosing per file in the terminal). **Cancel** is Ctrl-C: the step in flight finishes and is logged, then it stops. Rows then show done / failed (with the reason) / declined, and a summary line like the CLI's. - Not in version 1: watching the directory, a `--min-age` override, several directories in one Apply. ## 4. History & undo tab - Left: runs newest first (last 50, **Show more**), with time, directories, counts, `(undone)` / `(partly undone)`; undo runs labelled "undo of …". Selecting an undo run offers to finish the run it undid, as plain `krino undo` does. - Main: that run's undo plan — checkbox, file, reversal, target; refused files in red with the reason, not checkable; the header "N files · N to reverse · N refused". - **Undo N selected** takes the locks of the directories involved (a held one is reported), reverses checked files, logs unchecked ones as declined, runs in the background with Cancel (the file in flight is finished), then shows outcomes and refreshes the run list. - Not in version 1: raw log entries, searching runs. ## 5. Rules tab Layout A of the mockups: the directory's exclusions and rules on the left, a form in the middle, **Test on file** on the right; a **Text** sub-tab edits the whole directory file. Only directory files (`dirs/NAME.conf`) are edited; `krino.conf` is not, in version 1. ### 5.1 Forms - **Rule form:** name; conditions as a tree (all top-level conditions must hold, as `when`), with **+ test** (type, name, path, content, size, age, duplicate with optional directories, matched) and **+ and / or / not**; actions in order (copy, move, rename, delete, delete permanent, stop) with their argument; rule settings case, fold, on-conflict ("default" leaves them out). Placeholder help next to destination and name fields. - **Exclude form:** its conditions, as for a rule. - **Directory settings form:** path, recursive, max-depth, min-age, max-read, max-size, busy, case, fold, on-conflict, ignore patterns. - **Rule list:** add (after the selected rule), delete (confirmation), move up / move down. A rule's block for moving or deleting is its form plus the comment lines directly above it with no blank line between. - A form edit replaces only that form's text. If the form has comments inside it, saving a form edit warns that they will be dropped and offers the Text tab instead. ### 5.2 Text The whole file in a `GtkTextView` (monospace, line numbers), edited freely. Switching from Text to Forms re-reads the text; a text that does not parse keeps the Text tab until fixed. ### 5.3 Live check, test, save - After each change (300 ms debounce) the model loads the configuration with the unsaved text (`LoadWith`) and shows every diagnostic with line:col; in Forms a diagnostic inside a form marks that field when its position falls in it, otherwise it is listed above the form. A bar reads "✓ check: no errors" or the count. - **Test on file:** pick one of the directory's scanned files or any file under its path; runs `Explain` against the unsaved text, in the background (content extraction can take seconds), and shows the trace, captures and chain. - **Save** is refused while there are errors. It writes atomically (temp file, fsync, rename, mode kept, symlinks resolved as `krino new` does), after keeping the previous text as `NAME.conf.bak` beside it. If the file changed on disk since it was loaded, a dialog offers Reload, Overwrite or Cancel. **Revert** discards unsaved changes. ## 6. Safety, errors, tests - **Nothing changes on disk without a click on Apply, Undo, Save, or a confirmed delete-instead.** The GUI uses the engine's lock, log, Trash, exclusions and duplicate protection unchanged. - Engine errors appear in an info bar in the tab they belong to. A panic in a background operation is recovered, reported in a dialog, and leaves the tab as it was before the operation. - **Tests:** - `internal/model` against sandboxed homes (HOME and XDG_* under a temp directory), as the engine and CLI tests do: scan, select, apply, decline, trash/delete-instead, cancel; runs, undo plans, undo; load, edit, check with overrides, test on file, save (atomic, backup, external change), rule add/delete/move with attached comments. - Printer and splice: `parse(print(form))` gives the same form for generated forms (a fuzz target), and a splice leaves every byte outside the form's range unchanged. - Session refactor: every existing `cmd/krino` test stays green, plus a test that the GUI's session and the CLI produce the same log entries for the same plan. - GTK layer: no automated tests (no Xvfb on the development laptop); a written manual checklist, run on the real display with screenshots (`import`), before each GUI release. ## 7. Milestones 1. krino core: Session (sort and undo), LoadWith, Explain outcomes; CLI unchanged. Released as a krino version. 2. GUI module skeleton and the Plan tab. 3. History & undo tab. 4. Rules tab: Text sub-tab, live check, Test on file, Save. 5. Printer and splice; Forms (rules, excludes, directory settings; list add, delete, move). Each milestone ends working and tested; the GUI is reviewed and corrected after milestones 2, 4 and 5, from screenshots and use.