Commit Graph
4712 Commits
Author SHA1 Message Date
Marc-André Bombeck a4bb2adcb2 fix(e2e): select the measured wrapper by data-slot, not by birth order
The navigation-geometry spec failed on a merge commit whose tree was
byte-identical to the branch head that had passed minutes earlier, so nothing had
regressed. The spec resolved the element it measures with
`main.firstElementChild`, independently in each of its two `page.evaluate` calls.
Any node mounting ahead of the wrapper between the two reads made it measure two
different elements, which is why the failure was 64 against 131.92 rather than a
two-pixel drift. A timing problem gives a small difference or a null; this was a
substitution.

The shell's content wrapper now carries a stable `data-slot`, both reads select
it by that, and the wait gates on the element rather than on scroll state alone.
The same positional pattern in the overscroll spec is fixed with it, so the class
is gone from the measurement paths rather than from one spec.

Re-running the job made it green, which is a workaround. This is the fix.
2026-07-30 14:35:08 +02:00
Marc-André Bombeck 8d86416072 fix(compose): stop the worker example drifting, and bound logs and memory
The `app` service declares 62 environment variables. The commented-out
`app-worker` example declared 14, missing every SMTP, VAPID and APNs value plus
the AI-host allowlist and the offline geo directory. The worker is the process
that sends notifications and runs the AI jobs, so anyone following the documented
split got a worker that could not email, push or send APNs, and nothing failed
loudly. It just did nothing.

Hoists the shared block to a YAML anchor both services merge, so copy-paste
cannot drift again. The resolved configuration is unchanged: 62 variables on
`app` and 3 on `db` before and after.

Also:

- The commented GeoLite2 mount pointed at a directory the default GEOLITE2_DIR
  does not read. Uncommenting the example as written did nothing. The two now
  agree.
- Bounded log rotation on both services. The default json-file driver grows
  without limit and fills the disk on exactly the small hosts this file targets.
- `cap_drop: [ALL]` on the app, and a written reason why the database does not
  get it: the Postgres entrypoint starts as root and needs CHOWN/SETUID/SETGID
  to hand off to its own user. Dropping capabilities there breaks startup, which
  was confirmed rather than assumed.
- A memory limit on the app, and a written reason why the database has none. A
  bounded app container restarts clean when the kernel takes it; a database
  killed mid-write does not.

`read_only: true` stays off for now: the app writes a runtime cache and that
needs testing before it can be turned on.
2026-07-30 14:34:55 +02:00
Marc-André Bombeck 207010e8fa docs(deploy): warn that an image pin is sticky and a stale pin is silent
The digest-pinning section explained how to set HEALTHLOG_IMAGE_REF and never
said what happens if it is left behind. It stays until someone changes it, and
every deploy in between reports success while the running instance holds the
previous release.

Adds the warning, the two post-deploy checks that would have caught it (the
version endpoint AND the running container's image, because the first alone can
be served from a cache), the note that a suspiciously fast deploy is a signal,
and the duplicate-key trap: the platform accepts two rows with the same key
without warning and does not define which wins.
2026-07-30 14:30:19 +02:00
Marc-André Bombeck a8b61abf6f feat(cycle): drop the standing disclaimer from the cycle-tracking settings card
The card carried a four-sentence non-medical notice at the top of the cycle
on-ramp. It goes, at the maintainer's decision.

The goal-specific caveat on the prediction surface is untouched: a profile set to
avoid pregnancy still gets the stronger "not a contraceptive method" copy, which
is chosen server-side by goal and shared with the native client, so nothing about
that path changes here.

Removes the key from all six locales and the now-unused icon import.
2026-07-30 14:30:18 +02:00
Marc-André Bombeck ed05c49f6f Merge pull request #668 from MBombeck/release/v1.34.2
release/v1.34.2
v1.34.2
2026-07-30 12:29:54 +02:00
Marc-André Bombeck b8c9a519c1 chore(release): v1.34.2 — count a re-sent workout once on every surface; re-anchor the service-worker version
Carries the four fixes that landed after the v1.34.1 tag plus the two found while
reviewing them.

The workout collapse that shipped in that batch only worked on the workout list,
because it keyed a session on a field three of the five callers never fetched.
Those three kept averaging one session into a person's baseline more than once.
The service-worker fallback version had drifted a release behind with nothing
checking it; both halves now have a gate.
2026-07-30 11:58:20 +02:00
Marc-André Bombeck 17ea7bea2d fix(sw): re-anchor the fallback cache version and gate it against the release
The service worker derives its cache names from the version injected by
`sw-version.js`, and falls back to a literal compiled into `sw.js` when that
import fails. The prebuild step rewrites both from `package.json`, so both were
correct as long as a release rebuilt. One did not, and the literal trailed the
release by a version while every test stayed green.

