Commit Graph
7 Commits
Author SHA1 Message Date
Marc-André Bombeck 1f4ba5939d style: apply prettier across the tree
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
2026-06-19 22:20:31 +02:00
Marc-André Bombeck 7e498760f5 test(medications): anchor bulk-intake fixtures to relative past instants
The intake plausibility bound rejects a future takenAt by design, so
the fixed calendar dates and the today-evening slot in the bulk suite
rotted into rejections depending on the run date and hour.
2026-06-12 12:05:24 +02:00
Marc-André Bombeck 25f41f88fe feat(ux,settings): undo where it was missing and settings that behave one way
Deleting a measurement or mood entry can be undone from the toast via
new restore endpoints, dialogs submit on enter with busy states, silent
mutations gain error toasts, and the settings area finishes its
consolidation: provider and integration cards live in focused
subcomponents, every setting persists server-side at exactly one home,
and the save conventions are written down. The remaining raw fetches in
settings move onto the shared client.
2026-06-11 02:19:28 +02:00
Marc-André Bombeck 8683190580 test(integration): pin the clock for the slot-collapse cases
The v1.8.2 slot-collapse cases mint a 07:00 pending slot and replay a
taken-write onto it, but a taken-write must never snap onto a future
slot — so the cases failed whenever the suite ran before 07:00 local.
Pin Date (only) to today's local noon for those cases and give the
shared test session a long expiry so it survives the time jump.
2026-06-07 00:40:44 +02:00
Marc-André Bombeck 710902a43c fix(medications): harden the slot upsert against races, downgrades, and double-decrements
Review of the slot-convergence change surfaced gaps that could mis-record
a dose. Fold the write logic into one shared `applyCanonicalSlotWrite`
used by both intake routes and close them:

- Concurrency: catch the unique-key race on insert and converge by
  re-finding the slot row and updating it, instead of returning 500 (and
  dropping the tap) or leaving a duplicate.
- No downgrade: a pending projection echo replayed onto a slot already
  marked taken or skipped is a no-op — it never clears `takenAt`. A
  recorded dose is immutable, so an offline re-sync cannot flip a taken
  dose back to missed.
- Inventory: decrement a medication's remaining count only on a genuine
  pending→taken transition, not on an idempotent re-post.
- Deterministic slot-row selection: prefer an existing actioned row, then
  the pending row, ordered by createdAt then id.
- Snap instant: derive the canonical instant via the same `localHmAsUtc`
  the projector and reminder worker mint with, so the value is identical
  by construction across a DST transition rather than only test-pinned.
- Cleanup job: isolate each slot's collapse in its own try/catch and
  guard the instant-normalisation against a tombstone collision, so one
  slot can no longer dead-letter a user's whole dedup pass.
2026-06-01 08:32:42 +02:00
Marc-André Bombeck 0d7d08f616 fix(medications): converge intake writes onto one row per scheduled slot
A twice-daily medication ended up with two rows for one dose slot: the
projector / reminder worker mints a pending row (source REMINDER) at the
slot's `localHmAsUtc` instant, while a "taken" write from the per-med or
bulk intake route inserted a fresh row (source WEB / API). Because
`source` is part of the `(userId, medicationId, scheduledFor, source)`
unique key — and the written instant could drift by a minute from the
projected one — the two rows coexisted: one taken, one still pending and
overdue. The card then read the slot as taken, counted 100%, and
suppressed the "due now" prompt for a dose the user had not taken.

Resolve every scheduled intake write to the slot's canonical instant
before writing and upsert in place, source-agnostic:

- `resolve-slot-instant.ts` snaps an incoming instant to the nearest
  scheduled slot for that day (enumerated through the canonical
  recurrence engine, so PRN and cyclic off-weeks resolve to no slot) and
  anchors it on the same `localHmAsUtc` the projector and worker use.
  Off-slot and as-needed writes resolve to null and keep insert semantics.
- The per-med and bulk routes look up the existing row at the canonical
  `(userId, medicationId, scheduledFor)` ignoring source and update it in
  place — mirroring the status-toggle route — instead of inserting a
  sibling. The bulk per-entry response gains an additive `updated` status.
- The reminder worker's missed-dose mint now skips a slot that already
  carries an actioned row (taken or skipped) from any source, not only an
  existing pending REMINDER row.

PRN / as-needed writes are unaffected. Regression tests pin the collapse
(exact and one-minute drift), the PRN exemption, and the worker guard.
2026-06-01 08:06:37 +02:00
Marc-André Bombeck 3ece95f192 chore(release): v1.4.30
Server-side prep for the next iOS TestFlight build. Five surfaces land together so the iOS engineer can pick them up in one cut-over: a locked externalId shape for daily-aggregated cumulative HealthKit rows, the SyncMode foundation columns + handshake + bulk-backfill endpoints, a first-class MoodEntry.note column that replaces the legacy tag-based workaround, a cross-source workout dedup helper, and two new MeasurementType enums plus the shared category overlay that drives the iOS permission picker and the future Insights nav.
2026-05-16 12:37:55 +02:00