7.4 KiB
AGENTS.md
Kid-friendly soccer training tracker PWA for one player ("Gunner"), packaged as a StartOS 0.4.x .s9pk service.
Inbox check: At session start, if
~/Projects/standards/INBOX.mdexists, scan it for items tagged(premier-gunner)and surface them before proposing next steps; triage with/triage.
Stack (versions that matter)
- Backend: Node.js (ESM,
"type":"module") + Fastify 5,better-sqlite311,bcryptjs3,@fastify/cookie11,@fastify/static8. No build step. - Frontend: vanilla-JS PWA — no framework, no bundler. Chart.js v4 vendored at
public/vendor/chart.umd.min.js. Service worker atpublic/sw.js. - Packaging (
s9pk/):@start9labs/start-sdk1.5.3 (TypeScript), bundled with@vercel/ncc, packed withstart-cli(0.4.0-beta). Targets StartOS 0.4.x. Image built locally viaDockerfile(Node 22), x86_64 only.
Commands
Run from repo root unless noted.
- Run (dev):
npm run dev(node --watch) ornpm start— listens onPG_PORT(default 3000). Local:PG_PORT=3344 PG_PASSWORD=<dev-password> npm start. IfPG_PASSWORDis unset, the app uses a built-in dev fallback and logs a warning — set it explicitly. - Seed defaults:
npm run seed(only seeds an empty DB). - Tests: none configured — TODO. Verify manually: boot against a temp DB and hit the API, e.g.
PG_DATA_DIR=/tmp/pg PG_PORT=3399 PG_PASSWORD=<dev-password> node src/server.jsthencurl/login. No single-test runner — TODO. - Lint (root): none configured — TODO.
- Typecheck + format (s9pk):
cd s9pk && npm run check(tsc --noEmit) andnpm run prettier. - Build .s9pk:
cd s9pk && npm ci && make(vendors the app, builds the image, packspremier-gunner_x86_64.s9pk). - Deploy to StartOS:
cd s9pk && make install(installs newest.s9pkto the host in~/.startos/config.yaml). Status reachesinstalledviastart-cli package list.
Directory layout
src/— backend.server.js(Fastify app +/api/*auth gate),config.js(env),db.js(appliesschema.sql+ runs migrations),schema.sql,seed.js,auth.js,routes/{auth,categories,entries,plans,goals,stats}.js.public/— frontend.index.html,login.html,js/{app.js,api.js,dashboard.js},css/styles.css,sw.js,manifest.webmanifest,icons/,vendor/.s9pk/— StartOS package.Dockerfile,Makefile(edit here),s9pk.mk(do not edit),startos/(manifest/,main.ts,interfaces.ts,actions/,fileModels/store.ts,versions/current.ts,i18n/),instructions.md,README.md,TODO.md.s9pk/app/— generated copy of the app made bymake prep; gitignored, do not edit.data/— runtime SQLite (premier-gunner.db); gitignored.
Data model notes
- Metrics live in
category_metrics;kindis one ofcount | duration | score | decimal. Records aretrack_record(bool) +record(REAL, the live best) +record_floor(REAL, optional manually-pinned best) on the metric.record= the direction-aware best of the best logged value andrecord_floor(src/records.jsrecomputeRecord, respectinghigher_is_better); it bumps up on logging and is recomputed on entry edit/delete so it can drop again, but never belowrecord_floor. The Settings record field sets the floor. - Entries: one row per logged session (
entries, withnote); metric readings inentry_values.
Config (env-var names only — never hardcode secrets)
PG_HOST, PG_PORT, PG_DATA_DIR, PG_PASSWORD, PG_PASSWORD_HASH, PG_COOKIE_SECRET, PG_SESSION_DAYS. NODE_ENV=production enables the Secure session cookie (requires HTTPS). On StartOS, PG_PASSWORD is injected from store.json and is authoritative (re-hashed every boot); change it via the "Set Login Password" action.
Conventions
- Commits: imperative subject; body only when the "why" isn't obvious; author is the user, no AI attribution. No
Co-Authored-By/"Generated with" trailers. - Remote: self-hosted Gitea, not GitHub —
originis configured in.git/config(an SSH URL; keep it out of tracked files). Do not add a GitHub remote. Branch ismaster; push after committing. - Match existing file conventions; small reviewable diffs; comments explain why.
- Verify browser-observable changes before shipping (run it, check no console errors).
Always
- Bump
s9pk/startos/versions/current.tsversionon every rebuild that changes anything — StartOS won't register an update otherwise. Code change → bump semver (0.1.6→0.1.7); packaging-only → bump the revision (0.1.6:0→0.1.6:1). - Bump the
CACHEconstant inpublic/sw.jswhenever frontend assets change, so installed PWAs update. - For DB changes, edit
schema.sql(fresh installs) and add an idempotent migration insrc/db.jsguarded by asettingsflag (existing DBs) and updatesrc/seed.js. - Run
cd s9pk && npm run checkafter editingstartos/*.ts. - Keep i18n in sync: every
i18n('x')must be a key instartos/i18n/dictionaries/default.ts, with all indices present fores_ES, de_DE, pl_PL, fr_FRintranslations.ts, ortscfails.
Never
- Never edit
s9pk/s9pk.mk(plumbing) ors9pk/app/(generated). Makefile overrides go above theinclude s9pk.mkline. - Never rely on
schema.sqlalone for changes to existing databases —CREATE TABLE IF NOT EXISTSwon't ALTER; add a migration. - Never build/ship aarch64 — target is x86_64 only (
ARCHES := x86, manifestarch: ['x86_64']). - Never add AI co-authorship trailers to commits; never add a GitHub remote; never force-push or commit to a shared branch unless asked.
- Never commit secrets,
data/,node_modules/, or*.s9pk(all gitignored). Reference env-var names instead.
Gotchas
start-cli(andmake install) needs a git repo with at least one commit — it stamps the build with the git hash.make installpicks the newest*.s9pkby mtime; with x86-only there's just one.- Migrations key off a flag row in the
settingstable (e.g.migr_records_scores); pick a new flag name per migration.
Current state
Live on StartOS (deploy host set in ~/.startos/config.yaml host:, not in this repo) at v0.1.7:0; make install deploys and the PWA self-updates via the in-app banner. Pushed to self-hosted Gitea (origin).
- Working: daily logging, weekly planning, goals + thermometer, dashboard (streak calendar, radar, line/series charts, records), personal-best records (auto + manual floor, self-correcting on edit/delete), per-session notes, EPA Max/Weighted Speed, tap-to-type number fields, full category/metric management in Settings, "Set Login Password" action. Login is rate-limited (per-IP, 8 fails → 15-min lockout) and the password minimum is 8 chars.
- In progress: none — all requested features are built, committed, and deployed.
- Decided, not yet done: reconcile in-app password change with the StartOS action (env wins on restart); optional "log another" for a second same-category session in a day. See
ROADMAP.md. - Known issues: changing the password from the app's own Settings reverts on restart under StartOS — use the action.
- Eval backlog: deferred P2/P3 items from
EVALUATION.mdare catalogued inROADMAP.md→ Evaluation backlog (registry-submission blockers parked — not publishing). - Next steps: (1) set a real login password via the "Set Login Password" action; (2) confirm speed unit (
mphvskm/h); (3) work the ROADMAP eval backlog if desired.