The three exported helpers in lib/prisma.ts (getCaloriesBurned,
setCaloriesBurned, getCaloriesBurnedBulk) existed because an early
Prisma client generation didn't include the column. Schema and
client have been aligned for several releases — the workaround is
dead weight.
Removed: the helpers from lib/prisma.ts (~30 lines of
$queryRawUnsafe / $executeRawUnsafe).
Updated callers to use plain caloriesBurned field references:
- app/api/workouts/route.ts (GET list + POST create)
- app/api/workouts/[id]/route.ts (GET detail + PATCH update)
- app/api/settings/export-csv/route.ts (CSV export)
All call sites now go through normal type-safe Prisma queries.
Net effect for users: zero. Net effect for the codebase: cleaner
read paths, stronger TS coverage on caloriesBurned, fewer SQL
strings to audit.
No schema changes, no migration. Existing /data is untouched.
v1.0.0:5 promoted to current; :1, :2, :3, :4 in other.
Security: shipping admin@local / workout123 as a default that the
operator was supposed-to-rotate-but-might-not is the kind of footgun
that turns into "default-credential exposure" headlines. Eliminated.
prisma/seed.ts now ONLY seeds the InstanceSettings singleton — no
admin user, no UserPreferences, no exercises in the build-time
fallback DB. The image still ships with prisma/exercises.seed.json
(curated 164-exercise library) but those rows aren't inserted until
an admin is created via the StartOS Action.
The change-admin-credentials Action now does INSERT-or-UPDATE in one
shot. CREATE mode (no admin exists) inserts the User row, inserts
UserPreferences with sensible defaults, and runs
ensureExerciseLibrary.cjs for the new admin so they don't have to
wait for the next service start to see the curated library. UPDATE
mode (admin exists) keeps the v1.0.0:1-3 rotation behavior. The
mode is auto-detected by counting `WHERE isAdmin = 1`.
The login page is now a server component that reads the admin count
upfront. Zero admins -> renders a "needs setup" panel pointing at
the StartOS Action ("Services -> Proof of Work -> Actions -> Set
admin credentials"). Otherwise renders the existing LoginForm
(extracted to LoginForm.tsx). Eliminates the
"I tried admin@local/workout123 and it failed, what's wrong"
fresh-installer confusion.
Backward compatible for upgrades from v1.0.0:1-3:
- /data already has an admin user; the no-admin detection never
triggers; login behaves identically to before.
- The Action's UPDATE mode still works for rotation.
Version graph: v1.0.0:4 promoted to current; v1.0.0:1, :2, :3 all
listed as `other` for in-place upgrade paths.
README updated to call out the explicit no-default-account design
and how to bootstrap an admin in local dev (Prisma Studio, since
the StartOS action isn't available off-StartOS).
Removes the one-time `/data` snapshot from the deployed Docker image now
that the cutover from the legacy `workout-log` package is verified done
(v1.0.0:1 + :2 in production).
Dockerfile
- Drops `COPY start9/0.4/seed/data /app/seed/data`.
- Drops the `WORKOUT_BAKED_SEED_DB_PATH` env var.
- Comment block explains the rationale + how to re-seed if ever needed.
docker_entrypoint.sh
- Step 1 collapses to single-branch fallback: if /data is empty AND
/app/prisma/data/app.db exists, copy the empty-schema fallback. The
baked-seed branch is gone.
- Comment cross-references v1.0.0:3 for the rationale.
start9/0.4/seed/README.md rewritten to reflect historical-only status
+ how to re-seed for the rare "spin up another instance with this
history" case.
Version graph
- Adds startos/versions/v1.0.0.3.ts with empty up/down migrations and
release notes.
- Promotes v1.0.0:3 to `current`; v1.0.0:1 and :2 move to `other` so
hosts on either upgrade in place.
No schema changes, no data migration. /data on existing installs is
left exactly as-is. Image size drops by ~1.7MB (the snapshot size).
v1.0.0:1 shipped a per-request nonce-based CSP via Next.js middleware.
In production it produced a blank first paint: Next 14.2.x's bootstrap
inline scripts weren't picking up the nonce reliably from the x-nonce
request header, so the browser blocked them.
This release reverts to the pre-experiment posture:
- middleware.ts back to auth gating only (no nonce, no CSP).
- next.config.js restores the static CSP with `'unsafe-inline'` allowed
for script-src and style-src. Same headers (HSTS, Referrer-Policy,
Permissions-Policy, frame-ancestors 'none', etc.) all stay.
- New startos/versions/v1.0.0.2.ts with empty up/down migrations and
a release note explaining the bug + revert. Promoted to `current`
in the version graph; v1.0.0:1 moves to `other` so existing
installs upgrade in place.
No schema changes, no data migration. Existing v1.0.0:1 installs
keep their /data.
Re-attempt path documented in middleware.ts and next.config.js
comments: future PR can revisit nonce CSP using Next's documented
pattern verbatim (notably setting CSP on BOTH request headers and
response headers — we only set it on response).
Per-user sessions UI (Settings -> Active sessions)
- listMySessions returns the current user's still-valid sessions with
last-8-char token suffix (UX hint) and an isCurrent flag (the
authoritative "this device" marker).
- revokeSession refuses if the target is the actor's current token —
use Sign out for that flow. Per-row Revoke button on every other.
- revokeAllOtherSessions = the previously-internal `deleteOtherSessions`
helper exposed as a single button "Sign out other devices".
- All gated to the actor's own userId (never lets a user touch another
user's sessions).
CSV parser refactor + tests
- Extracted parseCSV, NAME_MAP, parseFloatMaybe, parseIntMaybe,
getVariationNote, resolveExerciseName, parseDate from
app/api/import/parse/route.ts to lib/csvParser.ts. Behavior
byte-identical; route is now a thin wrapper that imports from the
lib.
- 18 tests covering: empty input, simple rows, lowercased headers,
quoted-field commas, escaped double quotes, CRLF normalization,
empty-line handling; numeric maybe-parsers; getVariationNote known
patterns + null pass-through; ALL 27 NAME_MAP entries map to their
canonical target; named CSV-shorthand examples; M/D/YYYY + ISO date
parsing with noon-UTC anchoring (so US negative-offset zones still
see the same calendar day).
Workout + exercise CRUD route tests
- New tests/routes-crud.test.ts: GET/POST /api/exercises, GET/POST
/api/workouts. 401 on unauthenticated, per-user data isolation,
query filtering, soft-delete exclusion, isCustom stamping, duplicate
detection, type-driven inputFields defaults (cardio gets
duration+calories), Zod validation rejection, set creation with
weight/reps/rpe persisted, negative-reps rejected.
- Helper builds NextRequest objects so the routes' nextUrl.searchParams
access works.
Composite indexes for hot query paths (schema.prisma + entrypoint)
- Session: (userId, expiresAt) for "list my still-valid sessions" and
per-user cleanup.
- Workout: (userId, deletedAt, date) for the workout list query
(filter by user + alive + date order).
- SetLog: (workoutId, setNumber) for the always-ordered set fetch
under each workout.
- Existing single-column indexes kept; composites are additive.
- Entrypoint runs CREATE INDEX IF NOT EXISTS so live snapshots pick
up the new indexes on first boot after upgrade.
verify-database StartOS action (start9/0.4/startos/actions/verifyDatabase.ts)
- Read-only. Runs PRAGMA integrity_check + quick_check + row-count
queries against /data/app.db, reports as a structured result.
- allowedStatuses: only-running. Mounts the volume read-only.
- Use after a StartOS Backup, after a host crash, or after a fresh
sideload to confirm the data is sound before relying on it.
Test suite now 67 tests across 7 files in ~2.4s.
Schema
- User.isAdmin: Boolean default false (Prisma)
- New InstanceSettings singleton (id=1) holding signupsOpen flag
Boot-time compat ALTERs (docker_entrypoint.sh)
- Adds User.isAdmin column to legacy snapshots; auto-promotes the oldest
user to admin if no admin exists yet, so workout-log -> proof-of-work
cutover preserves admin functionality with no manual SQL.
- Creates InstanceSettings table + singleton row (signupsOpen=0) for any
snapshot that doesn't have it.
App: sign-up flow
- /auth/signup page: server component that reads InstanceSettings
upfront. If sign-ups are closed it shows a closed-instance message and
a back-to-sign-in link rather than a dead form. If open it renders
SignupForm (client) which calls signupAction (server).
- signupAction: re-checks the flag (defense in depth), validates email
format / 8-char password / matching confirm, blocks duplicate-email
enumeration with a generic error, creates the user with isAdmin=false,
seeds default UserPreferences, ensures the curated exercise library
for the new user (lib/library.ts upserts every entry), then issues a
session cookie.
- Login page now links to /auth/signup; old "Demo: admin@example.com /
password" footer (which was wrong anyway) removed.
App: admin in-app toggle
- Settings page renders new AdminInstanceSettings component for admins
only. Optimistic toggle posts to /api/admin/signups; error rollback
on failure.
- /api/admin/signups: GET returns current flag (any authed user, so the
UI knows whether to show the sign-up CTA later); POST flips it
(admin only).
StartOS package action
- toggle-signups: same setter as the in-app toggle, accessible from the
StartOS UI without an admin login. Single boolean input. Asserts the
read-back value matches what was written before reporting success.
- changeAdminCredentials now keys the UPDATE on
`WHERE isAdmin = 1 ORDER BY createdAt ASC LIMIT 1` (was: just
ORDER BY createdAt) — correct under multi-user.
Release notes / docs
- v1.0.0:1 release notes expanded to call out multi-user as part of
the cutover release (no separate version needed since this is the
first proof-of-work release shipping to anyone).
- Root README: short Multi-user section explaining both toggle paths
and that new users get the curated library automatically.
- README dev setup adds `npx prisma generate` step (required after
schema changes for local dev).
Repo cleanup
- Add top-level .gitignore (was missing; node_modules, .next, *.s9pk,
image.tar, seed/data/*.db, log files, etc.) and a root README.
- Delete legacy start9/0.3.5/ package (StartOS 0.3.5 wrapper, no longer
the deploy target).
- Delete start9-example-packaging/ (template from another project).
- Delete planning docs (START9_PACKAGING_LOG.md, VERSIONING.md,
STARTOS_0.4_UPGRADE_PROMPT.md, ICON_FILES_INDEX.md, etc.) — info now
lives in the deploy guide and code comments.
- Drop the standalone Dockerfile, docker-compose.yml, ICON_*, and dev
log/build artifacts from the app dir.
- Drop the v0.1.0:18/19/20 version files (they belonged to the legacy
workout-log package and don't apply to the new id).
Rename + new package
- Rename app dir workout-planner/ -> proof-of-work/.
- Rename StartOS package id workout-log -> proof-of-work; the new id
makes this a brand new StartOS service (clean cutover from the old
one rather than in-place upgrade).
- Reset version graph; v1.0.0:1 is the seeded cutover release. The
Dockerfile bakes a one-time /data snapshot and docker_entrypoint.sh
copies it into the new volume on truly-fresh first boot only (both
/data/app.db missing AND /data/.seeded absent).
- Move start9/0.4-migration/ -> start9/0.4/; the old start9/0.4/ stub
is gone.
Curated exercise library (multi-user-aware)
- proof-of-work/prisma/exercises.seed.json is the canonical library
shipped to every install (164 exercises today, dumped from the live
snapshot).
- proof-of-work/scripts/sync-library.cjs (npm run sync-library) refreshes
the JSON from start9/0.4/seed/data/app.db after refresh_seed.sh.
- proof-of-work/prisma/seed.ts now reads from the JSON instead of a
hardcoded 52-exercise array; runs at Docker build time to seed the
fallback DB and on first boot for fresh installs.
- proof-of-work/prisma/ensureExerciseLibrary.cjs runs on every container
boot (from docker_entrypoint.sh) and INSERT OR IGNOREs every library
entry for every user, keyed on (userId, name). Library updates flow
to existing installs on package upgrade; user-custom exercises
(isCustom=true) and any colliding names are never overwritten;
removed exercises stay on existing installs (additive-only).
Deploy guide (start9/0.4/DEPLOY_040.md)
- Rewritten end-to-end for the workout-log -> proof-of-work cutover:
refresh_seed, sync-library, build, sideload, verify, rotate creds,
stop the old service, then post-cutover cleanup release v1.0.0:2.