toZonedDate rendered an instant as an en-US string in the target zone and
re-parsed it as host-local. Two defects rode on that.
When the fabricated wall clock landed in the HOST zone's spring-forward gap
the engine silently advanced it an hour, so a dose inside its window could
be classified late and a just-taken dose could fail to suppress the take-now
pill — for users whose device zone differs from their profile zone.
The medication card and table then passed that already-shifted value into
dateWithWeekdaySmart, which applies the profile zone itself, so the next-
intake day label converted twice and could name the wrong calendar day.
Invisible whenever browser zone and profile zone agree.
Migrate every consumer to wallClockInTz, which returns the fields directly.
The status math only ever reads year/month/day/hour/minute/weekday, so it
now runs on WallClockParts throughout and reduceCurrentWindowStatus takes
the real instant instead of a pre-shifted Date — caller and helper can no
longer disagree about which zone the comparison runs in. Day labels use a
calendar-day distance (localDayIndex) rather than an hour count, so a 23 h
or 25 h day cannot round tomorrow into today, and the formatter receives
the raw instant.
Fixtures name the zone they mean instead of building host-local Dates.
The target-summary and coach-read strip tiles sit directly under a
metric sub-page's h1, alongside the stat strip that already renders its
title as an h2. Render their titles as real h2 headings too via the
existing titleAs prop, so heading navigation lands on each section. A
test pins that the dashboard exposes its page-level h1 through the
shared page header (no duplicate sr-only heading needed).
Health-chart and mood-chart now pass accessibilityLayer explicitly on
their ComposedChart root — Recharts 3 already defaults it on, but
pinning it here documents the choice and survives a future default
flip on the two busiest chart surfaces in the app.
TileHeader's titleAs="h2" was previously read as a single-owner
exception; the docstring undersold it — sibling h2 tiles in one grid
nest correctly under the page h1 same as a lone owning block. Wires it
through the dashboard's recent-workouts tile and the insights vitals
grid (both the loaded SparklineDeltaTile card and the two provisional/
building states), so a rotor user gets one heading per tile instead of
landing on the page's single h1 and nothing else.
The coach composer's focus ring rode ring-primary/15 — likely under
the 3:1 non-text-contrast floor next to its /50 border. Raises it to
/50, matching the family of "wrapper focus-within ring, outline-none
inner control" the standard already uses elsewhere at full strength.
Logging a dose via Medications → Add → "Log intake" (incl. backdated)
or the dashboard's medication-intake quick-add toasted success with no
Undo, unlike the card's own take/skip path — a dose logged against the
wrong medication (easy on the quick-add, which auto-picks) was only
correctable via detail page → history → find row → delete. Both paths
now capture the created event id and attach the same Undo action.
Today hero's top-signal delta used opacity-on-muted text, landing below
AA on the app's most prominent card; five mood charts and the sleep-
stage IN_BED fill painted raw --dracula-*/hardcoded-hex hues with no
(or an unused) light-theme counterpart. Route all of them through
existing semantic/chart tokens instead (destructive/warning/success,
chart-1..5, and a newly minted --chart-inbed with its own Alucard
override) so every hue is AA-tuned per theme.
Also collapses three duplicated primitives the audit flagged: the
click-Popover VitalsInfoTip and hover-Tooltip InfoHint merge into one
touch-first ui/info-popover; MedicationViewToggle and ModuleViewToggle
were verbatim copy-paste and now wrap one shared ui/view-toggle; the
triplicated sport-icon map (already drifted — mixedCardio rendered a
different glyph in the list vs the detail/dashboard views) moves to
lib/workouts/sport-icons.ts with HeartPulse as the settled icon.
Adds a no-restricted-imports lint rule banning direct recharts imports
outside the chart-runtime barrel, catching the bundle-duplication
mistake at local lint instead of PR-CI.
Intraday pulse rendered sparse spot readings as a smooth interpolated
curve across hours-wide gaps, implying continuous monitoring the data
never had. The chart now grids one point per bucket, breaks the area
over any missing bucket (connectNulls={false}), and discloses coverage
("based on N readings across M hours") whenever that produces a visible
break.
Four surfaces also collapsed a failed fetch into the honest empty/no-
data state instead of surfacing the error: the Today hero, the
medication efficacy tab (previously stuck on the loading skeleton
forever on error), the intraday pulse chart, and the recent-workouts
dashboard tile. All four now branch on isError and render
QueryErrorCard with a retry action; the tile's "loading…" text line is
replaced with row-shaped skeletons.
Reuses the TrendCard strip-tile pattern (icon, neutral direction
sentiment, stale-days caption) for a new waterIntake widget, but
sources it from NutrientIntakeDay instead of introducing a parallel
MeasurementType. The dashboard snapshot builder reads the last 30
day-keys for nutrient="water" (summed across sources), folds them
into a synthetic NUTRIENT_WATER summary key alongside the real
MeasurementType summaries, and the existing module-gating machinery
(SUMMARY_TYPE_MODULE, WIDGET_MODULE_BY_ID) strips it when the opt-in
nutrients module is off — no new gating code path, just two new map
entries.
The tile links out to /insights/nutrients instead of a measurement
detail page. Widget catalogue grows 37 -> 38 ids.
The digest-driven Today hero is the single promoted hero at the top of
the dashboard, so the old opt-in hero band is redundant — a user who had
opted in and has Insights on saw two heroes. Remove the legacy hero and
its heroVisible layout flag.
- Drop the DashboardHero / DashboardHeroSkeleton mount from the
dashboard page; the header now renders the greeting unconditionally
(its showGreeting gate is gone with the hero that owned it).
- Retire the heroVisible field from the dashboard layout (type,
defaults, resolver, serializer) and the widgets PUT schema. The
resolver is a whitelist constructor, so a stored heroVisible is
dropped on read and never re-persisted — no stored layout breaks.
- Delete the now-orphaned hero components (band, skeleton, ring
carousel, briefing spotlight) and their tests; drop the retired
dashboard.hero copy across all locales.
- Remove the hero-visibility toggle from Settings → Dashboard. The
score-ring picker no longer hangs off that flag and stays available,
since the rings it configures still ride the server-computed
dashboard snapshot.
- Repoint the phone-width density guard at the Today hero and mock the
daily digest so it renders.
- Repoint the preserve-when-absent doc analogy off the removed field.
Several readings were synced and charted but had no place on the dashboard
strip: heart-rate variability, blood oxygen, breathing rate, wrist temperature,
and — for body-composition scale users — muscle mass, body water, and bone mass.
Each now has a tile.
The tiles are pure client additions: the dashboard snapshot already ships a
summary for every measurement type, so no backend, query, or migration changed.
Each tile is data-availability gated (renders only when the account has that
reading) and is toggleable and reorderable under Settings like the rest.
hrv and respiratoryRate graduate from iOS-only to web-writable widget ids, and
muscleMass is a new writable id (catalogue 36 to 37, within the 40-id PUT cap);
wristTemperature moves from pin-only to a web-rendered tile. All additive — the
widget enum only widens, which the iOS contract treats as non-breaking.
Lab-report OCR now rasterizes a PDF to page images for any non-Anthropic
vision provider, exactly as the document vault already does — Anthropic keeps
its native PDF block, every other vision provider (codex/OAuth, OpenAI, local)
reads the rendered pages. The capability probe reports `pdfSupported` whenever
the picked provider reads PDFs natively or the server-side rasterizer is
available, so the "scan a PDF" affordance shows for a codex provider too.
Drop the dedicated document-scan (Lab-OCR) provider: labs OCR always uses the
main configured provider chain. Removes the settings card, the `aiOcr*`
resolution branch, the GET/PATCH handler fields, the OpenAPI schema fields, the
i18n keys across all locales, and the five backing `User` columns (migration
0234). The on-device tesseract fallback is untouched.
Dashboard hero: stack the overdue-dose action directly beneath the verdict
text (tight gap, left-aligned) on every viewport instead of pushing it to the
far container edge on desktop.
The overdue-dose CTA sat in a flex-wrap row, so a full verdict sentence
pushed the take/log button onto its own line — wasted vertical space.
Adopt the PageHeader inline-action pattern: items-start justify-between,
the sentence gets min-w-0 flex-1 and wraps internally, the button gets
shrink-0 and stays beside the text. Tighten the greeting to verdict gap
from space-y-3 to space-y-2.
Theme provider now initializes to the SSR-stable default and reconciles
the stored preference in a mount effect instead of reading localStorage
in the useState initializer — closes the same React #418 hydration seam
the sidebar already fixed (the inline script still handles the pre-paint
class, so no FOUC). Add a process-wide Intl formatter cache and route the
per-row workout/medication list helpers through it so a formatter is
constructed once per (locale, options) rather than once per row per
render.
- Dashboard metric tiles link to their /insights/<slug> detail page
(weight, blood pressure, pulse, glucose, mood, sleep, steps, VO2 max);
tiles with no dedicated sub-page stay un-linked rather than misroute.
- Daily-briefing key findings now route hrv, resting HR, active energy,
flights, distance, body temperature and recovery to their sub-pages —
the METRIC_HREF map had gone stale as those routes shipped.
- RecentWorkoutsTile adopts the canonical TileHeader (foreground icon +
title) instead of a muted hand-rolled row.
- Hero-strip header glyph moves off text-primary to the foreground tier.
Add resolveHeroRingOrder unit coverage (default health-score-first,
reconcile against selected set, drop/append, dedupe) plus resolver/serializer
round-trip; hero SSR tests for the default lead + a persisted custom order +
dropped-when-unrendered; Settings picker tests for the pinned health-score
anchor row, its locked-on switch, drag/arrow reorder, and a persisted
non-leading order. Extend the score-rings picker description across six
locales.
Add a persisted hero ring ORDER over the health-score anchor ring plus the
selected score rings. Defaults to the health-score ring on the leading edge;
the user can reorder the full set (health score included) from Settings →
Darstellung with the existing drag-handle + arrow-button pair.
The order rides the dashboard-layout blob (dashboardWidgetsJson) — no
migration. resolveHeroRingOrder reconciles a stored order against the current
ring set on every read/serialize, so a deselected ring drops and a
newly-selected ring appends; the hero orders only the rings it actually
renders.
- Dashboard hero: pull the briefing closer to the ring row (md:gap-3 -> md:gap-2); mobile spacing unchanged.
- Each hero ring is now a focusable link to its natural detail: readiness/recovery/sleep -> their score-anatomy page, doses -> the medications surface, health score -> the Insights overview. Real anchors keep tap-vs-drag native on the mobile carousel.
- Mental-wellbeing: shorten the page title to the short noun form (matches the nav/module label) across all six locales.
- Instrument cards: move the required attribution/licence line off the space under the Start button to a discreet info control at the top-right of the header; a popover carries the verbatim citation. Start now stands alone.
Below md the hero wellness rings no longer wrap into a multi-row grid;
a dependency-free CSS scroll-snap track shows one ring centred at a time
with dot indicators, native touch/keyboard swipe, and reduced-motion
respected. At md and up the same nodes revert to the inline
right-aligned row unchanged. Desktop tightens the greeting/ring-row drop
to the briefing one step (md:gap-3).
The greeting drifted to the vertical centre of the taller ring column
and the briefing sat a full step below it — the top row now aligns to
its start and the section gap tightens, so the greeting anchors to the
top edge and the briefing follows closely.
The band-fallback ring green landed on its own hue (#3fb868) while every
positive signal and trend delta beside it used --success (#50fa7b); a
green ring now lands on --success too, so 'green' reads as one green
across rings, signals, and deltas. Computed contrast 11.9:1 dark /
6.1:1 light — well past the graphics floor.
On phones the four score rings wrapped into a 2-up grid clustered in
the centre with dead margin on either side. They now spread evenly
across the full width — equal space before, between, and after — so the
block reads symmetric on all four sides instead of centred; desktop's
right-aligned row is unchanged.
On desktop the four rings sat 12px apart and the briefing section
followed the greeting row at the intra-block distance — the band read
top-heavy against its own symmetric padding. Rings now space at 24px
on md+ and the section gap steps up to match the card inset, so the
head zone and the briefing zone separate at the same rhythm the
padding sets.
A long signal headline beside a wide delta string squeezed the headline
into a one-word-per-line column on the hero spotlight rows; at 360 px
the delta pushed past the tile edge and got cut off by the band's
overflow clip. Below sm the headline/delta pair now stacks — headline
wraps at full row width, the delta sits beneath as a left-aligned
tone-coloured line — and the side-by-side row returns at sm+. Same
treatment on the /insights briefing card's shared finding/signal rows,
which speak the identical row language.
The 'Doses today' ring pinned the ring system's green arc — a colour
that vocabulary reserves for band semantics. Every medication surface
in Insights paints --primary (the compliance progress bars, the
compliance trend line, the drug-level and dose-strength curves), so the
ring now rides a new constant 'meds' hue on that token: flat by design
(both gradient stops on --primary), never a band gradient over pending
morning doses. Contrast, computed: dark #bd93f9 — 6.78:1 card / 5.90:1
bg; light #644ac9 — 6.00:1 card / 5.60:1 bg; both clear the 3:1
graphics floor with room. The guard now windows the assertion to the
dose ring's own SVG and pins primary present, ring-green absent.
A measured pass over every insights sub-page, the dashboard, and the
settings sections (computed text colour, left text edge relative to the
card edge, header→body distance, per card) surfaced the drift the eye
kept catching:
- The blood-pressure pulse-pressure/MAP explainer was a bare muted
paragraph at the container edge, 16–24 px left of every neighbouring
card's text. It now rides a compact Card in the regular foreground.
- The 'Usual range' read strip was a hand-rolled bg-card/60 shell with
its own padding; rebuilt on Card + TileHeader + CardContent at the
compact density so background, radius, icon spacing and text edge
match the neighbouring tiles.
- The spine tiles carried three different header→body distances (6, 12
and 24 px) via off-scale overrides (gap-1.5, md:gap-3, md:py-5,
gap-3). Assessment, target summary, medication tiles, the mood
stand-out card and the sleep hypnogram now share the sanctioned
compact tier (gap-2 py-3 md:py-4).
- The hero score card and its skeleton kept their translucent identity
but move from a static px-4 py-4 to the p-4 md:p-6 card norm, putting
their text on the same edge as every other card.
- Two dashboard shells (recent workouts, recent unlocks) froze at p-4
on every breakpoint; now p-4 md:p-6.
- Settings: the modules hint moves into the header's description slot
and the injection-site grid joins the section-wide mt-4 rhythm — the
two gaps that deviated from the settings 16 px header→content norm.
The briefing spotlight carried a separate underlined 'open Insights'
text link that thickened the card; every signal row below already
deep-links. Drop it and make the Tagesbriefing heading itself the link
— quiet, no underline, hover lift only — targeting the briefing card
directly via a new #daily-briefing anchor on the insights section
(scroll-mt-28, the app-wide offset token).
After the broad Insights button left the hero, the scoreDrop and
briefing verdict sentences were dead ends. Link-CTA verdicts without a
button label now make the sentence itself clickable (same quiet
treatment): scoreDrop routes to /insights, the briefing sentence
deep-links to the picked finding's metric sub-page via the METRIC_HREF
map (fallback /insights). Verdicts with their own action keep their
button.
The MED_COMPLIANCE hero ring carried the pooled 7-day adherence
percentage — a mystery number next to today-focused scores. It now
answers the question the hero poses: how many of today's doses are
done. The resolver reads takenToday/scheduledToday off the snapshot's
own medsToday block (one shared builder call, no second tally); score
stays the rounded 0..100 progress for wire compatibility and a new
additive doses field carries the taken/scheduled pair. No dose
scheduled today means no ring, and the band is progress semantics —
green once everything is taken, yellow while doses remain, never red
(a pending morning dose is not an alert state).
The web ring renders the tally (1/3) with a doses label over the ring
system's constant green arc — the same green family the compliance
surfaces in Insights paint — instead of a band gradient; the derived
rings keep their exact insights hues. ScoreRing gains documented
valueText/ariaLabel overrides for tally-shaped centres. OpenAPI
regenerated; labels land in all six locales.
The scoreDrop/briefing specs asserted the removed overview-only button;
they now pin its absence, and the orphaned action key leaves all six
locale bundles (the reverse-coverage guard flagged it).
The scoreDrop/briefing verdict button only opened the Insights
overview, which the health-score card already links — gone. The
briefing-spotlight rows now deep-link to their metric's sub-page via
the shared METRIC_HREF mapping; metrics without a routed page fall
back to the overview.
The hero's dose text row gives way to a ring row: the selected score
rings render at the health-score ring's sm flat geometry, readiness /
recovery / sleep in their wellness-strip hues and the 7-day adherence
ring in band semantics — the ring that carries the dose row's
information role. Rings self-gate on data, the verdict line above is
untouched, and the skeleton mirrors the new geometry (plain card shell,
no dose pill, the always-present health circle reserved). Settings ->
Dashboard gains a picker under the overview toggle: up to three rings,
module-gated rows, switches disable at the cap, persisted through the
existing widgets PUT. Locale bundles drop the retired dose-row strings
and pick up the picker copy in all six languages.
Add a PageHeader primitive (one H1 vocabulary, optional description,
back-link, and action slots) and route the hand-rolled headers through
it, so every module reads with the same title/subtitle rhythm and no
icon beside the page title. Mental Wellbeing gains a visible header.
Add a QueryErrorCard and wire the list surfaces so a failed read shows
a retry card instead of falling through to the empty state, and swap
their full-page spinners for skeletons that hold the layout.
Appearance ("Darstellung") stacked every module's view and ordering
settings on one long page. Split it into a hub that lists the modules as
navigable rows, each opening its own /settings/layout/<module> subpage
with a back link — the per-module settings move verbatim, no restyling.
The module routes now redirect to those subpages instead of an in-page
anchor, and the in-app cogs link straight to them.
Add a dependency-free DateTimeField that paints the date part in the
user's date-order preference and the time part in the hour-cycle
preference over a hidden native datetime-local input, preserving the
local yyyy-MM-ddTHH:mm value contract. Migrate the seven DateTimeInput
callsites (measurement add/edit, mood add/edit, lab add/edit,
medication intake quick-add) so the chosen format is honoured there too,
and cover the field's value contract and per-preference rendering with a
component test.
The briefing only reached the dashboard as the hero's lowest-priority
verdict, which the severity ladder almost always pre-empted, so a fresh
briefing was invisible above the fold. Lift its own signals (falling back
to key findings) onto the hero as a self-gating strip beneath the verdict
row, reusing the insights briefing's tone-bar row language. Placement
only: the verdict ladder and the briefing content are unchanged.
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.
Sweep the primary touch controls that sat below the 44px tap-target
floor on phones onto the canonical `min-h-11 sm:min-h-9` (text) /
`size-11 sm:size-9` (icon) pattern, condensing back at `sm:` upward:
- Coach composer mic / stop / send icon buttons (were `size-9`).
- Dashboard hero CTA, link and action variants (were `size="sm"`).
- Vorsorge list-view "mark done" button (was `min-h-9`) and its kebab,
which now uses `size="icon-lg"` so the rendered control is 44px
rather than the icon variant's 40px.
- Illness list-view "log day" button (was `min-h-9`).
- Medication wizard Step 3 injection-sites disclosure (was `h-9`).
The Vorsorge dashboard-card capture action was raised alongside the
ListRow migration.
The target editor invalidated a bare ["insights"] array on save and reset.
That literal sat outside the centralised factory, so the broad insights
fan-out it intended never matched the reads, which key through
insightsTargets/insightsRoot. Route the editor's reads and invalidations
through userThresholds(), insightsRoot(), insightsTargets() and analytics()
so a saved target actually refreshes every insights surface.
Migrate the other call sites the lint rule does not reach onto the factory:
the Coach conversation rail + thread reads, the per-metric personal-records
badge, the per-day measurement drill-down, the dashboard intake
compliance-chart fan-out, the per-medication side-effects list, the
onboarding auth invalidations, the notification-preferences read, and the
PWA persister's auth-me lookup.
Add the matching factory keys: coachConversations, coachConversation,
personalRecordsByMetric, measurementDrilldown, complianceChartInline and
medicationSideEffects. Each new key reuses the prefix its reads already use
so no invalidation shape drifts.
The guided tour now exists only as the first-time auto-start after onboarding.
Remove every in-app trigger that re-opened it: the per-module "show this tour"
button on the dashboard, measurements, mood, medications, labs, illness, cycle,
achievements, insights, and coach surfaces; and the Settings replay card.
Delete the now-orphaned ModuleTourTrigger component, the tour-restart worker,
the healthlog:module-tour and healthlog:tour-restart event listeners, the
single-stop overlay path, and the force-launch session marker. The first-time
auto-launch gate and the tour overlay itself are unchanged. Prune the orphaned
i18n keys across all locales and update the affected tests.
Add a 'Diese Tour zeigen' affordance to every module header that opens the
tour narrowed to that module's card in place, plus additive data-tour
anchors (measurements/medications/labs/illness/vorsorge/cycle/mood/
insights/coach/integrations/export/achievements) so the cross-page walk
has a real target on each page. Consolidate the full-tour replay onto a
single Settings -> Advanced card, relabel it, and drop the duplicate from
About. The replay reset clears the stored progress so it restarts cleanly.
Extract the pure first-paint / skeleton-reservation gates
(resolveDashboardFirstPaintGate, resolveConfiguredTileCount,
resolveChartRowPlaceholderCount + their widget-id sets) into
components/dashboard/dashboard-gates; page.tsx re-exports them so the
existing unit-test imports stay valid. Wire the page to read the
server-computed targetBands when snapshot mode is on (legacy client
compute kept for the snapshot-off fallback) and to issue one
series-batch fetch for the visible non-sleep charts, threading each
chart its slice via preloadedSeries.
Move the BP/pulse/body-fat/weight band + target derivation out of the
dashboard client component and into the snapshot DTO. The page recomputed
every band from the user profile on each render via the analytics band
helpers; the snapshot builder now resolves the same numeric structures
once server-side under a new targetBands block, calling the identical
helpers. Emit only the i18n-free numbers — the label-bearing BP target
zones stay client-assembled from the server numbers.
Add a parity test asserting buildTargetBands matches the prior client
derivation for a male, a female, and a no-profile user.
Add a calm, value-free Rest Mode indicator to the dashboard score card and
the recovery insight so the web matches what iOS already shows: when an
illness/condition episode is active the score and recovery are framed, never
penalised, and the surface now says so. The new banner is neutral and
untinted, self-gates on an active episode, and reveals only the onset date —
never a decrypted note or episode label. The dashboard reads the annotation
from the snapshot (resolved server-side alongside the score); the recovery
section reads it from the analytics payload.
Give the sleep page one assessment per distinct chart group: the
duration/architecture assessment now sits with its charts, and the
sleep-quality block carries its own SLEEP_SCORE assessment, matching the
"multiple charts, multiple texts" structure the recovery page meets.
Add the steps explainer (insights.subPage.explainer.steps*) and pass
explainerMetric="steps", and demote the recovery per-block title to an h3 so
the heading outline nests cleanly under the page heading and group heading.
The daily-verdict band wore a gradient surface and a health-score ring
with a drop-shadow bloom, a green-band breathing pulse, a conic sheen,
and a sweep entrance. It read louder than the chart cards and tiles it
sits among.
Drop the gradient: the band now wears the same bg-card + border + radius
+ padding as <TrendCard>, and the dose chip sits on a neutral muted
surface (no card tint, blur, or shadow). Add a flat treatment to
<ScoreRing> that suppresses the bloom, pulse, sheen, and entrance sweep,
and pass it on the hero ring only. The wellness strip and anatomy view
keep the full reveal. Reduced-motion handling is unchanged.
Information is untouched — only the visual treatment changes.
The medications header used flex-wrap, so its action group (take-all
+ customize wrench + Add) dropped to a second row below the title on
narrow viewports. Drop the wrap and let the title block shrink: title
div gets min-w-0, subtitle truncates, action group keeps shrink-0 —
the wrench and Add stay top-right inline with the heading.
The mood, measurements and dashboard headers shared the same latent
root cause (title div missing min-w-0, subtitle/greeting not
truncating, action group not shrink-0). Apply the same minimal guard
so none of them can wrap or compress the actions either.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The table wrapper clips its children now — the sticky name column's
opaque cell background squared off the left rounded corners. The
dashboard, insights and medications headers share a wrench as their
customize glyph, leaving the slider icon to the target and overlay
controls that already wear it. The header view toggle retires; the
cards-or-table choice lives in the medications settings section.
The snapshot's thick phase gated on every type the account had ever
logged having rollup coverage — one fresh reading from a new
integration nulled the health score and the blood-pressure extras
while the insights page showed a score for the same account. The gate
now checks only the three types the phase reads, per the established
per-type pattern. Underneath sat the destabiliser: the large rollup
fold deleted the window's buckets and re-inserted without a
transaction or duplicate tolerance, so a racing sync write aborted the
insert and left the delete committed — whole types lost their buckets
until the next clean fold, and accounts with data older than the
five-year fold window re-ran the full backfill on every boot because
discovery scanned unbounded history. Delete and insert now commit
atomically with duplicate tolerance, discovery shares the fold's
bound, the provisional ring says the score is being computed when the
inputs exist and reserves the no-data wording for accounts that have
none, and the greeting leads the band typographically.
A fresh crisis-level blood-pressure reading outranks an overdue dose in
the daily verdict, and once it ages past a day the dose leads again.
The greeting returns to the page header whenever the daily band is
hidden, and the settings toggle says it rides along. The nightly
convergence pass honours the early grace, so a take recorded shortly
before a window that starts at the dose time heals like the write path
credits it. The early-take context line states a fact — last dose
yesterday, last dose n days ago — instead of claiming an early intake
after a late one, the quick-add default pick runs in the profile
timezone, a dose line without a medication name reads cleanly, the
early-grace constant has one home, and the Telegram confirm writes
intake and snooze atomically again.
The external ingest endpoint was the one intake writer that never
attributed to the minted band: it stored a bare row beside the pending
slot, so an early take through the API showed as the last intake while
the slot kept demanding a dose, the ledger recorded an extra plus a
miss, and the day tally counted both. It now resolves the band and
converges onto the pending row like every other path, the Telegram
confirm and skip buttons do the same preferring the reminder's own
slot, and the nightly convergence pass heals the pairs this has
already produced — one band match, one live anchor row, both days'
rollups recomputed. The cache layer fences in-flight builders on
invalidation so a read can neither join a pre-write build nor have a
background refresh re-cache a pre-write body as fresh. Ad-hoc rows no
longer resolve a different slot through the six-hour radius, a bounded
early grace credits a take up to an hour before the window without
crossing the previous band, day-scale cadences taken early downgrade
the slot-day prompt instead of inviting a double dose, the overdue
pill ignores the projector's pending mints, created intakes carry the
same plausibility bounds as edits, slot moves keep the recorded
injection site and dose, both affected days recompute on a moved
toggle, deliberate slot-less skips converge instead of orphaning, the
coach reads adherence from the ledger, the cards and quick-add run in
the user's timezone with the served next-due as the gate, and
schedule-revision, wipe and restore writes invalidate the caches they
feed.