Serve the TUI's screens as a web app

money serve puts the seven screens in a browser: accounts, transactions, the
report with its period axis and sort, the rule builder with its live preview
and edit-in-place, the rules list, the transfer builder with its tolerance
preview, and the transfers list. Import and retag are buttons in the header.
It is one binary: the page is plain HTML, CSS and JavaScript embedded with
go:embed, with no framework and no build step.

It is a second frontend, not a second implementation. Amounts are formatted on
the server, the rule preview is matched by glob.Match there, and the transfer
preview runs transfers.Analyze, so the browser only lays out answers and
cannot drift from the TUI on what a rule catches or what a pair costs.

The server outlives hand edits to rules.toml in a way the TUI does not, so
retag and import re-read it first, as a fresh `money retag` or `money import`
would, and the overview says when the file on disk no longer matches what the
index was derived from. Edits and deletes still go by position, but carry the
rule they showed and are refused unless rules.toml still holds exactly that
there; a position from a stale tab could otherwise name a different rule. An
edit takes the type pattern from the rule on disk, never from the request.

There is no authentication, by request. It listens on loopback unless --addr
says otherwise, and writes must be sent as JSON so a cross-site form cannot
post to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 09:48:56 +02:00
co-authored by Claude Opus 5.5
parent 7d4c3eba17
commit 6faf99719a
8 changed files with 2793 additions and 0 deletions
+43
View File
@@ -60,6 +60,8 @@ compiles only the files listed, which breaks the moment the package has two.
```
money # open the TUI (default)
money serve # the same screens as a web app, on 127.0.0.1:8080
money serve --addr :8080 # listen on every interface (there is no login)
money import # extract new transactions from every statement
money import --force # re-parse statements even if unchanged
money retag # re-apply rules.toml: retag, and re-pair transfers
@@ -408,6 +410,47 @@ after a `y`. Definitions marked `⚠` are never pruned — they are doing someth
just not finishing it, and deleting one would hide the problem rather than fix
it. `r` re-pairs against what is currently in the index.
## Web app
`money serve` puts the TUI's seven screens in a browser — accounts,
transactions, report, rule builder, rules, transfer builder, transfers — with
the same behaviour, because it runs the same code: amounts are formatted, globs
matched and transfers paired on the server, and the page only shows the
answers. It is one binary with the page built in; nothing else to deploy.
```
money serve # http://127.0.0.1:8080
money --root /srv/money serve --addr 0.0.0.0:8080
```
**There is no authentication.** Anyone who can reach the port can read every
transaction, rewrite `rules.toml` and start an import, which is why it listens
on loopback unless `--addr` says otherwise. To use it from elsewhere, put it
behind a reverse proxy that does the logging in, or reach it over SSH / a VPN.
Requests that change anything must be sent as JSON, so another website open in
the same browser cannot post a form to it.
What differs from the TUI:
- **Retag and Import re-read `rules.toml`** (and Import the account folders)
before running, exactly as a fresh `money retag` / `money import` would — the
server outlives hand edits to both. Until then a banner says the file on disk
no longer matches what the index was derived from.
- **Edits and deletes check the file first.** They go by rule position, as in
the TUI, but the page also sends the rule it showed you; if `rules.toml` no
longer holds that rule there (another tab, a hand edit) the change is refused
and you are asked to reload, rather than editing whichever rule moved into
its place.
- Clicking a description in the rule builder's preview fills the glob with
`*THAT DESCRIPTION*`, as a starting point to narrow down.
- Shift-click **Import** for `import --force`.
The keys still work where they do not fight the browser: `1`–`7` switch
screens, `/` searches, `u` toggles untagged, `a` clears the account filter, `i`
imports and `r` retags. On the report `←`/`→` move the period and `s` cycles
the sort (or click a column heading); in the rule builder `ctrl+s` re-sorts the
preview. Inside a form every printable key belongs to the field, as in the TUI.
## rules.toml
**The most specific rule wins**, so `*NIKOLA*` claims what it names even with a