Commit Graph
527 Commits
Author SHA1 Message Date
Marc-André Bombeck 82a535c9b5 chore(release): v1.37.17 — the discovery reads days, not readings 2026-08-13 22:58:38 +02:00
Marc-André Bombeck 5d508575c9 chore(release): v1.37.16 — every write reaches every reader 2026-08-13 20:37:28 +02:00
Marc-André Bombeck 297496b517 chore(release): v1.37.15 — async work reports what actually happened 2026-08-13 13:38:44 +02:00
Marc-André Bombeck 499ca2c4b4 chore(release): v1.37.14 — the rail tells today's truth, immediately 2026-08-13 10:05:23 +02:00
Marc-André Bombeck 47b0d340b7 chore(release): v1.37.13 — the record reads honestly under load 2026-08-13 06:19:13 +02:00
Marc-André Bombeck 5191f213f4 chore(release): v1.37.12 — the public privacy policy matches the app 2026-08-11 18:20:18 +02:00
Marc-André Bombeck 1e89b8de9c chore(release): v1.37.11 — one save for the lifestyle context 2026-08-10 10:23:59 +02:00
Marc-André Bombeck 196b37fa78 chore(release): v1.37.10 — one drug, a different dose at each time 2026-08-10 07:59:29 +02:00
Marc-André Bombeck 0de59a7360 chore(release): v1.37.9 — the record you want a paramedic to have 2026-08-10 05:33:51 +02:00
Marc-André Bombeck 4d37ebfc8a chore(release): v1.37.8 — the assistant can answer for your visits 2026-08-10 02:19:59 +02:00
Marc-André Bombeck 0d496aaf86 chore(release): v1.37.7 — runtime GeoLite2 fetch for self-hosters
A self-hoster on the published image can set MAXMIND_LICENSE_KEY at
runtime and the app fetches the GeoLite2 databases itself, no rebuild or
manual mount. Refs #659.
2026-08-09 22:31:51 +02:00
Marc-André Bombeck 976c14b223 chore(release): v1.37.6 — the server-side safety pass
Two hardening changes collected on the trunk: a screening reminder can no
longer be marked done through a crafted request, and editing a custom mood
tag, tag group or cycle symptom now leaves an audit entry the way deleting
one already did.
2026-08-09 18:01:26 +02:00
Marc-André Bombeck d711392bea chore(release): v1.37.5 — the app-wide UI and copy polish 2026-08-09 13:15:08 +02:00
Marc-André Bombeck ca50b92025 docs(changelog): note the managed-record controls and spacing sweep under Unreleased 2026-08-09 11:38:59 +02:00
Marc-André Bombeck 7aa1fa0174 docs(changelog): note the copy tightenings under Unreleased 2026-08-09 10:32:11 +02:00
Marc-André Bombeck 3a03732578 chore(release): v1.37.4 — honest settings copy and doctor-visit polish 2026-08-09 09:27:27 +02:00
Marc-André Bombeck 089c74cf23 feat(links): searchable, grouped entity link picker for visits and shots
The visit form's document / lab / condition link blocks and the
vaccination form's document link were flat lists capped at 25 rows with
no search: 30 lab values arrived undifferentiated and anything past the
cap was unreachable. Replace them with one shared picker — an inline
summary of removable chips over an add button, and a sheet that searches,
groups and multi-selects. Lab results gather under their panel and sample
date with a select-all per group, documents gather by month, illnesses
stay flat. The picker raises only the fetch limit; the server link cap
stays authoritative.
2026-08-09 07:01:15 +02:00
Marc-André Bombeck 40e53b14d6 feat(practitioners): search and group the address book by specialty
Search now covers the specialty column alongside name and practice, so a
field name like "Zahnmedizin" finds the practice even when the name and
practice hold nothing of the sort. The same OR reaches the visit form's
practitioner picker, which now shows the specialty beside the practice so
a specialty hit is legible. The list groups its rows under their
specialty, sorted alphabetically with the unspecified rows gathered into
one group at the end; when nothing carries a specialty the headings stay
suppressed and the list reads flat as before.
2026-08-09 06:53:57 +02:00
Marc-André Bombeck 9aefce1625 fix(checkups): offer the record-a-visit action only for real visits
The file-a-visit affordance painted on every checkup reminder, including
the self-measurement ones (weigh-in, blood pressure, a screening
check-in) where there is no appointment and 'you went' is never a
reading. Gate both the card and the dense-list branch on the same
free-text predicate the mark-done action already uses: a reminder with a
measurement type is satisfied in the app, a free-text reminder stands for
a practice visit. The server states the same boundary in resolve.ts.
2026-08-09 06:48:46 +02:00
Marc-André Bombeck 4335b38e02 feat(nutrients): remove in-app water logging, keep the sync path
Manual water entry is gone from the app: the water item on the capture
picker, the dashboard quick-add menu and the hydration card's add button
are removed, and the manual quick-add sheet is deleted. Water that syncs
in through Apple Health still arrives and still shows on the hydration
card with its daily total, history and reference line.