Nothing checked it. The generated file is compared against `package.json`, and
the behavioural suite deliberately reads the literal out of the source so its
cache-name assertions cannot drift from the literal they exercise. Reasonable
apart, and together they left the literal itself unverified.

The effect is confined to the degraded path and is still worth closing: a client
that cannot load `sw-version.js` computes the previous release's cache names, so
it keeps serving the old shell and its cleanup treats the current caches as the
stale ones.

Re-anchors the literal and adds the equality as a gate, since it is a question
with an exact answer. A version bump committed without a rebuild now fails
locally instead of shipping.
2026-07-30 11:55:16 +02:00
Marc-André Bombeck 00c282d311 fix(workouts): collapse a re-sent session on every surface, not just the list
The canonical picker keyed a session on sport, start, end and duration. Only the
workout list selected the end timestamp; the Coach narration, the per-sport
baseline and the deep-link cluster passed rows without it, so those rows carried
no session identity at all and the duplicate survived. One record counted once in
the list and two or three times everywhere else, including in the averages a
person is compared against.

Drop the end timestamp from the identity. On a workout with pauses it measures
wall-clock time while the duration counts only moving time, so two re-sends of
one session can differ in that field alone and the stricter key rejected a
genuine match. It also bought nothing in exchange: nobody starts a second
workout of the same sport in the same instant with the same duration.

The deep-link cluster now selects the duration it needs. A structural guard
freezes the call-site set and records which file owns each caller's select, so a
new surface cannot join the set without fetching the identity, and the identity
cannot grow a field only some callers fetch.
2026-07-30 11:54:58 +02:00
Marc-André Bombeck 3811d4592e Merge pull request #666 from MBombeck/release/v1.34.1
docs: polish v1.34.1 release notes and remove planning files
2026-07-30 11:34:00 +02:00
Marc-André Bombeck 83af211641 chore(openapi): regenerate the spec for the badminton sport type
The badminton mapping added the enum member in the Zod registry and left the
generated spec behind, so the drift gate failed. One line, `- badminton`.

Third failure this branch revealed in sequence: the formatter hid a red test,
the red test hid this. Each gate only runs once the one before it passes, so a
single unformatted line kept two real problems invisible.
2026-07-30 11:01:11 +02:00
Marc-André Bombeck 1f87e63662 fix(insights): restore the shared card rhythm the two assessment cards lost
The spacing pass on this branch tightened `insight-status-card` to `gap-1.5`
and left `mood-insights-sections` at `gap-2`, so two sibling cards that are
meant to read as one family stopped matching. `assessment-spine-order.test.ts`
exists to hold exactly that contract and went red as soon as the formatter
stopped masking it: `format:check` failed first in the job, so the unit tests
never ran and the divergence shipped unnoticed onto the branch.

Bring the mood card to the same value rather than widen the other one back.
The direction of travel is the tighter rhythm, and both allowed steps are on
the sanctioned spacing scale.

The test's expected string moves with the contract, not around it: it still
asserts that both cards carry the SAME value, which is the whole point of it.
2026-07-30 10:49:06 +02:00
Marc-André Bombeck bc94223423 style(insights): run prettier on the metric stat strip
The formatter joins one JSX line that was left wrapped. No behaviour change.
It is the only thing standing between this branch and a green gate: typecheck,
tests, e2e, integration and both image builds already pass, and `format:check`
fails on this file alone.
2026-07-30 10:33:24 +02:00
Marc-André Bombeck c83254032e fix(google-health): map badminton workouts 2026-07-29 22:24:04 +02:00
Marc-André Bombeck 0c7b958b1b fix(workouts): collapse repeated same-source sessions 2026-07-29 21:30:52 +02:00
Marc-André Bombeck 90b1a4eaee fix(insights): improve narrative contrast and spacing 2026-07-29 21:28:29 +02:00
Marc-André Bombeck ab450bbfd1 fix(dashboard): keep hero headline when score lead is removed 2026-07-29 21:20:16 +02:00
Marc-André Bombeck e1c351e2b7 docs: polish release notes and remove planning artifacts 2026-07-29 19:19:18 +02:00
Marc-André Bombeck 9e85eb13a1 Merge pull request #665 from MBombeck/release/v1.34.1
v1.34.1 Security and reliability release
v1.34.1
2026-07-29 17:54:14 +02:00
Marc-André Bombeck 7c79b1a472 ci: acknowledge intentional release scanner exports 2026-07-29 17:39:42 +02:00
Marc-André Bombeck b1e20f59dc release: prepare v1.34.1 2026-07-29 17:31:31 +02:00
Marc-André Bombeck bdca4f5724 fix(dashboard): compact score-only hero 2026-07-29 17:05:53 +02:00
Marc-André Bombeck 8a19699a59 docs(security): record v1.34.1 dependency audit 2026-07-29 16:51:05 +02:00
Marc-André Bombeck 1682c3b54d fix(web): render public pages under nonce CSP 2026-07-29 16:46:19 +02:00
Marc-André Bombeck aaa51fb494 docs: capture v1.34.2 dependency remediation 2026-07-29 16:45:39 +02:00
Marc-André Bombeck ee2c93a67b test(medications): assert bounded replay details 2026-07-29 16:43:22 +02:00
Marc-André Bombeck 92218ccc00 test(dashboard): isolate arrival reaction browser coverage 2026-07-29 16:34:58 +02:00
Marc-André Bombeck f54555d187 fix(build): stabilize release bundle gates 2026-07-29 16:19:30 +02:00
Marc-André Bombeck 271f0ed28a fix(medications): make import replay results honest 2026-07-29 16:11:45 +02:00
Marc-André Bombeck 5b695f43ea fix(analytics): restore device-priority sleep selection 2026-07-29 16:08:41 +02:00
Marc-André Bombeck fb02cf89b1 fix(jobs): reconcile durable fact inventory 2026-07-29 15:59:27 +02:00
Marc-André Bombeck 75aa850eae style: satisfy v1.34.1 format gate 2026-07-29 15:59:12 +02:00
Marc-André Bombeck b0a94b3423 fix(web): stabilize document and example downloads 2026-07-29 15:57:20 +02:00
Marc-André Bombeck fb9ae2ac61 fix(coach): keep stateless turns grounded 2026-07-29 15:56:19 +02:00
Marc-André Bombeck 9354c649fe fix(integrations): surface Google sync progress and canonical workouts 2026-07-29 15:55:37 +02:00
Marc-André Bombeck b9879e8829 fix(nightscout): pin approved private origins 2026-07-29 15:54:01 +02:00
Marc-André Bombeck 8db21ab718 fix(jobs): persist bounded success outcomes 2026-07-29 15:50:42 +02:00
Marc-André Bombeck 7acc711d9e test(01-24): record blocked merged release gate 2026-07-29 15:43:31 +02:00
Marc-André Bombeck 8b3b2a388e docs(01-24): record merged re-audit block 2026-07-29 15:26:59 +02:00
Marc-André Bombeck 8a3c3d096f docs(01-23): complete locale reconciliation plan 2026-07-29 15:13:04 +02:00
Marc-André Bombeck b41b32d472 feat(i18n): complete v1.34.1 locale reconciliation 2026-07-29 15:11:40 +02:00
Marc-André Bombeck 163aa73e01 feat(i18n): reconcile English German and Spanish copy 2026-07-29 15:08:50 +02:00
Marc-André Bombeck 67eeefd584 fix(i18n): translate final security and import states 2026-07-29 15:07:35 +02:00
Marc-André Bombeck b6dfe62c9d test(i18n): inventory v1.34.1 locale contracts 2026-07-29 15:07:05 +02:00
Marc-André Bombeck d34d781d78 docs(01-22): record merged green outcome gate 2026-07-29 15:05:38 +02:00
Marc-André Bombeck 1dcadcb0f0 docs(01-21): complete Google sync fairness plan
- record atomic task commits and verification evidence
- retain GH-01 and GH-06 as live-account evidence gates
2026-07-29 15:04:53 +02:00
Marc-André Bombeck 5bc213c5b8 test(01-21): type guarded progress mock calls
- declare the updateMany mock argument tuple
- assert the guarded write exists before inspection
2026-07-29 15:04:08 +02:00
Marc-André Bombeck ba6d06be3b feat(01-21): expose Google sync terminal status
- return redacted per-resource outcomes from manual sync
- poll owner-scoped progress and invalidate committed workout views
2026-07-29 15:01:13 +02:00
Marc-André Bombeck f3008c9ee0 docs(01-26): complete workout pulse plan 2026-07-29 15:00:38 +02:00
Marc-André Bombeck 104a058096 style(workouts): format pulse enrichment 2026-07-29 14:58:46 +02:00
Marc-André Bombeck 5ec1a3836f feat(01-21): add fair Google sync outcomes
- finish workout and low-cost resources before dense metrics
- report bounded terminal outcomes and invalidate workout caches
2026-07-29 14:58:40 +02:00