Skip to content

Release v1.37.19 - #793

Merged
MBombeck merged 16 commits into
mainfrom
release/v1.37.19
Aug 14, 2026
Merged

Release v1.37.19#793
MBombeck merged 16 commits into
mainfrom
release/v1.37.19

Conversation

@MBombeck

Copy link
Copy Markdown
Owner

Second wave of the systematic QA pass: the P2 package.

Headline: medication stock understands per-slot doses end to end. The remaining-doses figure weighs each slot by its resolved dose, the API carries resolvedUnitsPerDose per schedule plus runwayDays on list and detail, and the low-stock engine runs on the same arithmetic so push and wire cannot disagree.

Also in this wave: the operator budget cap applies when the provider chain falls back onto an operator key; a document whose summary job died heals to an honest unavailable state after an hour, backed by an hourly sweep; the coach reminder sweep caps nagging at three mornings; disclaimer acknowledgement is versioned; restore reports written counts for every section including practitioners, appointments and vaccinations; the portable export names decrypt failures; timezone changes re-fold the compliance statistics; the all-time summary includes readings older than five years and the AI context falls back to live data on missing bands; measurement reminders claim their notification slot before sending; the intake-event and workout list API descriptions match the real wire; copy and layout polish across all six languages; the six measurement routes share one post-write helper; residual fixed sleeps in the e2e suite poll on real conditions and the expiry fixtures no longer age.

Every behaviour fix carries a recorded watched-red test. No migrations. OpenAPI changes are additive.

Battery: typecheck, lint, unit (21367 passed), format:check, integration (202 files, 1845 passed), production build all green in the working tree.

- Singular variants for the bulk-delete toast and confirm dialog
  (measurements + mood); the call sites branch on a count of one.
- Consent confirm buttons shortened so they no longer overflow the
  small button on phones.
- Workouts empty-state CTA is source-neutral instead of assuming an
  iPhone with Apple Health.
- "Pills" is no longer a navigation term in a medication app: DE says
  Navigations-Chips, EN chips, PL chipy; ES/FR/IT subtitles align with
  their own established terms.
- Health-score insufficient hint drops the translationese and fixes the
  pronoun.
- Side-effect tag helper uses the established Stimmungs-Tags term.
- Success toasts that are single fragments lose the terminal period
  (37 keys, all locales).
- Integration pill parked state short enough to survive truncation.
- The iPhone-managed reminder badge carries a short label; the full
  sentence moved to a helper line below instead of clipping mid-word.
- Vorsorge dashboard tile title joins the dashboard heading ladder
  (titleAs=h2 like its sibling tiles).
- A user's own note on the mobile measurement card renders in the
  foreground colour; user data is never muted.
- The compact Vorsorge list row expresses its density on the Card
  instead of overriding the content slot's padding with an off-scale
  step.
- The AI-consent card holds its space with a skeleton while the receipt
  loads instead of popping in and shifting the section below.
…e unread analytics columns

- The daily Coach sweep now charges each ignored day against
  surfaceCount and auto-dismisses at three — the cap the schema comment
  promised since v1.22 but nothing enforced.
- The welcome gate compares the stored disclaimer acknowledgment
  version against the current one, so revised disclaimer text
  re-prompts; /api/auth/me carries the version for the client.
- slope90 and anomalyCount leave the analytics wire: slope90 had no
  reader anywhere (computed on every request, in SQL twice), and the
  slim path could never compute anomalyCount (it always reported 0).
  anomalyCount stays as an in-process field on summarize() for the
  insights feature builder, which is its only real consumer.
… dead settings slugs

- The Coach verbosity picker is disabled while the concise tone forces
  brief, with a hint saying so, instead of a live control that silently
  does nothing.
- A fresh document upload flashes its new row like the duplicate path
  always did; the settle decision is a pure exported helper with a test.
- /about gains its two inbound links: the /privacy footer and a
  Settings→About credits row (the GeoLite2 EULA wants the attribution
  reachable from the running app).
- The eight settings slugs shadowed by permanent redirects (the seven
  layout children + sharing) leave the slug registry: no more
  prerendering unreachable pages, no dead component wirings, and the
  SectionPlaceholder fallback that could never render is gone. The
  redirects, section components, and layout-hub subpages are unchanged.
…he medication wire

