Release: one log page, grouped dishes, live search (#1)

Structure
- Kirjaa and Historia are one page. They were two views of the same thing — every history row already linked into the logger, and the logger had a day switcher. Two tabs instead of three. Also closed a gap: on an already-logged day there was no way to swap to a different dish, only to re-pick its sides.
- Ruoat → Ruuat, label and route.
- The catalog has a structure. It had no top-level headings at all — the mains simply began with "Liha". Both halves now carry a heading and a count, categories are visibly subordinate, and the add/edit forms collapse instead of filling the screen before any content.

Finding things
- Dishes grouped by category on both screens, Sekalaiset for multi-category ones. Derived from the stored set, not a fifth category, so one Tortillat still covers all four for the §8.1 suggester later.
- Live search on both lists, 250 ms after typing stops. Both remain plain GET forms, so they still filter with JavaScript off.
- History is paged 30 days at a time — it previously rendered every day back to the first entry, forever.

Correctness
- Future meals refused. The picker offered them and ?pvm= accepted them.
- today() wasn't midnight, so it never equalled a date parsed from ?pvm= — after saving, the card read "la 5.9. kirjattu" instead of "Tänään kirjattu".
- Deletes ask first, for dishes and logged meals. The meal is the more destructive: a dish is only soft-deleted.
- DB open failures name the path and uid, instead of unable to open database file (14).

Visual
- Category icons replace colour dots — steak, drumstick, fish, leaf, quartered circle.
- Row actions are a pencil and a bin; the header has a surface.

Housekeeping
- Datastar SDK dropped — one JSON decode was pulling in four modules including an HTTP compression stack. Five lines replace it.
- Release policy documented: main protected, releases arrive as PRs.

Co-authored-by: Esa Kataja <[email protected]>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-09-05 19:28:54 +00:00
co-authored by Esa Kataja
parent eb95bd0a03
commit e9754488db
13 changed files with 1194 additions and 264 deletions
+49 -13
View File
@@ -17,18 +17,29 @@ weeks of real history to weight against.
Working:
- **Kirjaa** — log a dinner: pick a dish, tick sides, save. Dishes are sized
by how often they are eaten. Edit or delete the day's entry.
- **Historia** — every day back to the first entry, with unlogged days shown
as explicit gaps.
- **Ruoat** — add, edit and delete mains and sides, or import a whole bundle
by paste or file upload. Deletes are soft, so old log entries keep showing
the dish they used.
- **Kirjaa** — log a dinner: pick a dish, tick sides, save. Dishes are ordered
and sized by how often they are eaten, so the likely answer is the biggest
target. The history sits on the same page underneath: every day back to the
first entry, unlogged days shown as explicit gaps, and every row a link that
loads that day into the logger above it.
- **Ruuat** — add, edit and delete mains and sides, or import a whole bundle
by paste or file upload. Grouped by category and alphabetical inside, since
this is a list you manage rather than one you pick from. Deletes are soft,
so old log entries keep showing the dish they used.
- **Light / dark**, remembered per device, dark by default. The button shows
the theme that is on — moon while dark, sun while light — not the one a
click would bring.
Still to build:
- Live search as you type, and paging for the history and catalog lists once
years of entries make them long. Both via Datastar.
- Category icons instead of plain colour dots — colour and shape together, so
a red blob and a yellow blob are told apart by more than hue.
- Edit and delete as icons in the catalog rows, and a confirmation step before
a delete actually happens.
- A background for the header. Something subtle; the palette gets overhauled
later.
- Stage 2: the seven-meal suggester, which starts once there is history to
weight against.
@@ -50,16 +61,31 @@ One static Go binary. No Node.js, no bundler, no separate database server.
## Branches
`main` holds released versions only. Every release tag points at a commit on
`main`, so its history is the deployment history.
`main`, so its history is the deployment history. **Nothing is committed to
`main` directly** — it moves only by fast-forwarding `dev` into it.
All development happens on `dev`. Merge into `main` when cutting a release,
then build and push the image from there.
All development happens on `dev`. `main` is protected on the remote: it takes
no direct pushes, so a release arrives through a pull request.
```sh
git switch dev # where the work happens
git switch main && git merge dev && make release
git switch dev # where the work happens
# ... commits ...
make check # lint, unit tests, smoke
git push origin dev
tea pr create --base main --head dev # or open it in the forge
# merge the pull request, then:
git switch main && git pull --ff-only
make release # builds, tags vYYYYMMDD-N, pushes the image
git push origin --tags
```
`make image` additionally refuses to run from any branch but `main`, so a
release tag can never point at a commit that was not released. That check
lives locally because it has to: tags and images are built before anything
reaches the remote, so protection there cannot catch it.
## Quick start
```sh
@@ -84,7 +110,7 @@ make up/down/logs compose
## Importing dishes
The **Ruoat** tab takes a bundle of mains and sides: paste the JSON or upload
The **Ruuat** tab takes a bundle of mains and sides: paste the JSON or upload
a file, and the app reports row by row what it did.
```json
@@ -181,6 +207,16 @@ That directory must exist and be owned by the user compose runs as — `make up`
creates it, and `FOODSTER_UID`/`FOODSTER_GID` in `.env` tell the container who
that is. Get them from `id -u` and `id -g`.
If the app exits with `cannot open /data/foodster.db ... unable to open
database file (14)`, the ownership does not match. Docker creates a missing
bind-mount directory as root, and the container is not root:
```sh
ls -ldn data # whose is it?
sudo chown -R 1000:1000 data # match FOODSTER_UID / FOODSTER_GID
docker compose restart
```
## Security
Access is a single shared password over HTTP Basic — no accounts, no