The POST /api/nutrients/water endpoint and the batch sync path stay
untouched. The nutrients page's module-enable error, which had borrowed
a water-quick-add string, gets its own key across all six locales.
Input-only water keys (nav.capture.water*, nutrients.hydration.quickAdd*)
are removed.
2026-08-09 06:46:28 +02:00
Marc-André Bombeck c8500dd869 docs(changelog): record the honest-error and MCP scope-note fixes under Unreleased 2026-08-09 04:33:35 +02:00
Marc-André Bombeck 8d5948ceb3 chore(release): v1.37.3 — the immunization record 2026-08-09 01:55:42 +02:00
Marc-André Bombeck ae2b82b1d9 docs: note the polish fixes under Unreleased 2026-08-08 21:10:26 +02:00
Marc-André Bombeck 0a9917a09d docs(changelog): drop a stray blank line 2026-08-08 16:22:59 +02:00
Marc-André Bombeck 102845c64a chore(release): v1.37.2 — the mood forecast, doctor visits, and a settings polish 2026-08-08 16:21:39 +02:00
Marc-André Bombeck 4c2dfa57b4 Merge remote-tracking branch 'origin/main' into fix/settings-ekg-polish 2026-08-08 16:17:40 +02:00
Marc-André Bombeck 650ac6189c docs: note the owner full name and the ECG teaser cap under Unreleased 2026-08-08 16:07:34 +02:00
Marc-André Bombeck 65a3d91b6e docs: record the settings and ECG polish under Unreleased
The mobile-consistency spec follows the change-password card to Security —
same claim, new address.
2026-08-08 15:17:05 +02:00
Marc-André Bombeck d45cedcf5d docs(changelog): note the mood comparison and its refusals 2026-08-08 12:15:44 +02:00
Marc-André Bombeck 61c220b7b8 Merge branch 'main' into feat/mood-level-b 2026-08-08 10:57:59 +02:00
Marc-André Bombeck b4051e6db3 Merge branch 'main' into feat/mood-level-b 2026-08-08 10:11:13 +02:00
Marc-André Bombeck b0627cfc5e Merge branch 'main' into feat/workout-series-enrichment 2026-08-08 10:09:47 +02:00
Marc-André Bombeck 1853a71b18 fix(mood): ask the ownership table who owns a linked metric, and pick one source
The resolver hand-wrote the map and got it wrong in both directions. It gated
steps and active energy on `workouts`, which gates workout SESSIONS and not
ambient movement, so the mood sheet hid a figure every other surface shows. It
called resting heart rate and heart-rate variability core with no owner, while
the table has assigned both to `recovery` since v1.30.22 — including the RMSSD
fallback, pinned there so a ring-only account cannot reach through it. An
account with recovery switched off was served the domain it had switched off,
which made the release note's own promise false.

Both directions now resolve through `moduleForMeasurementType()`, the table the
MCP wire, the correlations reader and the Coach snapshot already share.

Steps, active energy and the two vitals also run through the canonical-source
picker now. Without it a phone and a watch reporting the same day summed twice
and the sheet showed a walk nobody took; the vitals answered whichever row was
latest rather than the picked stream's.

A guard pins the three ownership claims, including that the two vitals share
one module — the block carries one availability flag for the pair, so the day
they diverge that flag becomes a lie and the block has to be split first.
2026-08-08 09:58:37 +02:00
Marc-André Bombeck f3c84018f9 Record a backfilled dose at its slot's own time
Marking an older missed dose as taken from the Verlauf pinned the
right slot but stamped the intake with the current moment. The body
builder is a pure helper now, anchored on the slot instant, matching
both the optimistic paint and the skipped-to-taken flip that already
behaved this way.
2026-08-08 09:38:30 +02:00
Marc-André Bombeck 3d55289ba3 feat(workouts): attach a heart-rate series to a workout already stored
A batch entry matching a stored workout stays first-write-wins for the
workout row. What it can do now is hand over a heart-rate series the
stored workout does not have yet: the series is attached, the row itself
is not written at all, and the entry reports back as `enriched`. A
workout that already carries a series is a no-op, so a client can walk
its whole history as often as it likes.

- The pre-flight probe now answers both questions at once, whether the
  row exists and whether it already has a series, and carries the id so
  the attach addresses the stored workout without a second round trip.
- The attach rides the same child write as a fresh workout's series.
  RETURNING names the rows that landed, so a workout that gained a
  series between probe and write reports as the duplicate it is instead
  of claiming an attach that never happened.
