The favicon is a bowl of soup: this household's catalog is half keitto, so it
is at least honest. favicon.svg follows the system theme in the tab strip.
Home-screen icons cannot do the same. They must be opaque and must not change
with the theme, so assets/icon.svg is a separate fixed-colour source, with the
artwork inside the central 80% for Android to mask to any launcher shape. The
same 512 is declared maskable in the manifest.
`make icons` rasterises with rsvg-convert and compresses with
`oxipng -o max --zopfli`, which beat `optipng -o7` at every size — 5860 bytes
against 6109 for the three files. Plain `oxipng -o max` was not an upgrade: it
won the two small icons and lost the 512, ending up larger overall. All output
verified pixel-identical to the rasterizer. The PNGs are committed so the
build needs no rasterizer.
Two things that only fail on a real device, so both are covered by smoke
checks: Go has no mime entry for .webmanifest and served it as octet-stream,
which browsers ignore; and a manifest fetch carries no credentials by default,
so behind Basic auth it needs crossorigin="use-credentials" or it 401s.
The catalog could only be filled by importing JSON, which is a poor way to
add the one dish you are about to eat.
Ruoat now covers PRD §7.3 in full: add and edit mains with their categories
and has_sides, add and edit sides, and delete either. Deletes are soft, so a
log entry keeps resolving the dish it used and the freed name can be reused.
Validation messages are Finnish and the rejected form comes back filled in
rather than blank. Bulk import moves into a details element, since it is now
the occasional path rather than the only one.
Kirjaa gets the same ability without the detour: a search that finds nothing
offers to add what was typed, and saving creates the dish and continues
straight to the sides step. An empty catalog shows the same card instead of
dead-ending on a link to another tab, and the search box is no longer hidden
behind the empty state.
The importer's own insert is gone; it and the UI both go through createMain
and createSide, so duplicate detection lives in one place and reason() can
match on errNameTaken instead of poking at driver strings.
Stage 1's point is collecting eating history, so the logging path is the one
that has to be frictionless: pick a dish, tick sides, done.
Kirjaa:
- dishes ordered and sized by how often they have been eaten, so the likely
answer is the biggest target on the screen
- picking a dish opens the sides step; a dish with has_sides false says so
instead of offering an empty list
- saving redirects, so a refresh cannot double-post
- a day already logged shows the entry with edit and delete. Editing reopens
the sides step with the existing sides ticked, which makes editing and
creating the same screen
- day switcher and a server-side search over the catalog
Historia walks back day by day to the oldest entry, so a day nobody wrote
down appears as an explicit gap rather than quietly missing.
No JavaScript is involved: every interaction is a link or a form, and the
checked-chip styling is :has(input:checked). Datastar stays loaded but unused
until an interaction genuinely needs to avoid a page load.
Also fix a Makefile ordering bug: lint did not depend on generate, so `go vet`
could run against templ output that was being rewritten. check now runs its
phases as sub-makes so `make -j` cannot interleave them.
Records two features that are specified but not built: dish CRUD in the UI
(§7.3, only import exists today) and a per-device light/dark switch (§7.4).
The switch must show the state that is active, not the one clicking produces.
SQLite writes three files — the database plus its -wal and -shm companions.
Putting them in one directory means a deployment mounts a single path and a
backup copies a single directory.
- FOODSTER_DB defaults to ./data/foodster.db, and openDB creates the parent
directory on startup rather than failing on a fresh checkout
- compose bind-mounts ./data instead of using a named volume, so the file can
be listed, copied and opened with any sqlite client without going through
the container engine
- the image runs as UID 65534, which cannot write to a host directory owned
by someone else, so compose now sets user: from FOODSTER_UID/FOODSTER_GID
- `make up` creates ./data first: left to the engine it appears root-owned
and the app silently cannot write to it
Bring up the stage 1 skeleton described in the PRD, enough that the app
builds, serves, and can be populated with dishes.
- net/http server with shared-password Basic auth, /healthz outside it,
graceful shutdown, and TZ-aware calendar days
- SQLite via modernc (pure Go, static binary), opened with WAL and a single
connection
- migration runner: numbered SQL files embedded and applied once each inside
a transaction, recorded in schema_migrations
- bundle import (PRD §7.3) as a live feature on the Ruoat tab: paste JSON or
upload a file, get a per-row Finnish report. The same importer is reachable
as `foodster -import` for repopulating a scratch database
- templ views and hand-written CSS with light-dark() theming; the Datastar
v1.0.3 client is vendored, since the Go SDK ships no browser asset and a
CDN would break an offline LAN
Names are normalized to sentence case rather than title case: Finnish
capitalizes only the first word of a phrase, so "Keitetyt perunat" is right
and "Keitetyt Perunat" is not. PRD §6 and §7.3 are amended to match.
Testing is behind make targets rather than ad-hoc commands: `make check` runs
lint, unit tests and scripts/smoke.sh, which exercises auth, static assets
and every import path against a scratch database on a spare port.