Under "What happened" an intake now reads "Ramipril 5mg" on one line, as
the same medication does under Running that day, instead of the name over a
muted dose line. A lab day's analyte count moves from the run-on second part
to the entry's tag: a chip of its own in the selection bar, brackets in the
screen-reader line and the hover title, the muted line in the chronicle.
The dashboard and the Coach now show the fixed strips at the right edge
with the day docked beside them, and the search field in the top bar.
Adds the command palette searching for sleep and the Health Score
history page. The timeline keeps All with one row per medication, five
value lines and a selected quarter. Each in light and dark, from the
demo seed on a local instance.
Day view docked beside the dashboard with its running line, values and
the five scores, and as a phone sheet; the timeline at All with one row
per medication, five value lines and a selected quarter; the phone
chronicle; the air quality and pollen card; the readiness inventory;
and the Coach beside the docked day with the new-chat button. Each in
light and dark, from the demo seed on a local instance.
The day view reads readiness from the nightly COMPUTED recovery row and
the health score from the record of each local day; neither job reaches
back, so a fresh seed showed both for today at most. Mint the rows the
jobs would have written over the window, and give magnesium the start
date its running line needs.
Day view docked beside the dashboard and as a phone sheet, the timeline
at All with a selected quarter, the phone chronicle, the air quality and
pollen card, and the readiness inventory, each in light and dark. Taken
from the demo seed on a local instance.
The demo account carried 90 days, which leaves the timeline's All zoom
and the day view with nothing to show before last quarter. The seed now
writes about four and a half years before that window: blood pressure in
bursts with a silent quarter and a thin one, weekly weight, phone steps,
watch resting heart rate, HRV and sleep from the third year, yearly
blood panels with HbA1c, lipids and hemoglobin, a resolved chronic
condition and three infections, ramipril with a stated start, a dose
change and a sick-day pause, a finished antibiotic course, flu shots and
a Tdap booster, visits, life events at day, month and year precision, a
sealed home location with a holiday period, and the stored weather, air
quality and pollen for every day. The timeline module is switched on.
All of it is invented and relative to the seed run; rows are written in
chunks so the seed stays quick.
From 1280 px the day and, on /coach, the conversations each keep a
narrow strip at the right edge, open or not. A click on the strip opens
its panel left of the strips and a second click closes it; the strip
never moves. Strips start below the top bar's band, side by side,
conversations first. Below 1600 px one panel is open at a time.
The day slides like the conversations (width, 200 ms, none under reduced
motion), stays open through a page change instead of folding and
reopening, and does not slide in on arrival. Its content loads when a day
first opens, so the scores, sections and calendar leave the shell's first
load: every watched route falls back under its unchanged ceiling. The
aggregate cap is re-measured for the chunk double count this causes.
react-day-picker and the seven date-fns locales rode the first load of
every page with a date field, the dashboard and the settings sections
among them. The calendar now lives in its own module that the popover
loads on first open; the locale map moves beside it.
Parallel PUTs to /api/dashboard/chart-overlay-prefs from one account ran
in Serializable transactions, which aborted all but one with a
serialization failure and answered 500. Lock the user row with FOR UPDATE
instead, so each writer waits for the previous commit and reads it. The
layout reset on DELETE /api/dashboard/widgets takes the same lock.
Keeping the day open across pages read every loss of ?day= as a page
change, so Back past the step that opened the day on the same page
reopened it at once. A loss of ?day= with the page unchanged is the
person leaving the day, and it closes it as before; only a change of
page carries the open day along.
The paged read still has to see every row; the latest pulse is now the
value of its latest local day, computed in the test through the same
day-mean helper.
The day's count line, the usual-range label beside the focus value and
the selection bar's "Open <day>" link are gone, and so are their strings
in all seven bundles. The pulse latest-day read is listed in the dense
read guard with why it stays bounded: one type over one day, the rows the
day view reads anyway. The summaries test's database stub answers that
read.
The layout settings gain a switch for the dashboard's top card (the Today
card above the tiles). Stored server-side on the layout blob as
`todayCardVisible`: missing means shown, only an explicit false hides it,
the default is omitted from storage, a save that omits the field keeps
the stored choice, and a layout reset shows the card again. Hidden, the
card leaves no skeleton or error card behind. The flag rides the
account-settings backup with the rest of the layout.
The field is deliberately not `heroVisible`, the retired opt-in hero's
flag that older stored layouts may still carry.
An end-to-end journey on its own account switches the card off and on
and walks the score histories' range tabs.
The health score had no history anywhere, readiness and the sleep score
showed a fixed sparkline, and only the stored nightly scores offered the
range tabs. Every score page now charts its course with the same
7 / 30 / 90 / All tabs, remembered per page, and each point opens its day.
- GET /api/insights/score-history?score=&days= serves the daily course of
the health score (its stored day rows), readiness (the nightly blend,
filed on its wake day) and the sleep score (each night against the
trailing window ending on it), so a point equals what the day view shows
for that day. MANAGE record read, module-gated for readiness and sleep,
days 1 to 3650, additive.
- The health score's line breaks where its recipe changed (configuration
or algorithm version); the usual range is formed from the newest side
of the last seam.
- The range tabs move into one shared component used by every chart;
recovery, stress and strain keep their measurement chart, now with the
range remembered and no overlay dropdown.
- New page /insights/health-score, linked from the score panel and from
the day view's score tile.
A closed period's name could not stand right of its own bar: the room
check counted the bar it names as the next mark ahead, so "Away" went
above its bar and sat on top of the life event named on the row above.
The room ahead now counts only what starts to the right. A row's names
and the names above the next row's bars share one band a few pixels
tall, so whatever is placed on one is written into the other as well.
The phone chronicle listed "First value of a measure" twice on a day
when two metrics began on it, under one React key; it lists one line per
kind of notable a day.
The journey now writes two medications through the real routes (one with
a start date and its course, one known only by an intake) and asserts
each is drawn on exactly one row; carries an open day from /timeline to
/labs and back after folding it; hovers and focuses a value point and
checks the card names that line alone and stays inside the chart; and
measures the page at 1280, 1440 and 1920 px, with and without the docked
day: no sideways scroll, and no height past the content beyond the
shell's own padding. Expanding all of a day's values scrolls the panel
and leaves the page height as it was. The "Open <day>" link the bar no
longer has is asserted absent; a chip opens the day instead.
Label texts carry their lane under their own attribute, so a lane
selector matches the lane and not its names.
A point of a value line now shows a card for that line and that point
only: its name, the value with its unit, the stretch it is the mean of and
the readings behind it, in the tooltip the other charts use. Each point
has a 24 px HTML target over it: a pointer resting on it shows the card,
the keyboard reaches every line once and walks its points with the
arrows, a tap shows the card and a second tap (or its "View the whole
day") opens the day. The card is clamped to the chart's width. The SVG's
native titles are gone.
The two screen-reader tables beside the chart were hidden with sr-only on
the table element itself, where overflow: hidden does not apply: they laid
out at full size below the page and made <main> scroll sideways and past
its last card. They are hidden by a wrapper now, and the page ends where
its content does at every width, with and without the docked day.
The docked day stays open across pages. Leaving a page drops ?day= from
the URL, and the column folded to its edge on every navigation. The
remembered day now also remembers that it was left open (per account and
record, as before); a page reached without ?day= while it was left open
brings the day back, without taking focus from the page. Folding,
closing, Escape, a swipe down or opening the Coach's conversations
beside it is what closes it.
Cmd/Ctrl+K or the search control at the end of the top bar opens a
combobox over a client-side index built from the lists the navigation
already renders: pages, Insights pages, every Settings section and card
(deep-linked by a new anchor prop on SettingsCardHeader), the Appearance
sub-pages, module switches and a few actions. Entries follow the module
map, the Coach capability, the admin role and a shared record's grant;
inside somebody else's record no cards, switches or backup are offered.
Ranking is prefix, then word start, then substring, then a small edit
distance, then subsequence, with a short multilingual synonym list. The
empty state shows recent places (per account, in localStorage) and the
main actions. The palette is its own lazily loaded chunk; the top bar
shows a search field where it has room and a magnifier otherwise. The
module switch mutation moves into a shared hook used by the Modules hub
and the palette.
Labels are placed without collisions: a name keeps a full gap before the
next mark on its line, is cut with an ellipsis when only part of it fits,
and is left out when not even a few characters do. A name above an open
bar ends before the bar's arrow head. All names are drawn after the today
and selection lines, on a halo of the card colour, so a line passes
behind a name instead of through it.
A value line's name wraps onto a second line instead of reading "Blood
pressure sy…", in every bundle, and it leads to the metric's page, as do
the value tiles in the day view. Both read one map from a kind of value
to its page (metricPageHref); a kind without a page stays plain text. The
links carry a 44 px target, a hover and a focus ring.
A life event's category is no longer run on after its title ("Parental
leave Family"): the chart names the event alone, the selection bar shows
the category as a small chip inside the entry, and the phone chronicle
puts it on the muted line below.
The selection bar shows each mean as name and value; the readings behind
it ("mean of 19 readings") are a hover title and a screen-reader detail.
The lone "Open <date>" link at its end is gone; a day opens from its
chips, Enter on the chart or a double click.
In the day view an event's time keeps to one line in every locale.
Running that day listed a medication, its course and its pause as rows of
their own, so Mounjaro read as "Mounjaro 7.5 mg" above "Mounjaro". The day
read now folds them into one entry per medication: its name with the dose
in effect that day (the last dose change on or before it), the course's
days on the second line, or the paused entry while a pause runs. The
Coach's get_day reads the same loader and gets the same single entry.
The dashboard's pulse widget showed the resting heart rate whenever one
was recorded but called it "Pulse", while the day view's "Pulse" tile
was the day's pulse; one day read 62 on the dashboard and 77 in the day.
The widget is now named by what it shows ("Resting heart rate" with
resting rows, "Pulse" without), and the pulse's latest value is the
latest day's pulse, the mean of its local hours' means through the shared
day-mean helper and the day view's own canonical-source pick, so both
"Pulse" figures are one number. The day view's pulse goes through the
same helper in the record's zone.
The count line above the day ("33 values, 3 entries") and the usual range
repeated beside the focus value ("Your 30 days before") are gone; the
sections and the value tiles already say both. An assessment's score and
band join with a comma, not a middle dot.
An active medication whose courses had all ended had no stretch left in
the current window: the courses decided when it was taken, none reached
into the window, and the medication's own open span was set aside because
it had courses. The row disappeared while the day view listed the
medication as running. The time since its last course now stands in as
its stretch, open to today.
The server cut a lane's items at the window edge, so a medication arrived
with only some of its courses and without the dose change that set the
dose of the piece running into the window. A medication touching the
window is now sent whole.
On a phone, every entry wears its lane's colour: the icon in the lane
colour, and a medication listed inside an illness keeps a dot on its own
rail instead of reading as the illness's orange. A month without an event
is no longer called empty when it had readings (its header stays and
carries the means) or while a medication was taken in it; only a month
with none of the three gets the "No entries" line.
A dash between two parts of a sentence ("{what} — tomorrow", "Could not
record {name} — try again", "End {name} — …") stands in for a comma, a
colon or a full stop the sentence should say. Every such string in the
seven bundles now says it in plain words. A range keeps its unspaced en
dash ("70–100 mmHg", "{start}–{end}"); that is how a range is written,
not a pause, and the spaced range forms became unspaced ones.
Labels the components put together themselves follow the same rule:
aria-labels read "Move up: Ramipril", a medication and its dose read
"Ramipril 5 mg", page titles use "|", Coach prefills are two sentences,
and the API's user-facing error messages lost their dashes too.
The bundle guard now refuses " — " and " – " beside " · ", with an empty
allowlist, and a sibling guard walks src/components and src/app for a
string or template literal that joins its parts with a spaced dash.
g then d/m/l/t/i/c/s opens a page through the same visible-destination
lists the navigation renders, so a switched-off module, a Coach without a
provider or a door a shared record does not cover stays shut. n opens the
capture picker, [ and ] step the open day, ? lists everything. Keys typed
into a field, held with Cmd/Ctrl/Alt, or pressed while a dialog or menu is
open are left alone; [ and ] still act over the day sheet and when the
layout needs Alt or AltGr to type them. The list is also reachable from
the sidebar account menu.
A medication arrived as up to four kinds of item (its own span, its
courses, its dose changes, its pauses) and was drawn item by item, so a
medication with a course read as two bars on two rows. Every item of one
medication now carries the medication's id as `group`, and the lane draws
one row per group: the courses are the stretches it was taken, each dose
change cuts them into pieces labelled with their dose, and a pause is a
gap in the same bar. The selection bar and the phone chronicle list one
entry per medication with the dose in the text.
The value series cap of six is gone. The response carries
`availableSeries`, every series the record can show, and the picker is
one compact "Values (n)" button whose menu lists them. Past the fifth
line the five data colours repeat; every line is named at its left.
Choosing a range opens a popover anchored to the "Range" segment (a sheet
on a phone) with "Apply", so the chart no longer moves; the URL keeps the
range as before. The selection bar sits on the card under a hairline
instead of a grey box, the legend explains the dashed stroke and the
hollow point, and no timeline text joins its parts with a middle dot or
calls an open span "ongoing".
In the phone's bottom sheet the date sits halfway down the screen, and a
month of finger-sized cells below it ran off the bottom, Today button and
all. There the calendar now opens as a bottom sheet of its own and hands
focus back to the date when it closes. The docked column and the side
sheet keep the popover.
The conversations panel's fold and unfold buttons carry a title instead
of a tooltip: focus lands on them when the panel opens or folds, and a
tooltip opened by that focus took the next Escape for itself.
Sleep is stored one row per stage, and the timeline read it like any
other type: the live path took the mean of a day's rows and the long
path the rolled-up month of them, so IN_BED, AWAKE and every CORE
fragment counted as a night, and a quarter of seven-hour nights read as
under two hours.
The sleep series now reconstructs nights the way the sleep page and the
day view do (CORE + DEEP + REM of the night's winning writer, keyed by
its wake day) and averages the bucket's nights, on every window length;
the rollups are not read for it. The stage rows are read a year at a
time with a day's margin on either side, so no read holds a decade of
rows and no night is cut where one read ends. A long window is cached
per record like the other long series.
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.