The three intake dialogs (log, edit, dose-history add) still used a raw
datetime-local input, so they ignored the user's date-format preference.
Route them through DateTimeField like every other date surface. Also
correct a stale diastolic-floor comment in the dashboard verdict.
The medication detail clients computed the supply headline in the browser
via summariseSupply, risking web/iOS drift and pulling the shared math
into the client bundle. The GET /api/medications/[id]/inventory route now
computes the canonical SupplySummary server-side through summariseSupply
(the single source of truth) and ships it in the DTO; the Bestand and
Übersicht surfaces render the ready figures. Adds the MedicationSupplySummary
schema to the inventory list response and regenerates the OpenAPI contract.
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.
Bring the medication card, scheduling, wizard, and detail component
files in line with the kebab-case file-naming convention. Component
export identifiers are unchanged; only filenames and the import paths
that point at them move.
Restructure the medication detail advanced surface (Erweitert + API tabs)
into expand/collapse groups: SettingsGroup gains an opt-in collapsible
mode built on the same Radix Collapsible primitive SheetSection uses, so a
long settings wall reads as scannable sections. Lifecycle and external-API
groups default open; Data and the destructive zone default collapsed.
Add a titration timeline to the injection tab — completed and planned
dose-escalation steps rendered as a vertical timeline with a "you are
here" marker. Reads the existing MedicationDoseChange stream (future-dated
rows are planned steps), so no schema change is needed. Neutral palette,
no state-driven colour.
Move the low-stock trigger from a bare runway threshold to the larger of that
threshold and the reorder lead time plus one dose interval, so a weekly
medication is flagged before its last dose rather than after — early enough to
reorder. The change only moves the alert earlier; no one's threshold shrinks.
Surface concrete dates (runs out on, reorder by) and a running-low vs last-dose
state through the existing badge and channel, never the card colour. A reorder
lead time is configurable globally with a per-medication override, and the glp1
card flag now runs the same runway decision as the daily check instead of a
stale dose count.
The low-stock alert fired when the projected runway dropped below the
user threshold (default 7 days). For a sparse cadence — a weekly
injection's 7-day floor is barely one dose-interval — the warning landed
with about one dose left, too late to reorder before the supply ran out.
Add an optional per-medication reorder lead time plus a user-level
default (0–60 days, default 10) and widen the alert trigger to
max(lowStockRunwayDays, leadDays + cadenceIntervalDays), so the warning
lands before the last dose for any cadence. Because the trigger is a
max() over the user floor it never shrinks anyone's current threshold; a
daily med with no lead keeps its prior boundary.
The push body and the card notice surface concrete dates — supply runs
out around <date>, reorder by <date> — with a last-dose informational
state and the days/units fallback when supply data is thin. Retire the
stale fixed 4-dose count threshold on the GLP-1 details endpoint and
route its card lowStock flag through the same runway evaluation so the
card and the daily cron agree. Surface the lead default in the low-stock
settings card with progressive disclosure; a single medication can
override it.
Additive schema: nullable Medication.reorderLeadDays (migration 0161, no
backfill) and notificationPrefs.medication.reorderLeadDays. OpenAPI
regenerated.
The intake write invalidates the dashboard snapshot, but the default active
refetch type only refetches mounted queries — and the snapshot is unmounted
while the user is on the medication card or detail page. On navigating back it
remounts under refetchOnMount:false, so the pre-write cache was served and the
due prompt lingered until a hard reload. Force the inactive snapshot to refetch
after every intake write so the dashboard clears as soon as the dose is taken.
A dose can now consume a fraction of a unit — a half, third or quarter
tablet for a split pill. The wizard offers a curated fraction set beside
the whole numbers (the most error-resistant input); units-per-dose and the
per-container counts widen from Int to Decimal, so a 30-tablet pack dosed
at a half reads 29.5 after one dose and the days-left projection follows.
The consumption ledger stamps and refunds the exact fraction, and existing
whole-number doses are untouched. OpenAPI regenerated — the three unit
fields move from integer to number — and the units-per-dose hint is
updated across the six locales.
Also anchors a first-use date relative to now in an inventory route test
that had aged past its 30-day in-use window.
- The escalated card row keeps its time anchor: the red badge carries
the matched window or the overdue-slot value beside it, and the
inline row's range stays visible on mobile, where it was the only
clock on the card.
- Take-all-due collects what the cards offer: a dose escalated past its
catch-up band joins the set with exactly the slot the card's own
button would record; as-needed medications never join.
- The table's stock warning follows the user's runway threshold instead
of a leftover four-dose constant; threshold off means no amber, empty
stays red.
- The card itself shows the remaining runway in the warning tone once
it crosses the threshold — only then, the card stays calm above.
- The low-stock push deep-links straight into the supply tab and its
APNs payload keeps url, runwayDays and thresholdDays; supply tab and
threshold setting link to each other; the reminder-thresholds and
notification-prefs endpoints enter the OpenAPI contract.
A medication can declare itself as-needed: no schedules, never due,
never reminded, excluded from every compliance rate — but intakes log
ad-hoc, consume inventory exactly like scheduled doses, and the
history stays. The wizard offers it as a cadence choice and round-
trips it on edit; card, table and detail show a calm marker where the
next dose normally sits, the take-all dialog never collects one, and
the supply runway stays unknowable by construction. (#316)
When two or more medications are due, the page header offers a single
confirm dialog listing them with dose and window. The takes loop the
per-medication intake route rather than the sync bulk surface — slot
attribution, inventory consumption and idempotency stay byte-identical
to individual taps, and a failed medication stays due while the rest
land. (#316)
Containers can be removed from the supply tab — the endpoint existed,
the UI never offered it; a stamped container deleted later was already
refund-tolerant. The adjust trigger wears the outline button vocabulary
instead of reading as dead text, and the state badge moves inline next
to the dose figures as plain meta text instead of posing as a button in
the action cluster.
/settings/medications now hosts the view preference (cards or table)
and the manual-order editor inline, following the dashboard and
insights pattern. The page header's gear icon links there instead of
opening a dialog; the quick view toggle stays next to the add button.
The order editor keeps its grouped active/inactive semantics — a saved
order can never interleave the groups.
The pill trigger paints its own label spans and mounts no SelectValue,
and Radix's item-aligned placement silently skips all positioning when
the value node is absent — the open menu laid out below the
height-locked app shell, fully off-viewport, which reads as a dead
control. The mood and type/source pills on the mood and measurements
lists were affected since the unified filter rail landed in v1.16.1.
Popper placement has no value-node dependency and anchors the menu
under the pill like the date-range popover next to it.
- The legacy GLP-1 ledger reads again: GET and the Coach snapshot fall
back to ledger-derived pen/dose figures when a medication has no
inventory items; items always win once they exist. The delta branch
on the POST is marked deprecated in favour of the inventory endpoints.
- Expired containers count as available nowhere: one shared supply
summary (ACTIVE/IN_USE with units) feeds the overview row, the
Bestand summary, the list payload, the GLP-1 card, and the runway
estimate; expired units surface as a muted suffix instead.
- The inventory endpoints speak one wire dialect: request fields renamed
to unitsTotal/unitsRemaining to match the responses, OpenAPI regenerated.
- The reorder dialog mirrors the page's grouping: active and inactive
sections, each sortable within itself, so a saved order can never
interleave them.
- Polish strings avoid the 2-4 plural trap, sub-dose quantities render
as below one dose instead of approximately zero, and the table's name
sort collates by the active locale instead of hardcoded German.
The medications page gains a compact table beside the card view — one
row per medication with the same served-due status pill, next-intake
line, cadence-scaled adherence bars, dose-derived stock and the take
and skip actions the cards use, all from the payloads the cards
already read; the list response aggregates stock in the same round
trip. Columns sort tri-state with nulls last, the default order is
yours to arrange — the same drag dialog the dashboard and insights
use, applied to both views — and the chosen view and order persist
per user on their own settings column with field-scoped writes, so
toggling the view never touches the order. Inactive medications pin
to the end, the first column stays put under horizontal scroll, and
the loading skeleton follows the view you left.
Stock only decremented on the web card's take button — the status
toggle, the phone sync, the Telegram buttons and the token API all
recorded the dose and left the inventory untouched, which is why a
fresh pack kept reading full. Consumption now stamps the intake row
itself: every path that turns a row taken consumes through one module
— open container first, then earliest expiry with auto-open, spilling
into the next pack and flooring at zero — and every path that takes it
back (undo, delete, a take turned skip, a converged duplicate)
refunds exactly what the stamp recorded. Replays and pending sync
echoes can never double-charge, and the nightly convergence pass
refunds the rows it tombstones. A medication can declare units per
dose, so two 2 mg tablets book as one 4 mg dose and stock reads in
doses with the unit count alongside; containers carry a named type
including tablet packs, hold up to a thousand units for inhalers, and
the GLP-1 card reads the same stock as the inventory tab instead of a
separate ledger. The inventory endpoints join the documented API.
Hidden states of the floating coach button now leave the tab order and
accessibility tree, its focus ring carries an offset, its icon is dark
on the gradient, an unread nudge is announced once via a live region,
and it sits correctly above the fold on tablet widths and yields to
the onboarding tour. Chart error cards announce themselves, share the
exact height variables of the skeleton band, carry per-chart retry
labels, and a failed chunk renders inside the regular card shell. The
header icon cluster spaces its extended hit areas apart, the tab strip
keeps horizontal paint room for focus rings, the streak row reserves
its slot in every card state, the selection bar clears the bottom nav
on landscape phones, and the version poller keeps an in-memory reload
guard for browsers that block session storage. Mood notes render as
plain paragraphs with a toggle that appears only when text is actually
clamped, the remember control reveals on hover and hands focus to its
confirmation, and the hypnogram names its source and paints only the
lanes the night contains.
Staleness can re-enter a generated text: measurement age is hashed as a
bucketed tier, so the fingerprint stays stable day to day but flips
exactly when a reading crosses the boundary the prompt caveat cares
about. The ingest debounce becomes a one-hour minimum gap with the
hash gate carrying the budget, so a notable morning reading is
narrated the same morning instead of after the next nightly run. A
manual regenerate now refills the status cards through the same
hash-gated pipeline — changed cards regenerate, unchanged cards
refresh free, no eviction touches the hash baselines. The user's
self-context joins the comprehensive fingerprint so a newly remembered
fact reaches the briefing, the sleep writer collapse covers every
caller that reads nights, the batched compliance build fans out
bounded instead of sequentially, and a failed compliance read renders
a quiet retry state in the exact skeleton footprint instead of
swallowing the error.
Intl.DateTimeFormat instances are memoised per locale, timezone and
options signature — construction dominated the 365-day ledger build and
the hot path drops about fivefold. A batched GET /api/medications/compliance
serves every card from one request through the same per-medication cache
cells the detail page reads, replacing the per-card fan-out; the list
response stays untouched so the due line never waits on a ledger build.
The medications and compliance buckets serve stale-while-revalidate,
interactive writes evict while background bulk sync marks stale, the
per-id route defers ownership and schedule reads until a cache miss,
and the card body reserves constant slots for the status and cycle
lines so late data stops shifting the layout.
The dashboard snapshot fires the moment login succeeds instead of
waiting for the page chunk (start ~670 to ~200 ms throttled). Guided
follow-ups are personalised from the user's snapshot and an answer gets
the coach's streamed reaction before the next question. Archived
schedule eras can be corrected through a superseding revision that
keeps the original as an audit trail, era writes serialise per
medication, and duplicate dose times collapse in validation. A rolling
medication whose next dose is tomorrow can no longer read as heavily
overdue — the card status derives strictly from the served next-due —
and the adherence bars keep a fixed geometry in every state. The
server-wide assistant key gains its admin card, the one duplicated
coach preference collapses onto a single owner, twenty-seven admin and
settings card headers move onto the shared pattern with sixty
containers normalised, and the remaining hydration warnings are gone.
The client bundle carries only the active locale — resolved on the
server and handed to the provider with no hydration flash, halving the
dashboard route to under a megabyte parsed (288 KiB gzip). The auth
shell renders the app chrome immediately instead of a spinner while the
session resolves in parallel, the widgets request folds into the
snapshot it duplicated, and insight pills prefetch their sub-pages —
a first visit to a sub-page lands in about ninety milliseconds instead
of nearly a second.
A dose past its slot time but still inside its catch-up window now stays
on the medication card as overdue and takeable instead of yielding to
the next occurrence — the next-due field can therefore carry a past open
slot, flagged with nextDueOverdue. Y-axis padding no longer dips below
zero for series that cannot be negative, so a steps chart stops
suggesting minus nine hundred steps.
The Withings OAuth callback finally reports its outcome — connected or
a readable reason — instead of dropping the user on the settings page
without a word. The adherence number carries one name everywhere and an
explanation right where it is shown. Demo mode announces itself with a
banner instead of failing silently in English. Bug reports become
reachable from the mobile menu, the about section returns to the
settings navigation, charts link to the target settings they draw,
integrations and source priority point at each other, and the
medication wizard warns when reminders are switched on without a
configured channel. The longest explainer texts shrink to two
sentences.
Every detail section now sits on the card surface with the standard
padding instead of a bare border on the page background, the schedule
tab loses its duplicated summary header, and the reminder tab folds
into the schedule tab next to the times it gates (old deep links
redirect). The overview tab becomes a real landing surface — next due
dose, reminder state and supply runway with jump links — instead of a
lone compliance bar. History rows calm down: settled rows carry a
single row menu, actionable rows exactly one visible take button plus
the menu, and the quick-mark toast gains undo. The card menu drops the
advanced-settings entry and its edit action opens the editor it
promises. Schedule-tab controls share one height system, and the
injection charts load on demand so the chart library leaves the
initial bundle.
An intake edit accepted any takenAt value, so a date typo could park a
dose a month away from its slot and poison the today feed, the rollup
and the rolling next-due anchor at once. The update schema now rejects
future timestamps (with clock-skew tolerance) and values older than five
years, the route rejects edits before the medication's start date, and
the edit dialog caps the picker at now, shows the scheduled slot and
warns when the new time sits more than 48 hours from it. The dialog's
note field is gone — it was never persisted and silently discarded
input.
Replace the Verlauf tab's flat intake table with the per-slot dose-history
ledger the shared band engine mints. The tab now renders EVERY expected slot
with its status — on-time, late, skipped, missed, upcoming — even when the
dose was never taken, and tags every off-schedule intake as a standalone
ad-hoc row at its real time. Rows group by day, most-recent first.
Genommen / Übersprungen sit on the dose row and recompute instantly: the
cached ledger and the headline adherence are mutated optimistically (the
engines are pure, so the client mirrors their accounting) before the write
fires, then the dependent-key invalidation reconciles; a failed write rolls
the optimistic snapshot back. A header add dialog records a dose — including a
backdated or off-schedule one — and an off-window take surfaces the "diesem
Slot zuordnen?" nudge, pinning it onto a chosen slot via forceSlotInstant
rather than orphaning it. A compact took/skipped bar sits at the top of the
tab; the card body still never tints by status.
Adherence is derived from the same ledger the user is reading (taken over
taken + missed; skip, ad-hoc, upcoming excluded), so the history view and the
percentage cannot contradict. New status + dialog copy in all six locales.
The medications-list card kebab no longer opens a modal editor or the
stacked advanced-settings sheet. Edit navigates to the detail page's
Zeitplan tab, History to Verlauf, Advanced to Erweitert. The list-page
wizard becomes create-only; editing an existing medication goes through
the detail hero's "Vollständig bearbeiten".
Dissolve the advanced-settings sheet entirely: its five stacked groups
(Lifecycle / External API / Data / Danger zone) now live as inset-grouped
sections in the Erweitert tab. Drop the orphaned modal component, the
standalone SettingsSection wrapper, and the lazy intake-count query the
sheet's danger zone needed (the tab fetches it on demand). Remove the two
tests that only exercised the retired surfaces.
The v1.15.9 take-window highlight washed the whole medication card green
(ring + tinted background) when a dose was in its on-time window. A card
turning green reads as an imperative "take it now" and drowns the rest of
the page once several doses fall due. Drop the surface tint entirely so the
card stays a constant neutral surface; the take-now / overdue / very-overdue
status remains, expressed only through the discreet top status line / pill
(text + glyph), never as a background or border colour. Tests assert the
neutral surface across every dose status plus the surviving status pill.
Remove the per-row dose count (the '· 12 / 12' caption) from the
compliance bars — the percentage, bar and window-days label remain. The
auto-miss engine now makes the two windows' percentages genuinely
diverge, so the counts no longer earn the row noise; the engine still
carries the counts on the wire for other consumers.
Remove the injection-site display from the overview card, including the
last-injection-site shown on the last-intake line. Site tracking and
logging are unchanged elsewhere (the post-dose picker, the detail
history); only the card surface drops it.
Two identical compliance percentages with no context read as a stuck
display, even when the math is correct (a rolling weekly med on cadence is
~100% on every window by construction; a daily med with full adherence
rounds to ~100% on both 7d and 30d). Render the taken-of-expected count
after each rate (e.g. 100% with 4 / 4 and 100% with 52 / 52) so the two rows
stay distinguishable and trustworthy. Falls back to the bare expected count
when only the denominator is on the wire, and to nothing when neither count
is available.
Thread a per-window taken count onto ComplianceDisplay (short.taken /
long.taken) alongside the existing expectedShort / expectedLong, wire both
through to the shared bars component, and regen the OpenAPI schema.
Add unit tests asserting the windows do not structurally collapse to one
value: a daily med with misses outside the 7-day window scores 7d above 30d,
and a rolling weekly med with a partial-adherence history yields a short rate
above the long rate. Add card-part tests for the count / fallback / no-count
render branches.
The GLP-1 card overrode the shared next/last slot with appointment-phrased
labels (Next / Last, "Nächster Termin" / "Letzter Termin") while the generic
card used Next intake / Last intake. Drop the override so both cards name the
concept identically; the GLP-1 relative-day value phrasing (in N days / today)
is unchanged. Remove the now-dead label override props from the shared slot
and the orphaned glp1NextLabel / glp1LastLabel strings from all locales.
The open-cycle status line repeated the upcoming-dose timing already
shown in the card's next-intake slot; the calm on-track "next dose in N
days" phrasing is now suppressed, leaving only the actionable due /
overdue tiers.
Remove the "recommended next site" nudge from the GLP-1 card — it is
noise on the at-a-glance surface. Injection-site tracking stays: the last
site still reads on the last-injection line and the post-dose picker
still captures the next site.
The open-cycle "next dose in N days" tests anchored their fixtures in the
runtime calendar while the component buckets days in Europe/Berlin, so a CI
runner in UTC near midnight read "now" as the next Berlin day and drifted every
count by one. Pin the clock to a fixed Berlin-noon instant and build the
fixtures as exact 24 h offsets so the whole-day delta is deterministic in any
timezone. Verified under TZ=UTC.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Medication cycle status: re-hydrate `nextDueAt` (it arrives as an ISO string
over the wire) before the Berlin day-bucketing, so "next dose in N days" no
longer drifts by a day near midnight or off Berlin. Covered by a new
string-input test.
- Wellness rings: drive the conic sheen off a fixed white so it always lightens
(a near-black light-mode foreground under overlay was darkening the tile into
a grey band); give the ring a transform transition so it scales smoothly with
the tile lift instead of snapping; cap the green-band glow at a few breaths
instead of an infinite drop-shadow repaint; deepen the light-mode stress and
recovery end-stops for non-text contrast; track the sm/md column count to the
active-score count so two or four scores never leave an empty cell.
- Mood factor crosstab: bound the snapshot's cross-metric read to the 365-day
window and exclude soft-deleted rows, so the factor deltas the model cites
match the visible board over the same canonical row set (no recency-biased
truncation, no soft-delete skew); thread the user's source priority so the
per-day canonical pick agrees with the screen; render the delta neutrally
rather than green/red, since a higher vital is good for steps but bad for
resting heart rate and the board is observational.
- Log-intake: surface an error when a pinned slot can't resolve to an instant
instead of silently logging the dose through the unscheduled path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The top medications-page Add button now opens a choice menu with two
paths: log an intake against an existing medication — including a
backdated one — or create a new medication (the existing wizard,
unchanged).
The log-intake dialog lists the active medications, optionally pins a
schedule slot, and picks a date and time that defaults to now but accepts
any past instant. It submits to the existing per-medication intake route:
takenAt carries the (possibly backdated) instant, and a chosen slot
supplies scheduledFor so the write routes through the server's canonical
slot upsert — the same snap path a live Taken tap uses — keeping the
one-row-per-dose-slot invariant intact. No new endpoint.
Submit orchestration lives in a pure, dependency-injected runLogIntake
helper alongside the shared take/skip logic, invalidating the medication
dependent query-key bundle so the dashboard and compliance views refresh.
New strings land in all six locales.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the per-med `complianceDisplay.currentCycle` descriptor into a calm,
rate-decoupled status line on both the generic and GLP-1 medication cards.
A sparse weekly or rolling med (a GLP-1 injection) that is simply between
doses has no closed cycle in flight, so the compliance percentage alone reads
as a scary 0% that says nothing about whether the user is on schedule. The new
`MedicationCycleStatus` card-part reports the open cycle instead: "next dose in
N days" / "due today" / "overdue", or a neutral "no closed dose cycles yet"
when there is not enough history to score. It complements the percentage rows;
it does not replace them.
Driven by `currentCycle.state` (on_track / due / missed / none). Colour follows
the existing success / warning / destructive ramp and pairs each tier with a
Lucide glyph so colour-blind users can disambiguate (WCAG 1.4.1). The relative
day phrasing buckets on the user's calendar day, matching the next/last slot.
Strings land in all six locale bundles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The next-intake and last-intake lines on the medication cards ran the
label and the time together on one run of text, reading as crammed. Lay
each line out as a two-column row — bold label pinned left, value flush
right — so the two rows read as distinct, aligned columns. The label
truncates first on a narrow viewport; the time never truncates.
The GLP-1 card routes through the same shared slot and now overrides only
the left-column label with its appointment-phrased wording, so the value
column carries the time and the optional rotation nudge. Its value strings
drop the now-redundant leading label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The medication cards show a single actionable dose — the open/overdue
window, or the server's next-due slot. The take / skip button POSTed
`{ skipped }` only, so the route defaulted `scheduledFor` to `now()` and
snapped to whichever slot was nearest. For a twice-daily 07:00 / 19:00
medication a morning tap (before the 13:00 midpoint) recorded the 07:00
dose, and a non-deliberate morning tap mis-recorded the morning dose.
Thread the displayed dose's canonical slot instant onto the POST so the
server resolves the exact dose the user is looking at, independent of the
wall-clock. The card derives the instant from its current-window status
(the window's start time today) or the next-due instant; a PRN dose with
no slot omits `scheduledFor` and keeps the legacy now-snap path. The route
already accepts `scheduledFor` and snaps it through the canonical slot
resolver, so no server change is needed — the cards now always send it.
Add a unit suite for the slot resolver and the POST body, and an
integration test proving a morning `scheduledFor` records the 07:00 slot
and an evening one records 19:00, driven by the supplied instant rather
than the clock, with marking one dose never marking the other.
(cherry picked from commit 438832cd965574da1b5dabf6567f7bffa26e36f3)
Lift the take / skip intake flow — the optimistic spinner, the
failure toast, the soft-delete Undo, and the dependent-key
invalidation — into a shared `useMedicationIntake` hook consumed by
both the generic medication card and the GLP-1 card. The GLP-1 card
previously inlined its own copy that never gained the failure toast
or the Undo affordance, so a failed POST was swallowed silently and a
misclicked dose had no quick reversal. Both cards now behave
identically by construction; each keeps only its post-success
injection-site prompt.
Converge the medication status-colour surface onto the semantic
feedback vocabulary. The status pill reads as a success → warning →
destructive urgency ramp (dropping the lone `text-dracula-yellow`
middle tier), the streak flame moves off the raw `text-dracula-orange`
palette token onto `text-warning`, the detail status dot swaps its raw
`hsl(var(--…))` form for `bg-success` / `bg-warning`, and the import
result line drops `text-dracula-green` for `text-success`. Each
semantic token aliases the same colour the raw form resolved to, so
the hue is unchanged in dark mode and lands AA-safe on the light card.
Route the compliance percentages through the locale number formatter
with rounding so a non-integer rate never leaks raw, and give the
medications Add button the same `min-h-11 sm:min-h-9` tap-target floor
the dashboard Add button carries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit a4415d706154b063a8783390e35eded5dbc57910)
The injection-site capture dialog dismissed optimistically the instant
Confirm fired — before the PATCH that records the site resolved, with no
pending state. A failed request left the dose looking site-tagged when it
was not, and reused the unrelated "no sites available" string for the
error. The dialog now holds a submitting state: Confirm and Skip disable,
a spinner shows, and a backdrop tap or Esc cannot dismiss while the
request is in flight. The card handlers keep the dialog mounted until the
PATCH succeeds and surface a dedicated save-failed toast on failure.
Two assertions depended on the live wall-clock: the card suppresses the
upcoming-injection line while now sits inside a schedule window, so a CI
run during the 08:00-20:00 window rendered the pill instead of the
Next/Recommended-next line. Pin to 07:00 Berlin on a non-scheduled day.
Extend the symmetry suite to assert both cards render the byte-identical
next/last slot wrapper (same reserved min-height, spacing, muted colour,
next-then-last order, no em-dash placeholder, no brighter foreground
token) and a constant-height bottom-pinned body across oral / injection
/ GLP-1 / PRN / one-shot, including the compliance-skeleton path. Update
the dose-accent assertions onto text-dose-accent and the GLP-1 rotation
test onto the folded-into-line nudge. Clock stays pinned.
Replace the stick-figure outline with a traced front-of-body silhouette,
still an inline SVG so it inherits the theme tokens (currentColor + a
soft currentColor wash) and tints correctly across light/dark and the
zinc/Dracula palettes with no raster asset.
Recalibrate SITE_COORDS onto the new 100x200 viewBox (aspect ≈0.5) using
the iOS client's hands-on fractional coordinates, so both apps land their
dots on the same anatomy. The coordinates double as the Euclidean basis
for the rotation recommender, so picker and recommendation stay in sync.
Dot states: selected (filled), recommended-next (dashed primary ring),
and a new last-used solid ring; hit targets are r=14 on the viewBox
(≈45 CSS px at the rendered scale), clearing the 44px floor. Props,
callbacks, the screen-left=="links" convention and the v1.8.5 site→enum
bridge are unchanged — graphic and coordinates only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 46125c3bbec5573910f41a319ae717d37f39c228)
Add two nullable, additive columns to `Medication` (`atc_code`,
`rxnorm_code`) so a user or clinician can assert a drug-classification
code that the health-record FHIR export emits on the medication concept.
Codes are taken verbatim, never machine-guessed: a wrong code is a
clinical-safety problem, an absent one is merely a (conformant)
text-only concept.
- Migration 0103 — additive, nullable, no backfill. Pre-existing rows
read NULL, which the exporter already treats as "no code".
- Validate on write — ATC `^[A-Z]\d{2}[A-Z]{2}\d{2}$`, RxCUI `^\d+$`;
422 on a malformed value. Wired field-by-field through the create +
update routes (no mass assignment); `null` clears a code on update.
- Surface both codes in the medication advanced-settings sheet's Data
group with a self-saving input, and echo them on the GET/POST/PUT
response shape. OpenAPI regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2e0051d42826fa3f043c32e78a34dab7c8050165)
The advanced-settings sheet crammed the external ingest endpoint into the
Data group next to CSV import/export — two unrelated audiences sharing one
frame. Pull `ApiTokensRow` into a dedicated "Externe API" group so the
developer-facing surface reads as a distinct, lowest-frequency block. New
group order: Reminders -> Lifecycle -> Data -> Externe API -> Danger.
The new group reuses the existing `SettingsGroup` bordered-card chrome, so
it stays visually consistent with its siblings. Adds the `externalApi`
group label across all six locales and updates the composition test to pin
the five-block order plus the API row's new home.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit f9f3a1277b5aad1710815511ddf7f6b210a652b2)
Render the estimated drug-level-in-blood curve for any recognised GLP-1
medication without requiring the Research Mode opt-in. The chart still
self-gates on a recognised brand and on having intake data, and the
pharmacokinetic-estimate disclaimer is now always attached so the curve
reads as a modelled value, not a measured blood concentration.
Drop the now-dead Research-Mode gated-placeholder branch and its copy.
The researchModeEnabled flag is unchanged; it still gates the advanced
settings surface and its acknowledgment dialog.