- A `samples` array that fails validation is refused per entry with
  `reason: "invalid_samples"` rather than failing the batch. One
  unusable session out of a hundred must not stall a history sweep the
  client cannot repair by retrying; every other validation failure keeps
  failing the batch.
- The wide event and the audit row tally enrichments. Counts only, the
  series never reaches either.
- `enriched` is the one contract movement: additive on the per-entry
  status, request shape unchanged. OpenAPI regenerated.

Integration coverage against a real Postgres pins the attach, the
repeat as a no-op, the refusal of a bad series alongside entries that
land, a mixed batch reporting each status, and the stored workout row
compared whole before and after, `updated_at` included.
2026-08-08 09:15:44 +02:00
Marc-André Bombeck 7b1a44e799 chore(mood): account for MoodContext in the four whole-schema guards
- the wipe plan names it before its parent, so the confirmation screen's count
  is the number of rows that existed rather than zero
- the backup plan classifies it as carried and two-ended, and the round-trip
  registry seeds one, which is what makes that claim provable
- the sharing surface admits the linked-day read with the reason it may answer
- CHANGELOG for the release
2026-08-08 08:10:15 +02:00
Marc-André Bombeck 035ec054da chore(release): v1.37.1 — five mood values and the range the lab report printed 2026-08-08 05:38:56 +02:00
Marc-André Bombeck 0c017ba335 docs(changelog): note the five mood values 2026-08-08 02:05:50 +02:00
Marc-André Bombeck 8c5ed74bc0 docs(changelog): note the mood restore fix 2026-08-07 21:51:30 +02:00
Marc-André Bombeck 315ca5e67c fix(deps): move the PDF renderer past a script-execution flaw
pdfjs-dist 6.1.200 and a transitive 5.6.205 both sit in the affected range
of a flaw that lets a crafted PDF run JavaScript inside the renderer. The
document vault feeds it every uploaded PDF, so pin the direct dependency to
6.2.108 and add a bounded override so the transitive copy lands there too.
The override stops short of pdf-parse's 5.4.296, which predates the flaw and
drives the local text-extraction path.

The runtime image ships every pdfjs-dist copy in the store, not just the one
that gets loaded, so an unused transitive copy still counted.

Hoisting in the image picked whichever copy the filesystem listed first, and
more than one copy reaches it, so the version named in package.json was not
reliably the one the renderer resolved. Carry the resolved version forward
from the build stage, select it by name, and fail the build when it is
missing rather than leaving another copy in its place.
2026-08-07 14:35:23 +02:00
Marc-André Bombeck 9849a369a1 docs(changelog): record the stuck-hold fix
Half a minute on a spinner with two tabs open is a user-visible defect in
a release whose subject is exactly that surface.
2026-08-07 12:13:15 +02:00
Marc-André Bombeck ad094cba4d docs(changelog): record the document-close fix
It is a user-visible defect in a release that ships the surface it lives
on, so it belongs in the entry rather than only in the commit that fixed
it.
2026-08-07 10:27:22 +02:00
Marc-André Bombeck 2980cca200 docs: bring the sharing copy up to what the release ships
The operator page still said every grant is read-only, one release after
the write level shipped, and the table of what a grant cannot reach
predates both the manage level and managed profiles.

Rewritten against the frozen behaviour: the eight sections a grant can be
narrowed to and what each opens, the three levels and what each adds, the
step-up that is unconditional on a managed profile and conditional on an
ordinary invitation, and what a guardian may keep for a record that has no
login of its own. The AI row is corrected rather than dropped: a manage
grant reads insights that already exist, and no level causes an outbound
provider call.

Every claim on that page was read out of the code before it was written
down. The one about backups was cut instead, because it was not.
2026-08-07 08:20:08 +02:00
Marc-André Bombeck cd3f628f98 Name the refusal the last release notes left out
The fix that stopped a delegate overturning a recorded dose through the second
intake route landed on the trunk with no line in the notes. A change that
alters what somebody else may do to your record and says nothing about it is
the shape this project keeps rediscovering, and a release assembled from these
notes would have shipped it silently.
2026-08-04 16:44:24 +02:00
Marc-André Bombeck 25e86b36b3 Close the delegated-access gaps the release audit left open
Seven findings from the adversarial pass before v1.36.1 was tagged. One
blocking defect shipped in that release; these did not, and they are all places
where somebody looking after another person's health record meets a wall with
no explanation, or where a guard cannot fail.

The document vault was the largest. `/documents` is marked as a shared
destination, so the shell offers it under a switch, and every read behind it
resolved the caller instead of the record: the vault painted a query-error card
and the illness view painted another beside it. That is the same failure the
release describes as fixed on the dashboard. Five GET arms move to the record
resolver now, and only the GET arms: the list, one document, its file, its
thumbnail and the vault's usage payload, which the filter bar is built from.
Uploading, retyping, deleting, linking a document to an illness and every AI
verb stay with the owner, and the two reads that spend decryption CPU key
their quota on the person calling rather than the record, the rule
`medications/compliance` already follows.

