Commit Graph
7 Commits
Author SHA1 Message Date
Marc-André Bombeck 524975d5f3 fix(daily): decide the line under the Today lead on the server
The muted line under the lead repeated what the lead had just said: a lead
about blood pressure sat above "Your latest blood pressure is sitting in
the optimal band · ↓ ~10 mmHg…". The web client only dropped the headline
when the lead quoted it word for word, and other clients had no rule at
all. The digest now carries `signalLine`, built next to the lead: when the
lead already talks about the top signal's metric (matched in every shipped
language) or says its headline, only the delta remains, and with no delta
there is no line. The hero renders the field verbatim.
2026-10-05 12:19:41 +02:00
Marc-André Bombeck 5cfacba16a feat(dashboard): add a Today overview to the hero
The hero led with the first sentence of the cached briefing, which was
often a greeting ("Good morning.") while the sentence with content was
never shown. Without an AI provider it fell back to "Your health score
today is N.", which the hero then removed because the ring already
shows the number, leaving a ring and "Nothing needs your attention".

The daily digest now publishes the lead resolved on the server and a
short list of facts about the day:

- lead: the reaction line or the first briefing sentence that is not a
  greeting and does not repeat the score while AI text is available,
  otherwise a deterministic sentence about the strongest signal (a vital
  outside its personal range, a night an hour off the usual, vitals in
  range, last night). A fact the lead was built from leaves the list.
- today: up to five statements in priority order (Rest Mode, doses taken
  today, an appointment today or tomorrow that is not already on the
  rail, last night's sleep against the usual, vitals against their
  personal range, cycle day). Module-gated on the server, never padded,
  never asking for anything. A phone shows the first four.
- restMode: passed through while an illness episode is active.
- score.steadyWeeks: how long the score has held, read off the stored
  daily scores, shown under the ring in place of a delta.

briefingLead skips greetings and score sentences the same way, so the
morning push improves with it. Visit times are rendered in the profile
timezone and hour-cycle preference; the cycle day comes from the same
verdict the cycle page uses. The ring, the rail and the charts are
unchanged. OpenAPI regenerated; additive fields only.
2026-10-04 20:00:18 +02:00
Marc-André Bombeck 3309ac177c feat(ai): serve Insights and Coach data whatever AI says
Data never depends on AI. The Insights data reads no longer ask an AI
switch or the `insights` module (now only the AI analysis opt-out): ECG
list, detail and upload, rhythm events, derived scores, rule cards,
correlations and patterns, the baseline strip, health status, intraday
pulse, and the Today digest with its dismiss. Breathing screening moves
to the sleep module and lab changes to the labs module. Stored Coach
facts, conversations, message ratings and the seen stamp stay readable
and erasable while the Coach is unavailable. Cards now say
`provider: "rules"`.

Reads that mix data with model text answer 200, null the model text
and publish an `ai` state from the capability resolver, without warming
anything: comprehensive (`ai.briefing`, `ai.statusText`, `hasProvider`
from the presence probe), the derived assessment override, the period
narrative, nudge status, the seeded opener, about-me questions, the
workout paragraph, the pregenerate warm (`queued: false`), the Today
digest (lead, reaction line, check-in) and the dashboard briefing,
which is now applied per read after the snapshot cache. Hiding the
Coach no longer hides the briefing.

`insights/generate` GET and POST ask the `briefing` capability and
`insights/chat` POST the `coach` capability, with the typed refusal
envelope; a missing provider keeps the chat's `coach.provider.none`
frame and adds `reason`. The /coach page, the MCP ECG and intraday
tools and `aiAvailable` on the provider route follow the same answer.

An inventory of AI and data routes replaces the Coach gate inventory:
every route that asks a capability names it, and no data route asks.

Refs #975
2026-09-24 09:19:36 +02:00
Marc-André Bombeck ece9ba90b1 Bound the two settle waits that could never time out
`settleBeforeMeasure` ends with a `waitForLoadState("networkidle")` whose
rejection is swallowed and a `page.evaluate` awaiting
`document.fonts.ready`. Both are written to end on their own terms, and
neither had a bound: the config leaves `actionTimeout` and
`navigationTimeout` unset, which Playwright reads as no limit, so a
single sub-resource that never completes consumes the whole test budget
rather than the wait.

The way it surfaces is the worst part. The swallowed timeout hides the
stall, the run reports whichever call was still pending when the fixture
closed the page, and the message reads

  Test timeout of 30000ms exceeded.
  Error: page.evaluate: Target page, context or browser has been closed

which names neither the surface nor the resource. Raising the budget
changes nothing, because no budget bounds an unbounded wait.

Route one font request to a handler that never answers and the current
code reproduces that message exactly; with the bound in place the same
run reaches the spec's own assertion in twelve seconds.

Six seconds is an order of magnitude above the slowest settle measured
across the suite (1.1 s, /insights at a mobile viewport).
2026-08-02 05:37:14 +02:00
Marc-André Bombeck af35346687 Derive score-version fixtures from the SCORE_VERSION constant
Eight test and fixture files hardcoded scoreVersion: 2, a value the
constant has not held since v1.35.0. Now they import SCORE_VERSION and
use it, so a future bump cannot leave a fixture pinned to a stale
number.

The one genuinely historical case (record.test.ts, proving the input
fingerprint moves when the algorithm version moves) now derives both
its "previous" and "current" markers from SCORE_VERSION instead of two
unrelated literals, so the pair keeps its meaning after the next bump.
2026-08-02 00:15:29 +02:00
Marc-André Bombeck bdca4f5724 fix(dashboard): compact score-only hero 2026-07-29 17:05:53 +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