Commit Graph
2638 Commits
Author SHA1 Message Date
Marc-André Bombeck 6e2a8fcd9f feat(day): show the day's scores and the cycle day with its phase
The day view gains a scores section below the values: the health score as
recorded that day, the readiness the nightly job persisted for the night
that ended that morning, a device's own recovery, the sleep score of that
night and strain (computed, or the device's day strain on its 0 to 21
scale). Each tile carries the person's usual range over the 30 days before
and opens the score's own page; a score the day does not hold has no tile,
and a day without any has no section.

Every value comes from where its page reads its history: the stored health
score record, the persisted RECOVERY_SCORE and STRAIN_SCORE rows read on
the day they describe, the canonical recovery ladder, and the sleep score
computed over one bounded read of the 30 nights up to the day. The stored
score rows leave the values list of the day they are stamped on, so a
readiness no longer appears a day early among the readings.

Scores follow their own module (recovery, sleep; the health score is core)
and map to the whole record for sharing: a scoped grant sees them named as
not shared, a whole-record grant sees them. get_day sends them to the
Coach and the MCP endpoint by name and number, and the Coach exclusions
take out every score built on excluded data.

A running cycle now states its cycle day and phase ("Cycle day 12,
follicular phase"), computed the way the cycle calendar does. Only logged
cycles count, the next logged start closes a cycle, and an open cycle
stops counting past its typical length plus the grace window instead of
reading a day count nobody recorded.
2026-10-09 14:03:31 +02:00
Marc-André Bombeck f8dab58bc5 refactor(timeline): drop an unused month helper 2026-10-09 10:56:44 +02:00
Marc-André Bombeck 470ddeedc4 fix(version): move the finer geo state to the admin status
geoLookup and geoProviderChosen on the public /api/version added one
more way to tell instances apart. They now live on /api/admin/status
(requireAdmin, cookie only) as geo { lookup, providerChosen,
providerHost }, which both admin status views read. offlineGeoEnabled
and geoProviderHost stay public as before.
2026-10-09 10:44:18 +02:00
Marc-André Bombeck 587ea35d34 fix(coach): bind the delete journal to the account and record it was sent for
A tab outlives a logout, another login and a switch into a managed
profile. A delete re-sent in another scope met a 404 that read as
"already gone" and dropped the entry, or tripped the record fence and
toasted a failure for a row the person could not see. Entries now carry
their owner, only the current owner's are re-sent, and a session end
clears the journal with the rest of the session state.
2026-10-09 10:29:36 +02:00
Marc-André Bombeck c40b21de3d Merge the Coach header, timeline colours, selection bar and range into release/v1.42.0 2026-10-09 10:18:23 +02:00
Marc-André Bombeck 5f5ddd37a6 fix(timeline): keep "No entries" for a bucket with nothing in it
A bucket that holds a mean but no lane entry is not empty; the means
say what it holds. The bar opened on such a bucket by default and called
it empty beside its own values.
2026-10-09 10:05:06 +02:00
Marc-André Bombeck 57c44006ee Merge the document layout default and the air quality history into release/v1.42.0 2026-10-09 10:02:56 +02:00
Marc-André Bombeck 61155d1856 feat(timeline): add a chosen range as a fourth zoom
Beside All, 12 months and 3 months the timeline takes a range of the
person's own choosing, from and to, through the app's date field. Each
change is clamped softly: never past today, never before the record's
first entry, never inverted, never longer than fifteen years. On a phone,
where the chronicle has no zoom control, a range button opens the same
fields with a way back to the whole record. A range with nothing in it
keeps its fields instead of the empty-record notice.

The zoom and the range live in the URL (zoom, from, to), so Back and a
bookmark return to them; a parameter that does not read falls back to
All. The API accepts zoom=range with both ends, at most fifteen years,
and picks the bucket by its length: quarters from two years, months from
120 days, weeks from 42 days and single days below, a new day bucket
whose dashed bridge spans at most three missing days. A range borrows
the grid and the keyboard steps of the fixed zoom nearest its length.
The selection bar heads a day bucket with that day.
2026-10-09 10:02:37 +02:00
Marc-André Bombeck f8557f57a0 feat(timeline): colour each value line and name the selection's bucket
Each value line takes the colour its type carries in the measurement
list (a --chart-N token); where two chosen lines would share one, or a
type has none, the later line in the menu's order takes the first free
data token, and a sixth line with none left falls back to the
foreground. The swatch beside the name, the line, the dashed bridge,
filled and hollow points and a ring on the selected bucket all inherit
one colour from their group. The menu, the selection bar and the phone
chronicle mark each value with the same dot. The colour map moves to an
icon-free module so the timeline does not pull the list's icons.

The selection bar is a cross-section of one bucket and now says which:
its head names the quarter, month or week in full, its entries are the
ones that touch that bucket, and each mean keeps its readings count.
Values go through the day view's formatter, so sleep reads as a
duration and the unit preference applies; the server sends canonical
units, which the old formatter took for display units. Before anyone
picks a day the bar opens on the newest bucket it has something for in
the requested window (documents, which it never lists, no longer count),
and a short line says how to pick another.
2026-10-09 09:48:53 +02:00
Marc-André Bombeck f788e2892d fix(coach): put New chat in the conversations panel header
The round button floated over the list's bottom right and covered the
last conversation, which the list offset with a pb-20 reserve. It is now
the first control of the panel header, left of the title, in the docked
panel and the phone sheet alike: 28 px beside a fine pointer, the 44 px
floor on touch, with its accessible name and a tooltip. The reserve goes
with it, and the header packs its touch-sized buttons tighter so the
title keeps its word at 390 px.
2026-10-09 09:48:46 +02:00
Marc-André Bombeck d81a6ddf25 feat(environment): fill air quality for every past day with entries
A background job fetches air quality, pollen and UV for every earlier
local day with a reading, mood entry, dose, symptom or workout: after the
update (boot and nightly discovery), when the home is set and when air
quality is switched on. A day with a weather row gets its air quality; a
day without one gets both, at the covering location period or else at the
home, also before the home was set. Days the source does not reach (before
2013 in Europe, before August 2022 elsewhere) stay empty and are not
counted.

Pending days are grouped by coarse location and cut into ranges of up to
90 days, joining days at most 14 apart, newest first. A run holds itself to
300 of the account's 400 daily calls and half of each instance window,
stores its progress on the user (migration 0391) and sends a follow-up while
it makes progress; a budget stop waits for the next discovery. The
Location & weather card shows the progress.

The air-quality client now asks pollen and dust from 2021 and skips the
request outside Europe before CAMS global starts.
2026-10-09 09:43:01 +02:00
Marc-André Bombeck 898729d1a8 feat(insights): explain the shorter correlation list once
Since correlations are tested on seasonally adjusted residuals with an
effective sample size, an account with history sees far fewer
relationships, which reads as a defect without a word about it.

The correlations response now carries findingsBeforeSeasonalAdjustment:
true when the record kept a discovery pattern first stored before the
change, one indexed lookup on the pattern store. An account created since
can never qualify. The mood "What stands out" card shows a short note
for those records, closable, remembered per viewer like the AI setup
hint. Copy in all seven locales; OpenAPI regenerated.
2026-10-09 09:29:38 +02:00
Marc-André Bombeck 4c58274bed Merge the Coach conversation delete fix into release/v1.42.0 2026-10-09 09:14:42 +02:00
Marc-André Bombeck 359f11fe4d fix(coach): make a deleted conversation stay deleted
Deleting a conversation hides the row and sends the DELETE only after
the six-second undo window. The pending delete lived in a timer alone,
so a reload, a closed tab or an installed app sent to the background
inside the window dropped it without a request, and the conversation
came back. When the window did run out, the hide filter was released
while the paginated history still held the row, so it showed again
until the refetch landed.

Every surface that deletes a conversation (the Coach panel, the
conversations page, the settings memory card) shares one hook, and the
fix lives there:

- a scheduled delete commits early when the page is hidden, on
  pagehide and on unmount, and the request is sent with keepalive
- a committed row leaves every list cache, head list and every history
  search variant, before anything else changes, and stays hidden
- deletes not yet confirmed are kept in sessionStorage; the next page
  hides them and sends them again, so its list read cannot bring the
  row back before the old page's request lands; a 404 counts as gone
- a failed delete restores the lists and says so instead of the row
  quietly returning
- Undo after the commit reports false, so the rail does not reopen a
  thread that is gone
2026-10-09 09:14:10 +02:00
Marc-André Bombeck 5cf1a6c552 fix(timeline): drop the explanatory line under the life event sheet title 2026-10-09 09:10:34 +02:00
Marc-André Bombeck f6f0969da9 Merge the timeline buckets, gaps and series limit into release/v1.42.0 2026-10-09 09:04:11 +02:00
Marc-André Bombeck fcc6f40410 feat(timeline): keep gaps in value lines, mark thin means, allow six lines
Every bucket with a reading gets its point; one with fewer than three
readings is drawn hollow. A short gap (one quarter, two months, four
weeks) is bridged with a dashed stroke between the two real points; a
longer one leaves the line open. The selection bar and the value table
for screen readers name each bucket's readings and say "no value" where
there is none. The phone chronicle names each bucket once, at its newest
month. The value menu takes up to six lines. The legend's causality line
is gone with its keys in all seven bundles.
2026-10-09 09:03:42 +02:00
Marc-André Bombeck fb7f1eea61 Merge the day running-items and docked-panel changes into release/v1.42.0 2026-10-09 08:54:46 +02:00
Marc-André Bombeck c3dff2c388 feat(day): collapse the docked day to an edge instead of closing it
From 1280 px the day sits beside the page, and its control at the top
left took it away entirely: after a page change there was no way back to
the day short of finding it again. The control now hides the day the way
a sidebar collapses. A narrow edge stays on the right, holding the date,
with a button that opens the last day again on this page or any other.
Focus lands on that button when the day collapses and on the day's
heading when it comes back.

The last open day is remembered per browser, bound to the account and
the record it was read in, and removed at every session end. The Coach
button keeps clear of the edge as it does of the open day. Sheets below
1280 px and on phones close as before, and Back still closes them.
2026-10-09 08:54:11 +02:00
Marc-André Bombeck 7340cd3ab7 feat(documents): run the vault's months side by side by default
An account that never picked an arrangement now sees the months flowing
across the width. The layout PUT stores only the fields the person chose,
so an untouched field keeps following the default while a saved choice
stays.
2026-10-09 08:50:20 +02:00
Marc-André Bombeck 0a6811c03f fix(admin): read a geolocation provider the operator named as a decision, not a warning 2026-10-09 08:38:58 +02:00
Marc-André Bombeck 09f94d3eac fix(day): run only records with a start of their own through a day
The "Running that day" list carried the profile facts (smoking status,
alcohol pattern, shift schedule) as running items, titled by their raw
codes and counted "day n" from the date the fact was filed. A person who
quit smoking years ago and entered it last week read "day 13". Those facts
describe the person, hold no start the person gave, and are not part of a
day; the lifestyle section and its running kind are gone from the day, its
index and the model-facing get_day.

The same false count reached a medication filed without a start date: its
creation date stood in as the start. `since` is now null there, with no
day count, and the entry's date only bounds which days show it. A life
event's category is worded in the reader's language instead of its code.
2026-10-09 08:38:01 +02:00
Marc-André Bombeck 88836452eb fix(day): show several readings of a type as their mean in the day's tiles 2026-10-09 08:29:12 +02:00
Marc-André Bombeck 6ff24cf2cf fix(coach): scroll the Coach with the same scrollbar as every other surface 2026-10-09 08:22:41 +02:00
Marc-André Bombeck 312e5de2b2 fix(security): keep the theme boot script a literal and drop regex tag stripping
The boot script interpolated its two colours; it is one string literal now,
checked against THEME_COLOR by the script's own tests. The retired-route
guard escapes every regex metacharacter including the backslash, and the
two markup tests read text by walking the string instead of stripping tags
with a regex.
2026-10-09 00:41:56 +02:00
Marc-André Bombeck 3ad0607cec fix(settings): stack the coach nudge frequency row on a phone
At 16 px touch type the longest frequency option and its label no longer
fit one row inside a 390 px card, and the select pushed the page sideways.
2026-10-09 00:41:56 +02:00
Marc-André Bombeck 4a02b78f5a test(e2e): take the mood empty state's add control in the managed-profile journey
Since an empty mood list carries the one add control and the header drops
its own, the journey clicked a header button that unmounted as the list
resolved. Start the profile without entries, assert the empty-state add,
then the header add once an entry exists. Both carry a data-slot.
2026-10-09 00:41:54 +02:00
Marc-André Bombeck f0fba23c34 refactor(insights): leave the arrange editor's sorted rows to the compiler
The draft now comes out of a render-time reconcile, so a manual memo
over it could not be preserved and the compiler skipped the component.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck facfcad107 fix(day): keep the pinned heatmap tooltip off the right edge
The tooltip shrinks to the room right of its clamped left edge and
wraps, so it never ran past the screen, but near the right border it
filled that room to the last pixel. Cap its width at the room less the
8 px gutter the clamp was meant to leave.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 7525d2ed00 fix(settings): end the allergy and family-history cards in one action row
The allergy card had two action rows, the list's add button and the
free-text note's save, and the family-history card on a shared record
put its sentence about relatives after the list's actions. The add
button now rides the note's row before save, and the sentence moves
above the list.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 04084dd6e2 fix(ui): link the adherence tile straight to the medication wizard and drop dead rail rules
The empty adherence tile linked to the retired /medications/new route,
which only redirects. The sidebar's short-touch rules keyed on a touch
screen at most 520 px tall, where the phone shell hides the rail, so
they could never apply.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck c1144b3c4c fix(ui): let a truncating label actually truncate
A span marked both shrink-0 and truncate never gives up width, so a
long source name on a collapsed priority group squeezed the metric
label at phone width, and a long month marker in the document flow
could overrun its cell. Both shrink now and cut with an ellipsis.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 8f5b5d6e54 fix(a11y): keep disclosure panels in the tree so aria-controls resolves
The encryption column table and the source-priority groups, overrides
and device ladder rendered their panel only while open, so a collapsed
toggle's aria-controls pointed at nothing. The panels now stay mounted,
hidden and empty, while closed.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck ef1976832b refactor(css): move the shell and sheet rules into the components layer
Unlayered, the safe-area, keyboard and sheet-clearance rules outranked
every utility on the same element, so a later bg-* on the shell or a
bottom-* on a sheet would have been ignored without a trace. They now
sit under the utilities. The bottom sheet drops its bottom-0, which the
layered keyboard rule replaces with the same zero until the keyboard
opens; the keyboard rules that must win keep their !important. The
toast lift stays unlayered to outrank the toaster's own rules.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 938b5151de fix(ui): drop the home-indicator padding of a bottom sheet on the keyboard
A bottom sheet standing on the on-screen keyboard kept its safe-area
inset as bottom padding, leaving about 34 px of empty space between
its last row and the keys on an iPhone.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck ad620bcf80 fix(pwa): keep the new worker's precache when loading a new version
The reload cleared every HealthLog cache after asking the worker to
update, but the update resolves as soon as the new worker starts
installing. Its fresh precache of logos and favicon was deleted with
the old ones, so the installed app lacked them offline. Leave the
target release's caches in place.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 58592e4822 fix(day): keep edge days and single readings in the dots under a time axis
A time-axis chart drawn from real instants tested each day's noon
anchor against the readings themselves, so the first day dropped out
when its reading came after noon UTC, the last when it came before, a
single reading never got a dot, and the span was cut in UTC while the
days were local. The row now spans the points' own calendar days, as
the chart hands them to its day links.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck f38d8a5803 fix(day): word the cycle and travel titles by record kind, not by text
The day view translated any title reading "cycle" or "travel", so a
medication or a document with that name showed up as the localised
cycle or travel label.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 798fae747f fix(measurements): drop the bare separator before a unitless row's date
A score or a rating has no unit word, but the phone row still appended
the separator, so its meta line opened with a stray dot.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 27dfc6a617 fix(ui): scroll to a deep link's target once a settling body reveals
A body held back until its reads settle was hidden when the page
arrived, so a link such as the pill-order anchor in the insights
settings had nothing to scroll to and the page stayed at the top.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck ecf56c6a1f fix(a11y): give the adherence and efficacy charts a keyboard way to their days
Both charts open a day on a click or a tap but carried no data table
and no day link, so a keyboard or screen-reader user could not reach
the days at all. Render the chart's points as a data table with day
links, and have the coverage guard require that way on every
day-linked chart or name where it lives instead.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck efbaf02335 fix(insights): settle the stat strip when its chart fails to load
The strip holds min, max, median and mean until the chart reports its
visible window. A chart chunk that failed was caught by the boundary
and never reported, so the cells stayed skeletons although the summary
was already there. The boundary now settles the strip with no window.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 78b0cc06e2 fix(documents): keep the bulk bar clear of the bottom bar on a phone held sideways
The bar dropped to the page edge at md, but the bottom bar follows the
shell and stays on a touch screen held sideways, which left the action
row, Delete included, half under it. Key the drop on shell-desktop and
include the home-indicator inset.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck ca47077944 fix(pwa): lift toasts above the bottom bar wherever the phone shell shows it
The toaster's mobile offset applies below 600 px only, while the bottom
bar follows the shell up to md and on a touch screen held sideways. In
between, the update toast and its reload button sat over the tabs.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck a5546507fa fix(insights): keep a pill save when the overview arrangement saves after it
The arrange card sent the tile list it copied on mount, with a token
read fresh from the cache, so a pill-order save made beside it was
overwritten without a conflict. Send the current server tiles, and let
an untouched section draft follow the server copy so the pill
manager's ECG toggle no longer reads as a pending edit.
2026-10-08 22:57:47 +02:00
Marc-André Bombeck 055c48a3fe fix(handover): show the link holder display names only
The claim preview is anonymous: whoever holds the link has proved
nothing yet. It listed each guardian's grant id and fell back to their
login name. It now carries the display name alone, null when there is
none, and the page says "a guardian" for that.

The notifications to the guardians fell back to the new owner's login
name when the record had no display name. They now use sentences about
the person the guardian looked after instead.
2026-10-08 22:37:57 +02:00
Marc-André Bombeck 78cde56542 fix(environment): fail closed on a travel period that does not open
A location period whose sealed location this host cannot open was
dropped from the resolver, so its days fell through to the home and the
next refresh overwrote the stored travel days with the weather at home.
Those days are now skipped and the run logs an error signal.

A restore now opens every sealed location once. One that does not open
is kept sealed, so the refresh keeps skipping its days, and is named in
the restore report under a new catalogue.
2026-10-08 22:37:56 +02:00
Marc-André Bombeck 313acdf304 Size the source-priority skeleton to every metric line
The loading state now reserves one collapsed line per metric, the upper
bound of the loaded list, so the action row does not move down when the
ladders arrive.
2026-10-08 21:04:09 +02:00
Marc-André Bombeck 8ff35231f8 Write English navigation and insights chip labels in sentence case
Settings, admin and main navigation read "Data & privacy", "Export &
import", "Health score", "API & tokens", "MCP connector", "System
status", "Danger zone" and "Admin console", and the insights metric
chips drop their title case ("Blood pressure", "Body fat" and the rest),
matching the labels around them. The other locales already followed
their own conventions; the API page title is translated where it had
been left in English.
2026-10-08 20:53:18 +02:00
Marc-André Bombeck a589e15f86 Name the arrangement settings hub "Layout"
The hub arranges and picks what the dashboard, insights and each module
show and holds no theme control, so "Appearance" promised something it
does not have. It reads "Layout" in every locale, and its subtitle says
what it does. In German "Darstellung" now names only the account page's
language-and-units card.
2026-10-08 20:47:55 +02:00