Logging a value on a metric you track yourself leaves the admitted set. It was
admitted, named on the consent screen in six languages, and unreachable: the
only form that posts one sits on a page a shared record does not open, and the
component that renders it says so in its own comment while returning null. It
is the third route the rule that withdrew allergies and family history fitted
exactly, and it was applied to two of the three. Reading those values stays.

Two guards could not fail, and both were proved by mutation before and after.
The prefetch guard checked rule three with two regexes: one was satisfied by
the doc comment every page carries, the other missed an aliased import, and
the pair stayed green with the dashboard genuinely prefetching off the
switch-blind session. It reads the AST now, resolves aliases through the
module's own import table, and pins the five prefetching pages by name so a
page that stops being detected is loud. The activity verb map decided
membership on a literal `requireRecordAuth("write")`, so putting the argument
on its own line, which is what the formatter does the moment the call grows,
dropped a route out of the scan and turned a genuinely-red run green. Both
counts are of matches now rather than of files walked.

The rest are single instances of rules applied everywhere else. "Take all due"
was withheld on a note saying it rides the bulk intake route; it does not, and
its own docblock says why not, so a helper with five morning tablets tapped
five times for nothing. Marking a dose skipped is offered wherever marking it
taken is. A form could be opened in the frame before the app knew whose record
was on screen and then stood there once the answer arrived, on the capture
sheet and on the dashboard's quick-entry sheets. Two more copies of the intake
toast were still building their own, one of them with an Undo pointing at a
route no delegate may call. The customise link on the mood page led to a page
that exists to say it is not part of what was shared. And the access-level
radios on the invitation screen are hidden by design with no focus ring on
their label, so keyboard focus moved invisibly across the one screen where
somebody grants write access to their record.

Three new structural guards, each watched fail first: the customise link sits
inside a capability branch on every surface a delegate can reach, every intake
surface confirms through the one shared decision, and the batch sweep is
offered at the level that admits it.
2026-08-04 14:55:32 +02:00
Marc-André Bombeck 69343c8820 Stop a delegate deferring somebody else's medication reminders
Found by an adversarial pass over the release candidate before it was tagged,
and it is the reason the tag waited.

`POST /api/medications/intake` is admitted for delegates because a caregiver
standing next to the patient has to be able to say the tablet went down. It is
an update, though, and the same body can carry two other things. Snoozing
writes `snoozedUntil` on the OWNER's medication row, and the reminder cron
skips that medication for as long as the stamp is in the future. The field
takes any future instant. So somebody with write access could stop another
person's medication reminders for years, and three things made it quiet: the
owner is deliberately not notified of a delegated snooze, the activity feed
renders the whole family as "marked a dose", and the only accurate trace was a
line in the admin audit log.

The second was flipping a dose the owner had already recorded — taken to
skipped and back, which rewrites their compliance history and refunds
inventory. The consent screen promises that changing what is already there
stays with the owner.

Both refuse for a delegate now and stay open to the owner, who is the only
person either was ever for. A refused attempt is filed under the owner and gets
its own sentence in the activity panel rather than borrowing the one for
marking a dose, because folding them together is what made the gap dangerous.

Two more from the same pass:

The consent notice named allergies among the things a delegate may add. That
left the admitted set before this release shipped, and it said so in six
languages on the one screen where somebody decides who may write into their
health record. It also omitted half of what IS admitted. The copy now matches
the frozen list, and the test asserts the withdrawal rather than only the
presence of the rest.

The banner told a WRITE delegate nothing at all — the mode line rendered only
in the read case, guarded by a comment stating that write grants did not exist
yet. This is the release that made them exist. It matters that the banner say
it, because the rule that a refused control is absent rather than disabled
rests on the banner naming the mode globally.

And a third instance of the substitution this release already fixed twice: the
BMI chart divided the owner's weights by the height of whoever was looking.
Inside a shared record it draws nothing now rather than a number belonging to
two different people.
2026-08-04 12:30:01 +02:00
Marc-André Bombeck d77a3fe8d7 chore(release): v1.36.1 — a second person can contribute, and the front door works 2026-08-04 11:44:13 +02:00
Marc-André Bombeck 314bf26638 chore(release): v1.36.0 — one record, two people, and a line that says which 2026-08-02 20:28:53 +02:00
Marc-André Bombeck 3cf2cb417f chore(release): v1.35.6 — one line where there were two 2026-08-02 07:29:42 +02:00
Marc-André Bombeck c9ee84e5e4 chore(release): v1.35.5 — a read stops deciding what gets written 2026-08-02 01:30:12 +02:00