A medication whose slots carry different doses (a whole tablet in the
morning, a half at noon — #219) was consumed correctly but reported
wrongly: the wire shipped only the raw nullable per-slot column (clients
had to derive the inheritance), stockDosesRemaining divided by the
medication-level units alone, and no runway figure existed outside the
low-stock push.

- Every schedule now carries resolvedUnitsPerDose, resolved server-side
  exactly like the intake consumption resolver.
- stockDosesRemaining divides the units pool by the schedule-weighted
  average units per dose.
- runwayDays (whole days of supply under the slot-aware burn rate) rides
  the list AND detail payloads; the detail GET also gains the stock
  aggregates the list already had.
- The low-stock engine runs the same slot-aware math, so the push and
  the wire cannot disagree; the web cards, table, and detail Übersicht
  prefer the server figure over their client estimate.
- OpenAPI regenerated with the new fields.
… DTO

- MedicationIntakeEvent declares the full row both intake responses
  actually return: autoMissed, attributionSource, injectionSite,
  doseTaken, inventoryConsumption, externalId, updatedAt, syncVersion,
  deletedAt were served but undeclared.
- GET /api/medications/{id}/intake is published (query schema, paged
  envelope); the spec only knew the POST while the client-facing
  history list has consumed the GET since v1.4.37.
- WorkoutListEntry's sportType/startedAt/endedAt/durationSec become
  nullable, matching the DTO — declaring them non-null was a
  strict-decoder bomb for the Swift client.
- OpenAPI regenerated.
- The provider-chain walker now checks the day's recorded spend against
  OPERATOR_COST_CAP before invoking an operator-funded admin-* entry. A
  request whose primary provider runs on the user's own key reserves
  under the generous user-plan ceiling; a fallback hop (or health-ledger
  reorder) onto the operator's key could then spend past the operator's
  cap unchecked. An exhausted cap skips the candidate like a failed hop.
- summaryState PENDING gets a giver-upper: the detail serialiser
  degrades a PENDING older than an hour to UNAVAILABLE (which offers the
  manual generate action), and an hourly reaper persists the same heal
  following the orphan-ImportJob reconcile precedent — a job dead past
  its retry budget no longer leaves the sheet saying "generating"
  forever.
The six measurement routes carried eight copy-pasted rollup-recompute +
status-invalidate blocks — the documented two-ended failure class
waiting for the next surface to remember one leg and forget the other.
All of them now call afterMeasurementMutation (the helper the deletion
fix introduced), and the two write surfaces that bypassed the hook
entirely join it: the nightly Recovery/Stress/Strain score rows and the
PHQ-9/GAD-7 assessment projection now recompute their (type, day)
bucket on the tick that writes them.
…vault probes

- The measurement-reminder tick claims its slot in the record-event
  ledger BEFORE any provider egress (the documented claim-first pattern
  from reminder-dedup.ts); a crash between dispatch and the nextDueAt
  advance can no longer double-send.
- mood-entries/bulk classifies a lost P2002 race as the duplicate it is
  (with the winning row id) instead of a skip carrying a raw Prisma
  message — the row IS stored, the racing twin wrote it. The workouts
  batch already re-derives per-entry status from RETURNING.
- The vault list's content-index and thumbnail probes ride the same
  Promise.all as the link loads: four independent grouped queries, no
  serialized round-trips on a 4s-polled list.
…d on zone change

- The restore summary counts practitioners, encounters, the three link
  tables, and vaccinations — restored in full since 08-01 but absent
  from the report, so a file carrying visits or an Impfpass read as if
  those sections were empty.
- A portable export that cannot open a field's ciphertext still nulls it
  fail-soft, but now names the loss in the file's own decryptFailures
  manifest — an export with a lost emergency note is no longer
  byte-identical to one where the note was never written.
- A timezone change enqueues the medication-compliance re-fold: the
  rollup keys its rows by the local day minted under the OLD zone, and
  the coverage probe compares counts, not keys, so nothing else could
  notice the stale bucketing.
- account-sharing polls the offline persister's own IndexedDB store for
  the flushed snapshot instead of sleeping past the debounce.
- The record-session-fence cases poll for the observable halves of their
  setups (the switch round-trip landing, the exit switch being issued);
  the one remaining fixed window is a documented absence-assertion grace
  with nothing to poll on.
- The documents suite's two remaining sleeps are named for what they
  are: a focus-dance grace the outer document cannot observe, and the
  long-press gesture duration itself.
- The inventory-date fixtures derive their printed expiry from now
  instead of a fixed 2027 date that would silently flip the fixture to
  expired when the calendar catches up.
…l-time stats

- The tiered AI context's band reader now falls back to a live grouped
  aggregate when a rollup band is empty (the module doc promised
  replace-with-fallback from day one; only the replace half existed), so
  a coverage-miss or never-folded YEAR partition no longer hands the
  model empty history. Far-from-UTC users get the day-grain band keyed
  on their own calendar (the same ±3h guard the correlation readers
  carry).
- The slim analytics slice splices measurements older than the 5-year
  fold window into its all-time count/min/max/mean: the rollup tier
  only covers the window, so "all time" silently truncated for long
  histories while the live fallback told the truth. A type whose only
  rows are pre-fold no longer reads as never logged.
- The record-settings contract, slug-list, and section-frame tests
  follow the slugs that left the registry (layout children resolve
  their headings from the layout namespace, like production).
- The orphaned settings.sections.<module>.title/description keys leave
  all six locales; the reverse-coverage guard flagged them the moment
  their nav entries stopped existing.
- The operator-cap ledger read fails open on a read error — the
  reservation layer still meters against the same ledger, and a
  transient blip must not take the fallback chain down.
- The reaper queue joins the subsystem surface; its healed count and
  the sweep's nag_dismissed join the job-fact vocabulary.
- The intake-event contract fixture carries the full row the resource
  now declares; the workouts empty-state test asserts the
  source-neutral cue; the persister poll gates on rendered content
  first.
@MBombeck
MBombeck merged commit d8cbe25 into main Aug 14, 2026
23 checks passed
@MBombeck
MBombeck deleted the release/v1.37.19 branch August 14, 2026 06:44
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