Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
b341f61
fix(ops-safety): safe cleanup pruning and YAML-escaped operator config
lnieuwenhuis Sep 17, 2026
ddfb5d3
fix(ops-safety): pass gh api flags as separate args and harden cleanu…
lnieuwenhuis Sep 17, 2026
785ba99
Merge pull request #173 from lnieuwenhuis/fix/audit-ops-safety-staging
lnieuwenhuis Sep 17, 2026
9c5a842
fix(benchmark): reuse validated baselines and pin fixture image inputs
lnieuwenhuis Sep 17, 2026
067bef3
fix(deps): upgrade npm advisory matches to fixed versions
lnieuwenhuis Sep 17, 2026
7629286
fix(deps): upgrade rustls and event-listener past advisory fixes
lnieuwenhuis Sep 17, 2026
393fdfa
fix(audit-ui-controls): shared number validity, number field refs and…
lnieuwenhuis Sep 17, 2026
9881747
fix(audit-ui-controls): preset focus trap and numeric guard, tab pane…
lnieuwenhuis Sep 17, 2026
5512537
fix(audit-ui-controls): onboarding precision and integer calories, re…
lnieuwenhuis Sep 17, 2026
4409008
Merge pull request #174 from lnieuwenhuis/fix/audit-dependency-staging
lnieuwenhuis Sep 17, 2026
fd24b13
fix(ui): route rejected action promises through error/rollback contract
lnieuwenhuis Sep 17, 2026
58dad07
fix(ui): invalidate in-flight photo analysis on image change
lnieuwenhuis Sep 17, 2026
7c5f675
fix(ui): classify barcode auth failures and preserve product names
lnieuwenhuis Sep 17, 2026
51ac6ad
fix(ui): suppress global day shortcuts while overlays own keys
lnieuwenhuis Sep 17, 2026
aca13ab
fix(ui): capture the local day per search-modal operation
lnieuwenhuis Sep 17, 2026
63804eb
fix(ui): honest template-save feedback in capture dialogs
lnieuwenhuis Sep 17, 2026
98cf877
fix(ui): preserve dirty drafts and track mutations per card
lnieuwenhuis Sep 17, 2026
40d1435
fix(ui): clear lint findings on audit-ui-mutation-state changes
lnieuwenhuis Sep 17, 2026
8636939
fix(audit): guard planner shopping dates against empty input (UI-04)
lnieuwenhuis Sep 17, 2026
50361d8
fix(audit): stable SSR token timestamps and clock snapshots (UI-11, U…
lnieuwenhuis Sep 17, 2026
437ffe9
fix(audit): network-first service-worker manifest refresh (UI-12)
lnieuwenhuis Sep 17, 2026
86d6c5e
fix(audit): actionable login error messages for all sign-in codes (UI…
lnieuwenhuis Sep 17, 2026
b78ae9c
fix(audit): year-inclusive history and projection dates (UI-14)
lnieuwenhuis Sep 17, 2026
be72e39
fix(audit): library query consistency, plural label, input and route …
lnieuwenhuis Sep 17, 2026
92cbd75
fix(audit): zero-goal fallback display and unique active admin nav (U…
lnieuwenhuis Sep 17, 2026
4dfe9aa
fix(audit): nonnegative macro share allocation totalling 100 (UI-23)
lnieuwenhuis Sep 17, 2026
8a670d3
fix(web): keep standalone packaging from deleting source dependencies
lnieuwenhuis Sep 17, 2026
8b8b69f
fix(web): resolve the startup migration command portably
lnieuwenhuis Sep 17, 2026
afc8a3d
fix(railway): watch railpack.json from the scoped frontend config
lnieuwenhuis Sep 17, 2026
f1ffedf
fix(db): preserve historical migration upgrades and session invariants
lnieuwenhuis Sep 17, 2026
5aff3ce
test(backend): pin fixture schema to migrations and require configure…
lnieuwenhuis Sep 17, 2026
14f6874
fix(auth): enforce the absolute session lifetime on the backend
lnieuwenhuis Sep 17, 2026
de80db8
fix(backend): rate limit the public API per client, not per proxy peer
lnieuwenhuis Sep 17, 2026
a6d2674
fix(backend): harden paid photo and barcode provider boundaries
lnieuwenhuis Sep 17, 2026
6a2146a
Merge pull request #175 from lnieuwenhuis/fix/audit-build-staging
lnieuwenhuis Sep 17, 2026
0beb9fe
fix(api): correct OpenAPI parameter formats, 404 coverage and respons…
lnieuwenhuis Sep 17, 2026
a2742be
fix(web): classify public API proxy deadline expiry as 504 timeout
lnieuwenhuis Sep 17, 2026
0954ad0
docs(setup): correct Node engine, pool, TLS and E2E test-route contracts
lnieuwenhuis Sep 17, 2026
baf920e
Merge fresh origin/staging (PR #175 build fixes) into fix/audit-secur…
lnieuwenhuis Sep 17, 2026
0824cd7
fix(benchmark): consume frozen verified fixture bytes and close basel…
lnieuwenhuis Sep 17, 2026
e8bb943
Merge remote-tracking branch 'origin/staging' into fix/audit-benchmar…
lnieuwenhuis Sep 17, 2026
c72297f
feat(api): add opt-in cursor pagination and truncation metadata
lnieuwenhuis Sep 17, 2026
1540801
Merge remote-tracking branch 'origin/staging' into fix/audit-migratio…
lnieuwenhuis Sep 17, 2026
92cf71d
Merge latest staging (build175) into fix/audit-ui-display-staging
lnieuwenhuis Sep 17, 2026
e0d0f35
fix(ui): keep framework control flow rethrowing through mutation catches
lnieuwenhuis Sep 17, 2026
f8f7dde
fix(ui): reset scanner mount marker on StrictMode effect replay
lnieuwenhuis Sep 17, 2026
81835c6
fix(audit): build local token date formatter per render so CI UTC and…
lnieuwenhuis Sep 17, 2026
4351547
fix(ui): keep HTML error responses as barcode outages
lnieuwenhuis Sep 17, 2026
2a54e27
test(e2e): prove an unsaved draft label survives a status change
lnieuwenhuis Sep 17, 2026
8b60a2d
fix(backend): lock meal patches and correct persistence edge cases
lnieuwenhuis Sep 17, 2026
a0f5a5a
fix(web): carry shopping multipliers and strip product provenance
lnieuwenhuis Sep 17, 2026
32b792e
Merge pull request #178 from lnieuwenhuis/fix/audit-security-staging
lnieuwenhuis Sep 17, 2026
fd2bfa9
feat(web): document the pagination contract and release 3.6.0
lnieuwenhuis Sep 17, 2026
ac644fa
Merge remote-tracking branch 'origin/staging' into fix/audit-benchmar…
lnieuwenhuis Sep 17, 2026
5ec8fcc
fix(audit-ui-controls): keep weight display precision after unit toggles
lnieuwenhuis Sep 17, 2026
b2d1516
test(audit-ui-controls): browser regressions for accepted UI findings
lnieuwenhuis Sep 17, 2026
0aff86b
Merge remote-tracking branch 'origin/staging' into fix/audit-migratio…
lnieuwenhuis Sep 17, 2026
fa86dc1
Merge remote-tracking branch 'origin/staging' into fix/audit-ui-contr…
lnieuwenhuis Sep 17, 2026
a6dc668
Merge pull request #179 from lnieuwenhuis/fix/audit-benchmark-staging
lnieuwenhuis Sep 17, 2026
de151bb
Merge remote-tracking branch 'origin/staging' into fix/audit-ui-state…
lnieuwenhuis Sep 17, 2026
16b5803
Merge remote-tracking branch 'origin/staging' into fix/audit-ui-state…
lnieuwenhuis Sep 17, 2026
aa80b85
Merge pull request #183 from lnieuwenhuis/fix/audit-ui-controls-staging
lnieuwenhuis Sep 17, 2026
20b3c48
Merge remote-tracking branch 'origin/staging' into fix/audit-migratio…
lnieuwenhuis Sep 17, 2026
05ae146
Merge staging (aa80b85b) into fix/audit-data-staging for final verifi…
lnieuwenhuis Sep 17, 2026
e7030ae
Merge pull request #180 from lnieuwenhuis/fix/audit-migrations-staging
lnieuwenhuis Sep 17, 2026
52dc336
Merge staging (e7030aec MIG-01/TEST-01/TEST-02) into fix/audit-data-s…
lnieuwenhuis Sep 17, 2026
b8069c7
test(backend): drop temporary default-label index now fixture provide…
lnieuwenhuis Sep 17, 2026
b8d00a0
Merge pull request #184 from lnieuwenhuis/fix/audit-data-staging
lnieuwenhuis Sep 17, 2026
596380d
Merge freshest staging (controls183+migrations180+security178+benchma…
lnieuwenhuis Sep 17, 2026
ea148e4
Merge remote-tracking branch 'origin/staging' into fix/audit-contract…
lnieuwenhuis Sep 17, 2026
cf788a2
Merge remote-tracking branch 'origin/staging' into fix/audit-ui-state…
lnieuwenhuis Sep 17, 2026
5e485c0
Merge remote-tracking branch 'origin/staging' into feat/audit-paginat…
lnieuwenhuis Sep 17, 2026
e8a4676
fix(api): document tzOffsetMinutes healthkit query param for DATA-04 …
lnieuwenhuis Sep 17, 2026
a991f7d
fix(tests): adapt DATA-03 stats test to StatsPageData envelope
lnieuwenhuis Sep 17, 2026
b896ec6
Merge pull request #177 from lnieuwenhuis/fix/audit-contracts-staging
lnieuwenhuis Sep 17, 2026
b0fce79
Merge remote-tracking branch 'origin/staging' into fix/audit-ui-state…
lnieuwenhuis Sep 17, 2026
4ffb155
Merge remote-tracking branch 'origin/staging' into feat/audit-paginat…
lnieuwenhuis Sep 17, 2026
b2c7959
Merge pull request #181 from lnieuwenhuis/fix/audit-ui-state-staging
lnieuwenhuis Sep 17, 2026
8baba71
Merge newest staging (state181+contracts177+data184) into fix/audit-u…
lnieuwenhuis Sep 17, 2026
062e709
Merge pull request #182 from lnieuwenhuis/feat/audit-pagination-staging
lnieuwenhuis Sep 17, 2026
75a2285
Merge pull request #176 from lnieuwenhuis/fix/audit-ui-display-staging
lnieuwenhuis Sep 17, 2026
5463310
test(backend): keep SEC-04 log captures immune to callsite-interest p…
lnieuwenhuis Sep 17, 2026
5fc048f
Merge remote-tracking branch 'origin/staging' into fix/audit-security…
lnieuwenhuis Sep 17, 2026
5a40861
Merge pull request #185 from lnieuwenhuis/fix/audit-security-logtest-…
lnieuwenhuis Sep 17, 2026
62230af
test(web): isolate startup fixtures from inherited npm_execpath casing
lnieuwenhuis Sep 17, 2026
2582e61
Merge pull request #186 from lnieuwenhuis/fix/audit-startup-test-isol…
lnieuwenhuis Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 23 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Macro Tracker runs as two services: a Rust backend that owns database access and

Requirements:

- Node.js 20+
- Node.js 22.22.2 or newer 22.x, 24.15.0 or newer 24.x, or 26+ (the locked `jsdom@30.0.1` requires `^22.22.2 || ^24.15.0 || >=26.0.0`; CI runs Node 22)
- pnpm 10+
- A PostgreSQL database
- A Google sign-in flow through Shoo, which is the auth broker this app uses
Expand Down Expand Up @@ -99,15 +99,24 @@ pnpm backend:start:release
node apps/web/scripts/start-with-migrations.mjs
```

For a deployed instance, set `APP_URL` to the public URL, `BACKEND_URL` to the backend service URL reachable from the frontend server, and use real random values for `SESSION_SECRET` and `BACKEND_INTERNAL_SECRET`. If you use remote PostgreSQL, `DATABASE_URL` uses TLS with certificate verification by default when `sslmode` is omitted or set to `verify-full`; use `sslmode=require` only when your provider requires encrypted TLS without certificate verification.
For a deployed instance, set `APP_URL` to the public URL, `BACKEND_URL` to the backend service URL reachable from the frontend server, and use real random values for `SESSION_SECRET` and `BACKEND_INTERNAL_SECRET`. Remote PostgreSQL TLS is selected by `sslmode` in `DATABASE_URL`, and the two runtimes treat `require` differently:

The production build uses Next.js standalone output. The direct Node command above runs startup migrations when explicitly enabled, then loads the generated server in the same process. Both Railway frontend configs use this command to avoid keeping pnpm and a launcher parent resident. `pnpm --filter @macro-tracker/web start` remains available locally; without standalone output the launcher loads the Next CLI. PostgreSQL pools default to 10 connections; set `POSTGRES_POOL_MAX` if you need a different cap. The old default of 3 was low enough that a burst of unauthenticated requests could exhaust it before any credential check ran.
| `sslmode` | Rust backend (`sqlx`) | JS migrations, tests and helpers (`pg`) |
| --- | --- | --- |
| omitted | TLS with full certificate verification | TLS with full certificate verification |
| `verify-full` | TLS with full certificate verification | TLS with full certificate verification |
| `require` | TLS; verifies the server certificate only when a root CA is supplied (`sslrootcert` in the URL or `PGSSLROOTCERT`), which this project does not configure | TLS with verification, except Railway private Postgres (`*.railway.internal`), whose self-signed chain is accepted |
| `disable`, `allow`, `prefer`, `no-verify` | rejected | rejected |

Local and loopback hosts connect without TLS in both runtimes. Prefer `sslmode=verify-full` (or omit `sslmode`) with a provider CA chain the system trust store accepts: `require` is not portable between the runtimes, and because the JS tooling verifies certificates by default it must not be treated as a safe unverified mode. To verify a provider CA only in the Rust backend, set `sslmode=verify-full` together with `sslrootcert`; the JS tooling reads no `sslrootcert` parameter and relies on the system trust store. `ALLOW_UNVERIFIED_DB_TLS=true` is a deliberately loud JS-tooling-only opt-out (it prints a warning and disables certificate verification) and must never be set in a normal deployment; the Rust backend has no equivalent opt-out apart from `sslmode=require`.

The production build uses Next.js standalone output. The direct Node command above runs startup migrations when explicitly enabled, then loads the generated server in the same process. Both Railway frontend configs use this command to avoid keeping pnpm and a launcher parent resident. `pnpm --filter @macro-tracker/web start` remains available locally; without standalone output the launcher loads the Next CLI. PostgreSQL pool settings are per runtime. The Rust backend opens up to `POSTGRES_POOL_MAX` connections (default 10, allowed 1-256): the old default of 3 was low enough that a burst of unauthenticated requests could exhaust the pool before any credential check ran. The JS migration and test pool defaults to 3 (`POSTGRES_POOL_MAX`) with a 10s idle timeout (`POSTGRES_POOL_IDLE_TIMEOUT_MS`) and a 5s connection timeout (`POSTGRES_POOL_CONNECTION_TIMEOUT_MS`); those variables do not change the Rust pool.

## API Access

Macro Tracker API v1 is available under `/api/v1/*`. Create personal access tokens from `/settings/api`, then send them as `Authorization: Bearer <token>`. Tokens start with `mtk_v1_`, are shown only once, store only a hash in the database, and can be scoped to read or write daily logs, foods, templates, recipes, weight, goals, and stats.

OpenAPI JSON is available at `/api/v1/openapi.json`, and the readable docs page is `/docs/api`. API responses use `{ "ok": true, "data": ... }` for success and `{ "ok": false, "error": { "code": "...", "message": "..." } }` for failures. Public API dates use `YYYY-MM-DD`.
OpenAPI JSON is available at `/api/v1/openapi.json`, and the readable docs page is `/docs/api`. API responses use `{ "ok": true, "data": ... }` for success and `{ "ok": false, "error": { "code": "...", "message": "..." } }` for failures. Public API dates use `YYYY-MM-DD`. The backend bounds one request at 30 seconds and answers a longer one with `504` and `error.code` `"timeout"`; the web proxy waits 35 seconds so that envelope normally wins, then aborts the call and answers the same `504` timeout envelope itself. Timed-out requests are never retried, so a timed-out mutation may still have been applied.

Self-hosted instances need the latest database migrations so the `api_tokens` table exists before users create tokens.

Expand All @@ -118,9 +127,9 @@ Useful optional environment variables:
| `APP_TRUSTED_ORIGINS` | Extra comma-separated origins that are allowed during auth flows. |
| `SHOO_BASE_URL` | Alternate Shoo base URL. Defaults to `https://shoo.dev`. The Content-Security-Policy is built per request in `apps/web/proxy.ts`, so the runtime value is the one that matters; `connect-src` is derived from it so the browser can reach this origin for the sign-in token exchange. |
| `ADMIN_OWNER_EMAILS` | Comma-separated emails that should get owner-level admin access. |
| `POSTGRES_POOL_MAX` | Optional PostgreSQL pool cap. Defaults to `3` for small deployments. |
| `POSTGRES_POOL_IDLE_TIMEOUT_MS` | Optional idle timeout for pooled PostgreSQL clients. Defaults to `10000`. |
| `POSTGRES_POOL_CONNECTION_TIMEOUT_MS` | Optional PostgreSQL connection timeout. Defaults to `5000`. |
| `POSTGRES_POOL_MAX` | PostgreSQL pool cap. The Rust backend defaults to `10` (allowed `1`-`256`); the JS migration and test pool defaults to `3`. |
| `POSTGRES_POOL_IDLE_TIMEOUT_MS` | Optional idle timeout for pooled JS migration/test PostgreSQL clients. Defaults to `10000`. |
| `POSTGRES_POOL_CONNECTION_TIMEOUT_MS` | Optional connection timeout for pooled JS migration/test PostgreSQL clients. Defaults to `5000`. |
| `NEXT_CACHE_MAX_MEMORY_MB` | Optional Next.js in-memory cache cap in MB. Defaults to `0`, which disables the in-memory data cache entirely; set a non-zero value where cached fetches are expected to hit. |
| `AI_GATEWAY_URL` | OpenAI-compatible chat-completions URL that powers food-photo estimates, for example `http://cliproxyapi.railway.internal:8317/v1/chat/completions`. Must be `https` unless the host is loopback or `*.railway.internal`. See `infra/cliproxyapi/`. Food-photo analysis is unavailable without it. |
| `AI_GATEWAY_API_KEY` | Backend-only bearer key for the AI gateway. Required for food-photo estimates. |
Expand Down Expand Up @@ -178,8 +187,13 @@ pnpm --filter @macro-tracker/db exec tsc --noEmit
# before Playwright so global setup, the frontend, and the Rust backend share state.
DATABASE_URL="$E2E_DATABASE_URL" pnpm db:migrate

# terminal 1: keep the backend running against $E2E_DATABASE_URL
DATABASE_URL="$E2E_DATABASE_URL" pnpm backend:start
# terminal 1: keep the backend running against $E2E_DATABASE_URL.
# Playwright sets ENABLE_TEST_ROUTES, TEST_ROUTES_SECRET and BACKEND_ENABLE_TEST_ROUTES
# for the Next dev server it starts, but not for this separately started Rust backend.
# BACKEND_ENABLE_TEST_ROUTES enables the test-only role-assignment RPCs the suite calls;
# it is accepted only while APP_URL points at localhost. Leave it unset everywhere except
# this isolated test backend.
DATABASE_URL="$E2E_DATABASE_URL" BACKEND_ENABLE_TEST_ROUTES=true pnpm backend:start

# terminal 2: run Playwright against that backend/database
DATABASE_URL="$E2E_DATABASE_URL" pnpm test:e2e
Expand Down
Binary file added apps/backend/assets/benchmark-foods/almonds.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/apple.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/avocado.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/banana.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/broccoli.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/carrot.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/cheddar.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/lentils.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/oats.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/orange.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/pasta.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/salmon.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend/assets/benchmark-foods/shrimp.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading