# bokftui guidelines Rules for the ncurses client so every view behaves the same. When in doubt, copy the behaviour of the voucher list / voucher form; they are the reference implementations. Inspired by Midnight Commander, htop, mutt and calcurse. ## Session start After login the org picker ("Välj organisation att representera") is always shown; the selected org is fixed for the session (`--org ID` bypasses it for scripts). The fiscal year is chosen from the dashboard and is changeable during the session. ## Universal keys | Key | Meaning | |---|---| | `Ctrl+N` | Add: new verifikat (dashboard, voucher list/detail), new mall (Mallar), new fiscal year (year picker), open editor (IB), upload file (Underlag) | | `F5` | Refresh the view | | `Esc` / `q` | Back one level. At the dashboard it does nothing — Esc never exits the app | | `Ctrl+C` | Quit the application (closes the session). The only key that exits | | `1`–`9` | In lists: jump to that row. In menus: activate that item | | `g` | Live goto: "Gå till rad/nummer:" updates the selection as you type digits (backspace steps back); Enter closes the prompt without opening anything | | arrows, PgUp/PgDn, Home/End | Move/scroll; selection always stays visible | | `e` | Edit the shown object (IB, where applicable) | | `a` | Add/upload (Underlag) | | `c` | Correct (voucher detail) | | `Ctrl+F` | Attach a file (voucher form) / pick a file (Underlag) via the file browser | | `F7` | Clear the current row — only inside row editors (never "new") | | `F9` | Save/post the current form | Every screen prints its keys in the footer via `hints()`. If a key exists, the footer shows it; if the footer shows it, the key works. ## Lists (`select_list`, `menu`) - Rows are numbered `NN. text`, right-aligned so 2- and 3-digit numbers line up. - Verifikation ids are shown concatenated as `series+number` (`V-8`, `A8`), using the org's `default_series` (Inställningar) for new vouchers. - The last row may be an action (e.g. `+ Nytt verifikat (Ctrl+N)`); selecting it runs the action instead of opening a detail view. - Selection memory: lists remember the selected row by identity (voucher id), not index, across detail round-trips, refreshes and screen re-entry. - Digits move the highlight; only Enter activates. Menus are the exception: digits activate directly (they are shortcuts). - Empty lists are never a dead end: show a message or keep the add-row. ## Forms - Layout: bold header fields at the top, then a bold column header, then rows; footer hints carry the keys. - Field navigation: `Tab` / `Shift-Tab` forward/back, arrows Up/Down between rows. `Enter` advances in simple forms; it never saves unless the screen is a one-line prompt. - The active field is drawn reverse-video and holds the hardware cursor at the caret. The caret is a real position: ←/→/Home/End/Del work inside the field, `Ctrl+U` clears it (see `field_edit`). - First keystroke in a freshly focused field replaces its content (`field_fresh`), so prefilled values like dates can be typed over. - Date fields (`date_field_edit`, `date_prompt`) accept digits only and insert the dashes themselves: type `20260315` and the field shows `2026-03-15`. Backspace deletes a digit (with its separator), ←/→/Home/End move by digit, and the caret renders like in any other field. Validation (`util_parse_iso_date`) happens on F5/F9. - Derived values (account names, balances) are dim and non-editable. - Row tables: always exactly one empty trailing row; entering data appends a new empty row; an empty row followed by another empty row collapses. `F7` clears the selected row. - Validation: `F5` validates without writing and reports exactly what is wrong (field, row number). Server `dry_run` is used where available. - Saving: `F9` writes, shows a confirmation message, and returns to the previous view. `Esc` cancels without saving. - File browser (`file_browser`): starts in the org's `attachment_dir` (or `$HOME`; a leading `~` is expanded to `$HOME`), `.. (uppåt)` is the first row, directories sort first with a trailing `/`, hidden files are skipped. Enter enters a directory or picks a file; `Esc` cancels. Selected files are uploaded immediately as unlinked underlag and linked when the voucher is posted. ## Messages - Info/confirmation: `message(title, ...)` box, dismissed with Enter. - Quitting: only `Ctrl+C` (from anywhere) or "Logga ut / avsluta" ends the app; `Esc`/`q` only navigate. After `Ctrl+C` every screen unwinds, the session is closed and the terminal restored. - Errors: `show_error(title, resp)` prints `CODE: message` from the server error object; if the response is missing/empty it says "Inget svar från servern (kör daemonen?)". Never render an empty error. - Irreversible actions (close year, lock, archive) ask first. ## Layout and text - Screen frame: title top-left, dim status line under it (`org | label start - end | role | user`), hints in the last line. - Columns are padded with `pad_field()` (UTF-8 display width) and truncated to their column; amounts right-aligned via `kr_format()`; never pad with `%s` widths directly on user text. - Selection = reverse video, headers = bold, derived data = dim. - All user-visible TUI text is Swedish; server messages are English. ## Adding a view — checklist 1. Data comes from public protocol commands only. 2. Wrap the screen in `frame()`/`hints()`; return `Esc`/`q` to the parent. 3. Use `menu()` or `select_list()` instead of writing a new loop; pass `allow_new`/`allow_refresh` so the universal keys apply. 4. Forms use the shared editor (`field_edit`) and the row-normalising helpers. 5. Support `F5` if the data can change elsewhere. 6. Update `PROTOCOL.md` §8 and this file if you add a new key or interaction.