aboutsummaryrefslogtreecommitdiff
path: root/docs/gui-design.md
blob: d377bb251ba6d229b8202efe6c72f1e50957bdc1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# 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` also returns, per matching rule, the
  capture values, and the chain the file would get (each step's action,
  destination after placeholders and conflicts, skip reason), so the test
  pane shows `{1}=2026` and `→ Pictures/Screenshots/2026-09/`.
- **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.