Commit Graph
1685 Commits
Author SHA1 Message Date
Marc-André Bombeck 90b1a4eaee fix(insights): improve narrative contrast and spacing 2026-07-29 21:28:29 +02:00
Marc-André Bombeck ab450bbfd1 fix(dashboard): keep hero headline when score lead is removed 2026-07-29 21:20:16 +02:00
Marc-André Bombeck bdca4f5724 fix(dashboard): compact score-only hero 2026-07-29 17:05:53 +02:00
Marc-André Bombeck 271f0ed28a fix(medications): make import replay results honest 2026-07-29 16:11:45 +02:00
Marc-André Bombeck b0a94b3423 fix(web): stabilize document and example downloads 2026-07-29 15:57:20 +02:00
Marc-André Bombeck 9354c649fe fix(integrations): surface Google sync progress and canonical workouts 2026-07-29 15:55:37 +02:00
Marc-André Bombeck 67eeefd584 fix(i18n): translate final security and import states 2026-07-29 15:07:35 +02:00
Marc-André Bombeck ba6d06be3b feat(01-21): expose Google sync terminal status
- return redacted per-resource outcomes from manual sync
- poll owner-scoped progress and invalidate committed workout views
2026-07-29 15:01:13 +02:00
Marc-André Bombeck f6555d471b fix(workouts): show aggregate pulse in lists 2026-07-29 14:58:25 +02:00
Marc-André Bombeck 89fefce7ac test(workouts): separate pulse aggregates from curves 2026-07-29 14:57:42 +02:00
Marc-André Bombeck 4b64556b38 feat(01-19): expose privacy-safe ECG import outcomes
- return and render bounded ECG result counters with partial-failure wording
- document archive limits, encrypted storage, and exact WHOOP constraints
2026-07-29 14:47:22 +02:00
Marc-André Bombeck 0bf3a3573d fix(01-20): render explicit assessment states
- prioritize safe fallback text over provider metadata
- keep terminal and pending card geometry stable
2026-07-29 14:40:55 +02:00
Marc-André Bombeck b8260474ed fix(01-17): remove duplicate mobile insights spacer
- suppress only the inherited mobile pre-pill top padding
- preserve safe-area clearance focus paint and 44px targets
2026-07-29 14:38:19 +02:00
Marc-André Bombeck 23c738efac fix(01-17): align and pin desktop sidebars
- place both sidebars on the heading row with one main scroll owner
- bound short-viewport sidebar content without horizontal layout drift
2026-07-29 14:36:18 +02:00
Marc-André Bombeck 7396119ec8 feat(01-17): group gated admin navigation
- cluster allowed destinations under semantic settings-style headings
- preserve fail-closed role gating and mobile target order
2026-07-29 14:29:51 +02:00
Marc-André Bombeck 431120d55e fix(01-16): unify records action grammar
- use Save for new and existing lifestyle facts
- show first-entry or populated-list add actions without duplication
2026-07-29 14:29:19 +02:00
Marc-André Bombeck c4ec2d552f fix(01-16): normalize assessment prose presentation
- use the readable foreground at confirmed medication and Mood mounts
- tighten Health Score and medication assessment spacing
2026-07-29 14:28:47 +02:00
Marc-André Bombeck d67b899138 fix(01-16): remove redundant score and summary presentation
- keep useful Today prose without repeating the ring score
- remove stored-summary regeneration and Health Score notice/method/list copy
2026-07-29 14:27:45 +02:00
Marc-André Bombeck 65ca1aae42 test(01-10): add red responsive navigation contracts
- pin grouped and heading-aligned Settings and Admin sidebars
- add mobile Insights geometry and deterministic release evidence captures
2026-07-29 14:22:54 +02:00
Marc-André Bombeck 6f84629482 test(01-13): add red Google sync terminal contracts
- cover authenticated status polling and bounded resource outcomes
- prove committed workouts invalidate server and client cache surfaces
2026-07-29 14:22:49 +02:00
Marc-André Bombeck 18d0facdfe test(01-10): add red assessment and records contracts
- pin honest assessment fallback, exhaustion, foreground, and state envelopes
- require consistent save and empty-state add action grammar
2026-07-29 14:18:35 +02:00
Marc-André Bombeck 8e944dbcd3 test(01-10): add red dashboard and score presentation contracts
- preserve useful Today narrative without duplicate score prose
- remove stored-document regeneration from the detail surface
- retain score identity while rejecting redundant method copy
2026-07-29 14:12:58 +02:00
Marc-André Bombeck 27a3e08436 fix(01-07): make private Nightscout access operator-owned
- remove the user-facing private-host bypass
- render a redacted operator-action state for denied legacy connections
2026-07-29 13:57:48 +02:00
Marc-André Bombeck 7874eea04d feat(01-08): require reauthentication before passkey enrollment
- offer password, TOTP, passkey, and security-key proof choices
- start registration only after the selected existing factor succeeds
2026-07-29 13:53:59 +02:00
Marc-André Bombeck 3f4afb26a4 test(01-03): pin passkey enrollment reauthentication UX
- require password, passkey, TOTP and WebAuthn proof choices
- order fresh-factor completion before challenge creation
- keep successful enrollment in one cookie-bound ceremony
2026-07-29 13:39:55 +02:00
Marc-André Bombeck 5569b8259b test(01-04): pin Nightscout operator-action UI
- Remove the user-facing private-host bypass contract
- Require redacted operator approval guidance for legacy failures
- Preserve usable public and approved-private connected states
2026-07-29 13:39:50 +02:00
Marc-André Bombeck 668e3ca6b6 fix(insights,settings): restore hero-strip column width, gate hero fieldset on hydration 2026-07-28 22:03:39 +02:00
Marc-André Bombeck 7b8fb98e48 feat(analytics): replace health score with reference composite 2026-07-28 21:12:05 +02:00
Marc-André Bombeck 3aff2c16db fix(insights): scope generation guard to prompt fields, clear cache on restore 2026-07-28 21:11:43 +02:00
Marc-André Bombeck c91558f18f feat(analytics): replace health score with reference composite 2026-07-28 21:10:15 +02:00
Marc-André Bombeck 666a85d311 feat(insights): persist correlation patterns 2026-07-28 14:01:11 +02:00
Marc-André Bombeck 8ece6f39cc fix(status): expose background failures 2026-07-28 14:00:44 +02:00
Marc-André Bombeck a689329e79 feat(dashboard): configure Today highlights 2026-07-28 13:22:08 +02:00
Marc-André Bombeck 51dbfefbcb fix(backup): carry the intraday day shape through export and restore
`IntradayCumulativeProfile` shipped classified as backed up and carried by
nothing. The drain folds the per-sample rows into twenty-four running totals
and deletes the originals in the same transaction, so past the grace window
the table is the only copy of a day's curve: a restore rebuilt the account
without it, reported success, and left the same-time baseline reading "still
learning" for a fortnight.

