Skip to content

Log a workout by hand - #1089

Merged
MBombeck merged 15 commits into
mainfrom
feat/manual-workout-entry
Oct 2, 2026
Merged

MBombeck merged 15 commits into
mainfrom
feat/manual-workout-entry

Conversation

@MBombeck

@MBombeck MBombeck commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Refs #1085. Deliberately small: HealthLog is a health record, not a training app.

  • "Log workout" in the dashboard "+ Add" menu and the bottom-bar capture picker (module workouts, surface capture:workout, own record only, since the batch route writes only the caller's account), and as a header action and empty-state action on /insights/workouts.
  • One form in a ResponsiveSheet: sport type (existing enum, translated, sorted), start (profile time zone), duration (hours, minutes), optional distance (km/mi per unit preference, stored in metres), optional energy (kcal). Inline validation via FieldError (start not in the future, duration 1 min to 24 h, sane maxima).
  • Saved through the existing POST /api/workouts/batch with source: "MANUAL" and an externalId minted once per opened form (manual:<uuid>), so a double submit comes back as duplicate instead of a second row. Workout, dashboard and today queries are invalidated.
  • New DELETE /api/workouts/[id]: own MANUAL workouts only (404 for others, 409 workout.delete.synced_source for synced ones, which the next sync would bring back); removes the row with its child rows and the personal records it set, then re-runs record detection; audit row; OpenAPI and error-code catalogue updated. A delete button with confirmation on the detail view for manual workouts in the own record.
  • The dashboard "+ Add" menu now builds its entries with the same visibleCaptureKinds as the capture picker, so "Log mood" and "Log medication intake" also follow their modules; the workouts empty state mentions manual entry.

Tests: payload, zones, km/mi, validation, form behaviour, delete route, menu/picker parity across module combinations, and an integration test (save, read back via GET /api/workouts, duplicate, delete, 409 for a synced workout); each with a red proof on the old code. iOS: workouts are not in the sync delta and are re-read via GET /api/workouts, so a deleted manual workout disappears on the next read.

MBombeck and others added 15 commits October 2, 2026 21:22
DELETE /api/workouts/{id} removes a MANUAL workout outright, with its
child rows and the personal records keyed on it, then queues a silent
detection pass so the previous best comes back. Any other source answers
409: the next sync would write it back. Own record only, 404 for another
user's row.

Refs #1085
A small sheet with sport, start, duration and optionally distance and
active energy, nothing else. The start is read in the profile timezone,
distance in the reader's unit and stored in metres. It saves through
POST /api/workouts/batch as source MANUAL with an external id minted once
per opened form, so a second tap lands as a duplicate, not a second row.

The workouts page carries the action in its header and in the empty
state; the detail page offers delete for a hand-entered workout.

Refs #1085
The dashboard add menu and the bottom bar's capture picker gain a
workout entry next to measurement, mood and medication. It belongs to
the workouts module (capture:workout) and is offered in one's own record
only, because the batch ingest resolves the caller and no grant reaches
it.

Refs #1085
Builds the entry the form sends, posts it through the batch route, reads
it back from the list, re-posts it as a duplicate, and deletes it; a
synced workout is refused.

Refs #1085
…ister the delete

A repeated submit that wrote nothing reads as a neutral notice rather
than a success. The delete control is entered in the destructive-control
registry, and its 409 code in the published error catalogue.

Refs #1085
…re picker does

The dashboard add menu asked only whether the record's section was
writable, so a switched-off mood or medications module still offered its
entry here while the bottom bar's picker hid it. The menu now renders from
the picker's own visibleCaptureKinds, so the two cannot disagree.

Refs #1085
…entry

Main measured 3718 KB gz after v1.39.7 and v1.39.8, almost all of it new
copy in the seven lazy locale catalogs; the workout form, its sheet, the
delete action and their copy add about 7 KB (3725). The cap moves from
3724 to 3749, the same 24 KB headroom as before; the eager routes stay
inside their drift allowances.

Refs #1085
@MBombeck
MBombeck merged commit 2473d52 into main Oct 2, 2026
25 checks passed
@MBombeck
MBombeck deleted the feat/manual-workout-entry branch October 2, 2026 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant