Commit Graph
9450 Commits
Author SHA1 Message Date
Marc-André Bombeck 88cea09c7b fix(measurements): cut chart day buckets at the user's midnight
Switching a chart from 7 to 30 days moved steps, active energy,
flights and distance totals onto neighbouring days. The 7-day range
buckets raw rows by the user's calendar day; the 30-day range read the
DAY rollup tier, whose buckets are UTC days, and the chart labelled each
bucket's UTC-midnight start in the user's zone. East of UTC a sample
shortly after local midnight counted on the previous day; west of UTC
every bucket read one day early.

The daily series (the chart read and the batched dashboard read) and the
aggregate=daily|weekly|monthly list now fold live in the user's zone and
return the instant of local midnight, so every range and every summed
metric shares one day boundary. The fold runs in Postgres and returns
one row per day; about 0.1 s for 90 days of per-minute samples. Windows
longer than the DAY cap still step up to the coarser rollup tiers.

Refs #1026
2026-09-24 07:56:33 +02:00
Marc-André Bombeck fccc44f7f1 test(ai): run the 0343 data steps against seeded rows
The Coach availability fold and the AI analysis carry-over for people
who hid the Coach are read out of the migration and run twice against
rows shaped like an upgrading instance, including a malformed
preference blob and the concurrency token.
2026-09-24 07:54:59 +02:00
Marc-André Bombeck b7a72489ed test(ai): pin the account payload's capability block against Postgres
Each state changes one layer against a fully set-up account and checks
the capabilities it owns: master switch, Reading documents, no
provider, the operator's key with and without consent, hiding the
Coach, the AI analysis opt-out, the workouts module, the single Coach
switch, a delegate, and a guardian limited to the operator's key.
2026-09-24 07:53:28 +02:00
Marc-André Bombeck cc4934897c docs(api): describe the AI capability block and refusal meta
AccountPayload gains ai with the AiCapabilities, AiCapabilityState,
AiUnavailableReason and AiProviderState components. The feature-flags
route is marked deprecated, drops correlations and documents
documentAi. The error envelope's meta documents capability, reason and
module, lists the assistant.disabled switches, and the catalogue
carries the three new ai codes.
2026-09-24 07:51:16 +02:00
Marc-André Bombeck 464522f315 feat(ai): publish capabilities on the account payload
GET /api/auth/me gains ai: the state of every capability and the
provider block, resolved for the active record and masked to the
grant like moduleAccess. The authority comes from the switch the
payload already resolved, so a delegate reads not_permitted_for_record
and a guardian is limited to the operator's key.

useAiCapability is the web reader and fails closed until the payload
arrives. GET /api/feature-flags becomes a deprecated projection of the
operator switches with a Deprecation header and a link to its
successor.
2026-09-24 07:49:14 +02:00
Marc-André Bombeck 7c757520f1 feat(ai): capability loader, gates and refusal envelope
loadAiCapabilityInputs reads the switches, module access, provider
presence and active consent kinds for one record in parallel, memoised
per request, and fails closed to check_failed. requireAiCapability,
getAiCapability and aiCapabilityForJob resolve for the request's
record, masked to the active grant like moduleAccess, or for a job's
record under the worker's authority.

apiHandler renders AiUnavailableError as one envelope with errorCode,
capability, reason and module. Existing codes stay: the operator
family keeps assistant.disabled.<switch>, modules keep module.disabled,
consent keeps consent.ai.required. New codes are ai.record.notPermitted,
ai.provider.none and ai.unavailable.
2026-09-24 07:46:12 +02:00
Marc-André Bombeck 1be2392c81 feat(ai): one provider presence probe
probeProviderChain describes the chain that would serve a record
without decrypting, building a client or refreshing a token: which
entries hold a credential, in chain order, whether each reads images,
the in-browser OCR opt-in, and where the credential comes from. It
applies the provider-work authority, appends the central Codex only
behind the opt-in, and falls back to the legacy provider only for an
empty chain. hasAnyConfiguredProvider and resolveProviderAvailability
now answer through it.
2026-09-24 07:39:56 +02:00
Marc-André Bombeck a7888a9011 feat(ai): replace the Correlations switch with Reading documents
The operator switches become master, Coach, Daily Briefing, Status
notes and Reading documents. Correlations goes: it gated statistics,
not anything a model writes. Reading documents covers the document
vault, lab scans and medication extraction.

The Coach module's operator availability now follows the Coach
switch, so there is one place to turn the Coach off server-wide. The
migration folds a stored Coach availability of false into the switch,
and gives everyone who hid the Coach the AI analysis opt-out, since
hiding the Coach no longer stops nightly briefing and status notes.

A switch read that fails now fails closed. The insights module is
described as AI analysis in every locale, and the switch guard now
keeps both retired switches out and freezes the callers of the old
operator-only gate.
2026-09-24 07:36:47 +02:00
Marc-André Bombeck c8a01ac3ed feat(ai): resolve AI capabilities from their layers
A pure resolver turns operator switches, module access, provider-work
authority, provider presence and consent receipts into one state per
capability, reporting the outermost reason. The document provider rank
moves to its own module so the resolver and the document routes read
the same order.
2026-09-24 07:26:33 +02:00
Marc-André Bombeck a5fdf675e1 feat(ai): add the capability vocabulary and table
One frozen table names every AI capability with the operator switch,
owning modules, consent rule, modality and provider order that decide
it. The reason enum is listed in precedence order. No I/O, so the
resolver, gates, contract and guards can all depend on it.
2026-09-24 07:23:57 +02:00
Marc-André Bombeck 01cd755de6 fix(medications): keep recent doses in the trailing history window
The Verlauf ledger dropped doses the full history showed. Three windows
ended too early or started too late:

- The server clamped the window floor to the medication's creation
  instant and filtered recorded doses by the same floor, so a dose
  logged for earlier the same day on a medication added afterwards
  fell out of "Last 90 days". The creation floor now bounds minted
  slots only; recorded doses use the requested window.
- A dose taken inside its window before the slot's own time is stored
  on the slot anchor, which still lay past the window end. The read
  horizon now reaches the latest recorded anchor within a day, without
  minting later upcoming slots.
- The client pinned from/to at mount and reused them on every refetch,
  so a dose added after the tab opened stayed hidden until a reload.
  The window is now resolved when the request fires and only "from"
  travels; the query key carries the window length instead of instants.

Refs #1028
2026-09-24 07:22:06 +02:00
Marc-André Bombeck aa58495cbe Merge pull request #1007 from muhdusama/medfix/apple-health-scheduled-mirror
fix: allow scheduled Apple Health medication mirrors
2026-09-20 23:47:32 +02:00
muhdusama 24ff031f7a fix: preserve legacy compliance response contract 2026-09-21 07:17:13 +10:00
Marc-André Bombeck 50717c567a docs: keep README screenshots on one line (#1022) 2026-09-20 20:15:57 +02:00
dependabot[bot] edb613bf2b chore(deps): bump docker/setup-buildx-action from 4.3.0 to 4.4.1 (#1020)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.3.0 to 4.4.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...f87e5991a6d7451dcb8d9637bfbc97413f497069)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 19:00:55 +02:00
dependabot[bot] 9402e48936 chore(deps): bump docker/build-push-action from 7.3.0 to 7.4.0 (#1019)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a...c3c9e263c25d99ce0380d002d59b67737d91b0dc)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 19:00:44 +02:00
dependabot[bot] 2c640683a7 chore(deps): bump undici from 8.10.0 to 8.10.2 (#1018)
Bumps [undici](https://github.com/nodejs/undici) from 8.10.0 to 8.10.2.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.10.0...v8.10.2)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 8.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 19:00:31 +02:00
dependabot[bot] fd8431ae47 chore(deps-dev): bump eslint-config-next from 16.2.12 to 16.3.5 (#1015)
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 16.2.12 to 16.3.5.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.5/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 16.3.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 18:24:50 +02:00
Marc-André Bombeck 271cebeead Merge pull request #1014 from MBombeck/MBombeck-patch-1
Enhance README with additional images
2026-09-20 17:50:30 +02:00
Marc-André Bombeck 6b1b061b0b Enhance README with additional images
Added images for insights, resting pulse, medications, and coach chat to the README.
2026-09-20 17:22:36 +02:00
Marc-André Bombeck 749f3682c0 Add assets 2026-09-20 17:18:05 +02:00
muhdusama 41d47c3184 fix: mark mirror adherence not applicable 2026-09-20 21:48:20 +10:00
muhdusama c223b6d77d fix: scope Apple Health schedule exemption 2026-09-20 20:46:47 +10:00
Marc-André Bombeck ac59e68a54 Merge pull request #1012 from MBombeck/docs/app-store-links
docs(readme): point at the App Store listing
2026-09-20 11:52:47 +02:00
Marc-André Bombeck 325e94a8f9 docs(readme): point at the App Store listing
The iOS client left public beta. The badge row, the navigation lines and
the iOS section named TestFlight only, which now reads as the single way
in. Both routes stay, with the price and the free-code offer stated
rather than left for someone to discover on the store page.
2026-09-20 11:36:27 +02:00
Marc-André Bombeck 0195c59667 Merge pull request #1010 from MBombeck/release/v1.38.25
Release v1.38.25
v1.38.25
2026-09-20 11:10:30 +02:00
Marc-André Bombeck 4f31b4b025 chore(release): v1.38.25 — readings that never arrived, and an import that said no 2026-09-20 10:53:24 +02:00
Marc-André Bombeck 6291c95b5a Merge pull request #1009 from MBombeck/fix/import-export-xml-name
fix(import): find the export XML whatever iOS named it
2026-09-20 00:33:54 +02:00
Marc-André Bombeck c405f15ef0 fix(import): find the export XML whatever iOS named it
The archive member was matched against the English lowercase spelling
alone. iOS writes that file name in the phone's language and some
versions capitalise it, so an ordinary export could arrive as
Export.xml or a translated name and was refused with a message telling
the reader their export was invalid.

The basename now matches in any capitalisation, and a single XML member
at the top of the archive is taken as the translated spelling. Apple's
clinical-document file never qualifies: it sits beside the real export
and would parse to nothing. When no member qualifies the error lists the
XML members the archive does hold, so the next report arrives with the
answer in it.

Refs #1008
2026-09-20 00:14:26 +02:00
muhdusama 30e454fb83 fix: allow scheduled Apple Health mirrors 2026-09-20 00:26:05 +10:00
Marc-André Bombeck c183d1862e Merge pull request #1003 from MBombeck/fix/cycle-symptom-severity-null
fix(cycle): accept a symptom logged without an intensity
2026-09-18 19:33:22 +02:00
Marc-André Bombeck 6113855b19 chore(openapi): regenerate after the symptom severity change 2026-09-18 19:10:39 +02:00
Marc-André Bombeck bff083f532 fix(cycle): accept a symptom logged without an intensity
Picking a symptom without tapping an intensity is how most of them are
logged. The sheet sends severity null for those, the schema took a number
or nothing at all, and the day log came back 422 with two issues for two
symptoms. All the sheet could say was "Could not save. Try again.", and
the cycle saved fine as soon as the symptoms were removed, which sent
people looking in the wrong place.

Nothing else disagreed: the link row has always stored an unrated symptom
as a plain presence, and the writer already maps a non-number to NULL.
Only the gate at the door refused it, on both the create and the patch
path, and no test touched either schema.

Refs #1001
2026-09-18 18:51:38 +02:00
Marc-André Bombeck 1fe90f3fab Merge pull request #1000 from MBombeck/fix/hk-percent-tolerance
fix(apple-health): take a percent from a fraction or an already-scaled client
2026-09-18 16:58:59 +02:00
Marc-André Bombeck 9d9e8570f7 fix(apple-health): take a percent from a fraction or an already-scaled client
Shipped iOS builds multiply oxygen saturation and body fat by 100 before
upload. The importer scaled them again, so 0.97 was stored as 9700, the
plausibility guard rejected the row as out of range inside an HTTP 200,
and the client treats that skip as final and moves its HealthKit anchor
past the sample. Every reading of those two types was lost, silently, on
every install that syncs them.

The contract says values travel raw and the server scales, and that is
what the OpenAPI description publishes. One comment in the schema said
the opposite, that the client must multiply by 100 first, and that is
the line the app was built against. It is corrected here.

The five percent-shaped HealthKit identifiers now take either spelling:
a value above the fraction ceiling is already a percent and passes
through, anything at or below it is scaled. The domains do not overlap,
and the per-type range guard still rejects what is impossible either
way. Current app builds stop losing readings without an app release.

The tests pin both spellings, the range after conversion, and that the
tolerant converter covers the percent class and nothing else: waist
circumference uses the same factor to turn metres into centimetres, and
a blanket change made a 1.02 m waist into 1.02 cm.

Refs MBombeck/healthlog-iOS#7
2026-09-18 16:40:11 +02:00
Marc-André Bombeck 244d0e4b69 Merge pull request #999 from MBombeck/release/v1.38.24
Release v1.38.24
v1.38.24
2026-09-16 18:48:07 +02:00
Marc-André Bombeck 5188b28ea6 chore(release): v1.38.24 — Insights loads without the assistant, and the database stays current 2026-09-16 18:27:51 +02:00
Marc-André Bombeck bf728ff552 Merge pull request #998 from MBombeck/dependabot/npm_and_yarn/types/node-26.5.1
chore(deps-dev): bump @types/node from 26.1.1 to 26.5.1
2026-09-16 15:47:54 +02:00
Marc-André Bombeck c2ec2b76cf Merge pull request #994 from MBombeck/dependabot/npm_and_yarn/sonner-2.0.8
chore(deps): bump sonner from 2.0.7 to 2.0.8
2026-09-16 15:47:26 +02:00
dependabot[bot] 34182cbb4f chore(deps-dev): bump @types/node from 26.1.1 to 26.5.1
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 26.1.1 to 26.5.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 13:23:10 +00:00
dependabot[bot] 4ac11c71cb chore(deps): bump sonner from 2.0.7 to 2.0.8
Bumps [sonner](https://github.com/emilkowalski/sonner) from 2.0.7 to 2.0.8.
- [Release notes](https://github.com/emilkowalski/sonner/releases)
- [Commits](https://github.com/emilkowalski/sonner/compare/v2.0.7...v2.0.8)

---
updated-dependencies:
- dependency-name: sonner
  dependency-version: 2.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 13:10:36 +00:00
Marc-André Bombeck 629731120a Merge pull request #997 from MBombeck/dependabot/npm_and_yarn/jose-6.2.12
chore(deps): bump jose from 6.2.10 to 6.2.12
2026-09-16 15:09:43 +02:00
Marc-André Bombeck c33a88b282 Merge pull request #993 from MBombeck/dependabot/npm_and_yarn/napi-rs/canvas-1.0.9
chore(deps): bump @napi-rs/canvas from 1.0.7 to 1.0.9
2026-09-16 15:09:20 +02:00
Marc-André Bombeck 19ba97e3fc Merge pull request #990 from MBombeck/dependabot/npm_and_yarn/sax-1.6.1
chore(deps): bump sax from 1.6.0 to 1.6.1
2026-09-16 15:08:31 +02:00
dependabot[bot] 9db3770ea3 chore(deps): bump @napi-rs/canvas from 1.0.7 to 1.0.9
Bumps [@napi-rs/canvas](https://github.com/Brooooooklyn/canvas) from 1.0.7 to 1.0.9.
- [Release notes](https://github.com/Brooooooklyn/canvas/releases)
- [Changelog](https://github.com/Brooooooklyn/canvas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Brooooooklyn/canvas/compare/v1.0.7...v1.0.9)

---
updated-dependencies:
- dependency-name: "@napi-rs/canvas"
  dependency-version: 1.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 12:06:47 +00:00
dependabot[bot] fade5d467e chore(deps): bump sax from 1.6.0 to 1.6.1
Bumps [sax](https://github.com/isaacs/sax-js) from 1.6.0 to 1.6.1.
- [Commits](https://github.com/isaacs/sax-js/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: sax
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 12:06:47 +00:00
dependabot[bot] 453c1141a7 chore(deps): bump jose from 6.2.10 to 6.2.12
Bumps [jose](https://github.com/panva/jose) from 6.2.10 to 6.2.12.
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.10...v6.2.12)

---
updated-dependencies:
- dependency-name: jose
  dependency-version: 6.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 11:54:00 +00:00
Marc-André Bombeck 6f562c05ca Merge pull request #992 from MBombeck/dependabot/npm_and_yarn/nodemailer-10.0.9
chore(deps): bump nodemailer from 9.1.1 to 10.0.9
2026-09-16 13:53:51 +02:00
Marc-André Bombeck 78996e6ba5 Merge pull request #996 from MBombeck/ci/pin-trivy-installer
ci(security): pin and verify the Trivy installer
2026-09-16 13:46:41 +02:00
Marc-André Bombeck 01cc7197fb Merge pull request #995 from MBombeck/chore/postgres-major-ignore
chore(deps): keep PostgreSQL majors out of the update stream
2026-09-16 13:46:37 +02:00