Builder and restore sit together in one file, the way the profile and cycle
pairs do, because the restore route delegates and a grep of the route alone
lies. The section spreads into the payload rather than being copied key by
key. Two curves for the same metric and day, or a curve that is not a whole
day, throw naming the day instead of writing a row every reader drops.

The pair is proven end to end rather than at each end: the round-trip suite
builds a real payload from a populated account, serialises it, parses it back
through the real parser, drives the real restore, and asserts on the rows
written. Remove the read and it goes red.
2026-07-28 00:07:10 +02:00
Marc-André Bombeck 2aeb925e28 merge: s15-stepsdisplay into release/v1.34.0 2026-07-27 23:41:34 +02:00
Marc-André Bombeck b14943ea1b merge: s8-baselines into release/v1.34.0 2026-07-27 23:41:34 +02:00
Marc-André Bombeck fbdadae0ce merge: s5-export into release/v1.34.0 2026-07-27 23:41:33 +02:00
Marc-André Bombeck b402be221d merge: s4-yardstick into release/v1.34.0 2026-07-27 23:41:33 +02:00
Marc-André Bombeck 6bd8d387ae feat(insights): surface the same-time baseline on Today and in the grid
The rail gains a same_time_baseline kind and a server builder, which is the
growth path that type documents for itself. It speaks only when today is
actually outside the typical band: a card that reports doing exactly what you
usually do teaches people to stop reading the rail. Dismissing it is keyed by
day and metric, not by the hour, so it stays dismissed instead of coming back
every sixty minutes.

The Insights grid gains a section of four tiles, one per cumulative metric,
each honestly quiet until the history accrues.

The two-trajectory chart it needs did not exist, so DeltaSparkline gains a
second mode rather than a second chart family: the comparison curve rides the
same container, the same fixed height and the same shared y-domain as a dashed
muted line behind the live one, which is the only way the two can be read
against each other.

