# bokf — project state Settled decisions live in `docs/DECISIONS.md`; this file is the live snapshot. Read with `AGENTS.md` (rules) and `docs/TUI-GUIDELINES.md` (UI conventions). ## Status Working self-hosted bookkeeping system, not production-proven. Backend ledger core is complete, as are invoicing, the payroll engine and the payroll TUI; filing/year-end work remains. TUI is usable and exercised by `make test-pty` (golden screen-text scenarios, now including the payroll run and the lönebesked); `make test` covers the server/protocol/ledger, the TUI widget unit tests and the docs consistency check. ## Resume here (2026-09-22) - **Deployed**: `v0.1.64`, healthy on `nas` (Alpine runtime, static aarch64 binaries cross-compiled on this machine — ~20 s, the host only assembles the image; image 33.7 MB, no `libssl3`). `v0.1.55` brought the payroll server waves (schema v11: employees, tax tables, lönebesked) and the payroll TUI; `v0.1.56` the **Bolaget** hub; `v0.1.57` the flat main menu without section headings, the **System** hub (Skattetabeller, Revision) and the client-side remembered attachment directory; `v0.1.58` loads the system CA bundle explicitly so the static binaries can verify TLS (the Skatteverket fetch, SMTP, static clients); `v0.1.59` adds the keyboard protocols (though gnome-terminal/VTE cannot send Ctrl+Enter); `v0.1.60` the per-feature voucher series (Bolaget → Verifikationsserier, IB configurable with legacy `IB` still recognized), Nästa fakturanummer in Fakturauppgifter and `F9`-only save hints; `v0.1.61` lets underlag be granskade (text in a pager, other files in the desktop viewer) or nedladdade from the voucher detail and the inbox; `v0.1.62` brings invoice **text rows**, `u = duplicera`, `b = kvittera betalning` (`invoice.pay` links a payment voucher) and rejects name-like `smtp_from` values (schema v12: `invoice_rows.is_text`, `invoices.paid_date`/ `payment_voucher_id`); `v0.1.63` prints the organization name in the invoice and lönebesked headers and adds the shared document colour setting `document_header_color`; `v0.1.64` is the TUI interaction-model spec (docs only, no runtime change). The live database migrated v9 → v11 on the v0.1.55 startup and v11 → v12 on the v0.1.62 startup, each with the automatic pre-migration snapshot in `var/db/backup/`. `main` and the tags are pushed to `nas`. - **Invoices (2026-09-22)**: the follow-ups are done and deployed in `v0.1.62` — free-text rows, `u = duplicera`, `b = kvittera betalning` (`invoice.pay`, strict receivable check) and `smtp_from` address validation. `test_core` has `invoice_extras`; the pty suite has `invoice-duplicate` and `invoice-pay`. Remaining invoice work is in the backlog (credit notes, per-row account, kundreskontra, partial payments). - **Document header (2026-09-22, deployed in `v0.1.63`)**: the invoice header bar prints the organization name instead of the hard-coded `MAKANDRA AB` outline (scaled down and truncated with `...` if long). The shared document setting `document_header_color` (`#rrggbb`, default `#314c59`, editable in Bolaget → Fakturauppgifter) colours the header bar of both the invoice and the lönebesked, and the lönebesked prints the employer name there too (it previously used the Comfortaa wordmark). The invoice's `FAKTURA` outline is unchanged; the `MAKANDRA AB` outlines in `src/wordmark.h` are now unused at runtime. - **TUI interaction model (2026-09-22, design, not implemented)**: the UX session settled two focus modes, three entity lifecycles, explicit `Spara` in every savable form and `` drafts persisted to `$XDG_CACHE_HOME/bokf/drafts.json` (deletable from both the list and the editor), plus one `tui_action` registry per screen behind `F2` (no `§`). Spec in `TUI-GUIDELINES.md` "Interaction model"; decisions in `DECISIONS.md` #28. Rollout is backlog item 17. - **Mail configuration**: Makandra AB (org 2) has **no** `smtp_*` settings in bokf, so `invoice.send` there is `SMTP_NOT_CONFIGURED` (fine if invoices are sent elsewhere — set them up when wanted). Mock AB (org 1) has host/port/user/password but its stored `smtp_from` is the name "Anders Bergsten": change it to an e-mail address in **Bolaget → E-post (SMTP)** before sending; the new validation refuses names on save. - **Next tasks (payroll follow-ups)**: fold `payroll.settings_get/set` into `settings.get/set` (cmd_settings.c was busy during wave 1); settle the over-80k % rule (SKV 433 leaves it ambiguous — currently a clear `INVALID_ARGS`); move `invoice.send` to the shared `src/mail.c`. The TUI has no screen for the payroll account/rate settings yet (they are defaults and can stay server-side until the fold). - **Done in the TUI wave**: `clients/screens_payroll.c` with Lönekörningar (list + Ctrl+N, run screen with F5 preview, Ctrl+Enter post after confirmation and the action rows Lönebesked, AGI-underlag, Betala skatt & avgifter), Anställda under Bolaget and the Skattetabeller fetch/import/status screen under System. A pty scenario posts a run and fetches its lönebesked, so the payslip PDF path is exercised end to end. - **Menu (2026-09-21)**: the dashboard is one flat list without section headings: Verifikat, Underlag, Bankavstämning, Mallar, Fakturor, Lönekörningar, Rapporter, Bokslut, Bolaget, System, Ingående balans, Räkenskapsår, Logga ut. **Bolaget** is the master-data hub (Företagsuppgifter, Fakturauppgifter with Nästa fakturanummer, **Verifikationsserier**, E-post (SMTP), Styrelseledamöter and the registers Anställda, Kunder, Momsregler); **System** holds Skattetabeller and Revision. Inställningar is gone, and bilagornas mapp is no longer a setting: the file browser remembers the last pick directory in `tui.conf` and falls back to `$HOME`. `--screen settings` remains an alias for Bolaget. ## Open decisions None open. Completed items that used to be listed here are archived in `docs/DECISIONS.md`. ## Backlog (prioritized, from COMPLIANCE.md §10 and the audit) 1. ~~eSKD file generation~~ done: `report.vat_eskd`. 2. ~~Bokslut automation~~ done: `bokslut.post` + TUI Bokslut screen. 3. ~~K2 årsredovisning draft and SRU files~~ done: TUI draft + `sru.export`. 4. ~~`audit.verify` voucher chain~~ done (also attachments with `full:true`). 5. ~~`report.general_ledger` / `report.voucher_list`~~ done. 6. ~~`describe` argument schemas~~ done (`CMD_ARGS`). 7. ~~Pre-migration `VACUUM INTO` snapshot~~ done. 8. ~~Docker image + compose~~ done (no registry, no systemd unit). 9. Password change, user disable, TOTP. 10. ~~Bank import/reconciliation (CSV first)~~ phase 1 (SEB CSV + matching, schema v8) and phase 2 (Ctrl+N prefills the voucher and auto-matches it, `9b1021e`) done. Phase 3 (`bank_rule.*`) **dropped by the user 2026-09-21**; PSD2 only if it ever hurts. 11a. Imported history years with source P&L closings: decided, no importer change (see DECISIONS.md). 11. ~~SIE import only into an empty fiscal year~~ chronological multi-year import works; each year must still target an empty fiscal year. 12. TUI polish: horizontal scrolling in long text fields, bracketed paste. 13. ~~Payroll TUI (wave 3)~~ done (`clients/screens_payroll.c`); the remaining payroll follow-ups are listed under "Resume here". No employee is registered yet in the real orgs. 14. ~~Invoice text rows, duplicate and payment registration~~ done in `v0.1.62` (schema v12). Remaining invoice follow-ups when needed: credit notes (`invoice.credit`), per-row account in the invoice form, kundreskontra view, partial payments. 15. `make test-pty` speed if it ever exceeds ~60 s: parallelise the independent scenarios (each has its own rig) and add `--only` symmetry; measure first. 16. Test fixtures (`t_fresh_org()`) + one test file per domain (`tests/core_.c`) so `--only` stops cascading; pilot with one domain. 17. Interaction-model rollout (spec: `TUI-GUIDELINES.md` "Interaction model", decisions #28): `struct tui_action` + `F2` menu in the widget layer, `clients/drafts.[ch]`, the Kunder pilot, then the other registers; settings get `Spara`; pty scenarios for draft create/save/delete/reload. Original entries for the struck items are in `docs/DECISIONS.md`. ## Environment / how to run - **Deployed**: `scripts/deploy.sh` (latest `v0.1.64`, healthy on nas). Live daemon `tls:bokf.makandra.eu:8788`, token `~/.config/bokf/migration-token` (scopes `read,write`; owner-only actions like closing years must be done by the human in the TUI). Git remote `origin` is `nas:/mnt/data/git-repos/bokf.git` (push `main` and tags). `sh scripts/install-hooks.sh` once per checkout points `core.hooksPath` at `.githooks/`, so every push runs `make gate` (clean `-Werror` build in `build-gate/`, the suite and ASan); it is installed here. `deploy/Dockerfile.cross` + `deploy/cross-build.sh` produce static aarch64 `bokfd`/`bokfctl` in ~20 s when the host architecture differs; the image is assembled on the host from `.prebuilt` (see docs/DEPLOY.md). - **Local test rig** (transient, `/tmp`): daemon `./build/bokfd --db /tmp/opencode/bokf-local/t.db --socket /tmp/opencode/bokf-local/sock`, org 1, login `admin`/`testpass123`. Drive the TUI over a pty with `scripts/tui-sandbox.sh -- ./build/bokftui --socket /tmp/opencode/bokf-local/sock --org 1 --fy 1 ...` plus a small driver that feeds keys and an ANSI renderer (recreate if gone; arrows are `ESC O B/A`, Tab `\t`, `^X` `\x18`, `^Enter` `ESC[27;5;13~`, F5 `ESC[15~`). Never test against the live daemon. - Demo: db `~/bokf-demo/bokfd.db`, socket `~/bokf-demo/bokfd.sock`, pid file `~/bokf-demo/bokfd.pid`; login `admin` / `demo1234`. Start TUI: `cd ~/work/bokf && BOKFD_SOCKET=$HOME/bokf-demo/bokfd.sock \ BOKFD_USER=admin BOKFD_PASSWORD=demo1234 ./build/bokftui` - Restart daemon: kill the pid file's process, then `BOKFD_BACKUP_DIR=$HOME/bokf-demo/backup \ BOKFD_EXPORT_DIR=$HOME/bokf-demo/export setsid nohup \ ./build/bokfd --db $HOME/bokf-demo/bokfd.db \ --socket $HOME/bokf-demo/bokfd.sock > $HOME/bokf-demo/daemon.log 2>&1 &` - The user's own early instance was `/tmp/x.db` + `/tmp/bokfd.sock` (schema v1, old binary) — recreate or migrate it with the current build if it is still wanted. - TUI smoke tests: drive over a pty with `script -qec`; function-key escape sequences are timing-sensitive there (not an app bug). Arrows arrive as application-mode sequences (`ESC O B` for Down), not `ESC [ B`, because curses enables the keypad. `Ctrl+N/C/F` are single bytes and reliable. Always wrap the run in `scripts/tui-sandbox.sh -- ./build/bokftui ...`: it isolates `XDG_CONFIG_HOME`/`XDG_CACHE_HOME` so a test can never overwrite the real `~/.config/bokf/tui.conf` or `~/.cache/bokf/tui.log`. ## Known caveats - Saving with Ctrl+Enter needs a terminal that speaks xterm `modifyOtherKeys` level 2 or the Kitty keyboard protocol; gnome-terminal/VTE sends neither, so the hints advertise `F9`, which works everywhere. - Developer tooling: the `g_cmd_[]` tables in `src/cmd_*.c` carry declarative argument schemas (`CMD_ARGS`); `describe` emits them and the dispatcher validates before the handler runs. `make check` (part of `make test`) fails when a command or error code is missing from `PROTOCOL.md` (`scripts/check-consistency.sh`) or when the generated command catalogue is stale (`make gen-protocol`). `make test-asan`/ `test-ubsan` build `test_core` with sanitizers; `make test-pty` runs `scripts/tui-golden.py` against a throwaway `/tmp` daemon; `make gate` is the pre-push check (clean `-Werror` build in `build-gate/` + tests + ASan). - Never commit unless the human asks. - SQLite files must not be backed up live with restic; use `backup.snapshot` (`VACUUM INTO`) and point restic at the snapshots. - Schema version is 12 (v3 moms rules; v4/v6 year info; v5 org description/shares + board members; v7 attachments append-only triggers; v8 bank reconciliation; v9 invoicing + widened `vouchers.source` with a table rebuild; v10 payroll + `payroll`/`payroll_tax` sources, same rebuild; v11 `employees.email`; v12 `invoice_rows.is_text` and `invoices.paid_date`/`payment_voucher_id`); forward migrations are in `db.c`. ## Makandra driftstatus (org 2) - **Org**: Makandra AB, org 2. Räkenskapsår (id): 2022=3, 2023=4, 2024=5, 2025=6, 2026=7, **2027=2 (öppet, aktuellt)**. Bokslut/AR/deklaration görs för det år som är valt i sessionen. - **FK2027**: importerade Kapitas-böcker + 30 bokförda verifikat (V21–V50) för bank/skatt maj–sep 2026: V49 makulerar en dubblett (V20) och V50 är Hetzner-förskottet (nedan). 1930 stämmer mot banken utom **CDON 2 409 kr** (väntar på kvittots del 2–4; bokförs när det kommer) och Hetzner-kortköpet 2026-09-21 som ännu inte är importerat. 1630 = 40 721 (exakt enligt Skatteverket). - **Hetzner-förskott (2026-09-22)**: V50, datum 2026-09-21 (bankens bokföringsdatum), "Förskott Hetzner 100 EUR": **D 1790** 1 156,39 / **K 1930** 1 156,39, med båda PDF:erna som underlag. Matchas mot banken när nästa SEB-fil (efter 2026-09-18) importeras. Förbrukningen bokförs när Hetzner-fakturorna kommer (se reglerna nedan). - **Underlag**: 309 attachment i org 2 (alla historikdokument + insamlade underlag, inkl. de två Hetzner-PDF:erna). Bank-/SKV-utdrag ligger i `~/Makandra AB/{bank,skatteverket}` (Syncthing), källkorpus i `~/Downloads/Makandra AB-…/Bokföring/`. - **Stängning**: 2022–2026 ska stängas av ägaren via **Räkenskapsår** i TUI:n; låt FK2027 vara öppen till nästa bokslut. - **Deklaration**: FK2026 är deklarerad av revisorn. FK2027 deklareras våren 2027 (INK2/SRU via Bokslutshubben → Inkomstdeklaration). - **Årshäftet**: fylls i Bokslutshubben (händelser, stämma, utdelning + datum, medelantal, noter). OBS: `dividend_ore` för FK2027 kan vara ett testvärde (10 000) — kontrollera före AR/deklaration. - **Beslut/regler från bokföringsarbetet**: inga bokföringar utan godkännande; låsta år förblir låsta (rättelser görs i aktuellt år); SIE- importören ändras inte och importerad data "manipuleras" inte; historikårens P&L nettar noll pga källsystemets stängningar (AR hoppar över "Stäng"-verifikat i flerårsöversikten); utdelning bokförs vid stämman med mallen **Utdelning** (D 2099/K 2898); pappersoriginal finns i fysisk pärm (får refereras i efterhand, även i stängda år); leverantörsförskott och förbrukningssaldo (Hetzner) bokförs som **D 1790/K 1930** vid påfyllning och **D 6540 + D 2645 / K 2614 / K 1790** vid förbrukning; kortköp bokförs på **bankens bokföringsdatum** (lättare att härleda och matcha vid import). - **Lön (2026-09-21)**: bara ägaren är anställd, men schema och kommandon är fleranvändarklara. Lönemotorn (schema v11: anställda, skattetabeller, lönekörningar, lönebesked + mejl) och TUI-sektionen Lön är klara, men **ingen anställd är registrerad än** (personnummer, lön, skattetabell och kolumn fylls i under Register → Anställda). Konton enligt historiken: 7210/7510/2710/2731/1630. Skattetabellerna hämtas med en knapp från Skatteverket (varning när året är gammalt), personnumret är krypterat i databasen. Ingen semesteruppbokning; arbetsgivardeklarationen (AGI) görs manuellt med underlaget från `payroll.agi`.