Six locales. The wipe and backup plans learn about the new table: the profile
cannot be rebuilt once the samples are gone, so it is carried, not derived.
2026-07-27 23:32:22 +02:00
Marc-André Bombeck e69e0b3941 fix(admin): count every record class in the upload summary
The total shown after a backup upload skipped the nutrient day totals, so a file
that carried and restored them reported fewer records than it held. Same defect
as the payload gaps this branch closes, on the reporting side. Counts the
nutrient days, the health profile and the custom metrics with their readings.
2026-07-27 23:27:18 +02:00
Marc-André Bombeck be348f8820 test(score): pin the weight yardstick, the mood gate and rollup/live parity
Covers what the required parameters now make impossible to forget: the
scorer takes the band whole and never widens it, a missing target scores
the bare trend, the resolved yardstick rides the weight component, the
shared builder refuses the reference band that sits under an override,
the snapshot gates its mood pillar on the module map, an explicit target
reaches both the score and the dashboard bands, and weight/BMI record
direction follows the user's own goal.

F1 adds the golden the score has never had: the rollup read branch and
the live read branch scored against one series encoded both ways. On the
canonical one-weigh-in-a-day cadence they agree exactly. On an uneven
cadence they part company, and the test pins that the whole difference is
the per-day mean collapse - the rollup branch equals the live branch fed
those same daily means - with a companion assertion proving the fixture
is not a no-op.
2026-07-27 23:15:38 +02:00
Marc-André Bombeck 232217ce68 test(insights): pin the three step-page display rules
One guard per defect, each verified by breaking it on purpose first.

A non-negative metric's baseline `low` never goes below zero — swept over
every type in the catalogue with a series whose spread exceeds its
median, plus the reported step shape rendering an edge of exactly 0. The
axis assertion sits in the same file, because the point of the fix is
that the two read one fact.

No insights page passes an English word as a unit — the page sources are
scanned for `unit=` / `yAxisUnit=` literals and held against the set of
language-neutral symbols, and every `unitKey` is resolved in all six
locales. That second half matters: a key passed as a prop is invisible to
the `t()` call-site coverage sweep, so nothing else would notice a
missing translation.

An integer-valued metric never renders a fractional part — the step page
is rendered in German with no precision prop at all and must produce no
comma-decimal, on the Min/Max card and on the band sentence below it.

Two types were missing from the plausibility map, so the first guard also
asserts the map covers the enum.
2026-07-27 23:11:37 +02:00
Marc-André Bombeck 6911f115bc fix(integrations,labs): let the write decide how a sync result reads
Six integration cards and the lab-OCR dialog keyed their tone off `res.ok`.
A sync that imported nothing because every reading was refused rendered a
green tick and a count, and a re-scan in which every confirmed row turned out
to be a duplicate raised a green toast saying zero readings were saved.

Each surface now renders the outcome the route resolved: a tick only when
everything landed, a warning when some of it did not, an error when none of
it did, and a neutral note when there was nothing new. The lab-OCR toast
picks its variant the same way.

Replaces the directory-scoped import guard with one that covers the whole
component tree and pins per-file occurrence counts, so a listed file cannot
quietly add a second tick. Its docstring states what it cannot catch.
2026-07-27 23:06:44 +02:00
Marc-André Bombeck b2955beffd feat(insights): add the same-time baseline for cumulative metrics
Compare today's running total at the last completed local hour against this
person's own typical total at that same hour, over a trailing four weeks of
stored intraday profiles. Same estimator as the vitals baseline — median for
the centre, median plus or minus k times MAD for the band — so a reader who
understands one understands the other.

It refuses to speak below fourteen usable days and says so through the
existing insufficient arm rather than a band built on three days. It ships no
projected day total: extrapolating a partial day is a guess, and this exists
to replace guesses.

Steps first; active energy, distance and flights ride the same engine.
2026-07-27 23:04:01 +02:00
Marc-André Bombeck f5c676bbeb fix(insights): give a metric's numeric domain one home
Three defects on one card of the step sub-page, all of them a surface
answering a question that belongs to the metric.

"Your usual range is -5,595.6-13,387.6 steps", under a chart whose own
y-axis refuses to draw a negative step. Same app, same question, two
answers: the axis clamped and the sentence beneath it did not. Both now
read the same fact. `VALUE_RANGES` already declares every metric's
plausibility floor for ingest; `value-domain.ts` reads it and answers
"can this metric be negative" for the padded y-domain and for the median
+/- k.MAD band alike. The band's lower edge floors at zero for a
non-negative metric; `spread` stays the unclamped dispersion so a
deviation-in-sigma calculation is unaffected. `buildBaselineBand` now
takes the metric it is describing as a required argument, so a new caller
cannot quietly reintroduce a negative edge. Two types carried no
plausibility range at all, which let any number through ingest and read
as "unknown" on the negativity question: walking steadiness gets the same
0-100 percent band as its two gait siblings, the loud-listening event a
floor of 1 with a generous ceiling.

"13,387.6 steps" also said steps to a reader of German. Five sub-page
units are English words, not symbols - steps, flights, falls, years, and
a "count" that was a poor label in any language. They now travel as i18n
keys through a new `unitKey` on the metric-page scaffold and resolve in
all six locales. The thirteen real symbols (bpm, mmHg, %, m/s, kg/m2,
...) stay literals; translating a symbol would be the opposite mistake.

And the Min read "104.0 steps". Precision was a per-page prop defaulting
to one decimal, so every page had to remember that its metric is a count
- and the step page did not. It follows the metric now: a discrete metric
(tallies, category events, closed integer instrument scores, ordinal
levels) resolves zero decimals wherever it is rendered, and a page only
passes the prop to override that.
2026-07-27 23:01:33 +02:00
Marc-André Bombeck b0862c0902 feat(score): say which yardstick graded the weight row
The weight pillar has been graded against something for two years and
never said what. The score card's weight row now carries one line: the
user's own target band when they set one, or a plain statement that the
row scored the trend with no target at all. The band rides the analytics
DTO in canonical kg and the page formats it into the reader's display
unit, so an imperial account reads pounds.

The weight sub-page shades the same yardstick: its chart bands follow an
explicit target when one exists and fall back to the height-derived band
only when it does not.
2026-07-27 23:00:26 +02:00
Marc-André Bombeck 52288b7f77 refactor(outcome): lift the write-outcome classifier out of the import panel
The rule that a write which wrote nothing must not read as a success was
scoped to the import panel. Provider syncs need the same rule, so the
classifier moves to src/lib/outcome/written-outcome.ts and the tick plus the
success colour move to one presentation component the whole tree renders
through. Adds the sync shape: a run reports what it imported and whether any
part of it failed, and the outcome is resolved from both.
2026-07-27 22:43:27 +02:00
Marc-André Bombeck 99836bb08c fix(documents): make the delete-key hint readable enough to be a hint
The Del chip added beside the document card measured 4.06:1 against the card's
background, under the 4.5:1 floor, and axe failed the vault on it. So the
change that made the destructive key visible introduced a barrier of its own.

It uses the full foreground token now. The design rule says muted for meta
text, and a key hint is meta — but it is the one kind of meta that has to be
readable at a glance, so here the contrast rule wins and the reason sits next
to the class list rather than in a commit nobody will read again.

Found by running the e2e suite locally before pushing rather than after.
2026-07-27 17:41:49 +02:00
Marc-André Bombeck 7997e2c61f feat(dashboard): make the native-only widgets configurable from the web
BMI renders on the native dashboard and was configurable from nowhere.
Two individually-correct decisions produced it: `bmi` is a catalogue id
with no web render path, and the web layout editor filters those out
because a toggle over something this page cannot draw would be a silent
no-op. The widgets PUT accepts and persists them all the same, and the
native client writes them, so an account ended up holding rows it never
set and could not change from any surface.

The sortable list stays exactly as it was. Below it, the ids the web
does not draw but the layout carries get their own labelled group with
the same two flags and no reordering — placement belongs to the client
that draws them. An account with no native client has nothing there and
sees nothing. The module gate applies as it does above, fail-open.

Refs #581
2026-07-27 14:47:56 +02:00
Marc-André Bombeck e4a4c996b7 fix(documents): stop Backspace deleting a document, and show the key that does
A focused card deleted on Backspace as well as Delete. The timeline's
roving tabindex gives a card focus the moment someone tabs into the
grid, Backspace is the reflex "go back" key, and a card carries no
delete control at all — so the reflex press removed a medical document
with nothing but a transient undo toast behind it.

Delete alone now deletes. The key resolution moves into a pure helper
in vault-utils so the contract is testable without a render harness,
and the card stops keeping that contract in a code comment: the overlay
button carries aria-keyshortcuts, and a kbd chip appears while the card
holds focus. The label is per-locale (Entf / Suppr / Supr / Canc).
2026-07-27 14:47:56 +02:00