Commit Graph
7964 Commits
Author SHA1 Message Date
Marc-André Bombeck f0b4e902bb Merge pull request #1193 from MBombeck/fix/maintenance-shm
Refs #1192
2026-10-10 12:19:02 +02:00
Marc-André Bombeck a2ee79a746 fix(maintenance): run the measurement vacuum without a /dev/shm segment 2026-10-10 09:33:17 +02:00
dependabot[bot] e1eebebd1d chore(deps-dev): bump the development-weekly group across 1 directory with 3 updates (#1191)
Bumps the development-weekly group with 3 updates in the / directory: [@next/bundle-analyzer](https://github.com/vercel/next.js/tree/HEAD/packages/next-bundle-analyzer), [lefthook](https://github.com/evilmartians/lefthook) and [shadcn](https://github.com/shadcn-ui/ui/tree/HEAD/packages/shadcn).


Updates `@next/bundle-analyzer` from 16.3.3 to 16.3.8
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.8/packages/next-bundle-analyzer)

Updates `lefthook` from 2.2.1 to 2.1.17
- [Release notes](https://github.com/evilmartians/lefthook/releases)
- [Changelog](https://github.com/evilmartians/lefthook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evilmartians/lefthook/compare/v2.2.1...v2.1.17)

Updates `shadcn` from 4.21.4 to 4.21.3
- [Release notes](https://github.com/shadcn-ui/ui/releases)
- [Changelog](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/CHANGELOG.md)
- [Commits](https://github.com/shadcn-ui/ui/commits/shadcn@4.21.3/packages/shadcn)

---
updated-dependencies:
- dependency-name: "@next/bundle-analyzer"
  dependency-version: 16.3.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-weekly
- dependency-name: lefthook
  dependency-version: 2.1.17
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-weekly
- dependency-name: shadcn
  dependency-version: 4.21.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-weekly
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-10 02:07:07 +00:00
Marc-André Bombeck b10a1132e6 Merge pull request #1190 from MBombeck/deps/vitest-5
chore(deps): vitest 5
2026-10-10 03:37:09 +02:00
Marc-André Bombeck 91c358c56d Merge remote-tracking branch 'origin/main' into deps/vitest-5 2026-10-10 02:44:39 +02:00
dependabot[bot] c1309fba33 chore(deps): bump the production-weekly group with 2 updates (#1186)
Bumps the production-weekly group with 2 updates: [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) and [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react).


Updates `@aws-sdk/lib-storage` from 3.1146.0 to 3.1147.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1147.0/lib/lib-storage)

Updates `lucide-react` from 1.53.0 to 1.52.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.52.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: "@aws-sdk/lib-storage"
  dependency-version: 3.1147.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-weekly
- dependency-name: lucide-react
  dependency-version: 1.52.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-weekly
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-10 00:15:35 +00:00
Marc-André Bombeck 6b11739a51 Merge pull request #1187 from MBombeck/deps/dotenv-18
chore(deps): dotenv 18
2026-10-10 02:12:14 +02:00
Marc-André Bombeck e4879522c0 Merge pull request #1185 from MBombeck/deps/dev-2026-10-09
chore(deps): weekly development updates
2026-10-10 01:43:51 +02:00
Marc-André Bombeck 013a3fb86a chore(deps): move the test runner to vitest 5
vitest and @vitest/coverage-v8 move from 4.1 to 5.0.3 together. Neither
config uses an option the major removed or changed: no projects, so the
new setupFiles concatenation does not apply; no browser mode; and the
integration config's forks pool, serial files and isolation keep their
meaning. Node 22.12 and Vite 6.4 are the new floors, and both are met.
2026-10-10 01:15:10 +02:00
Marc-André Bombeck 369775e039 chore(deps): move dotenv to 18
The breaking changes in 18 do not reach this repository: nothing preloads
dotenv with -r, nothing reads a .env.vault, and both entry points in use
(the default export in prisma.config.ts and the side-effect dotenv/config
import in the one-shot scripts) keep their behaviour. dotenv/config stays
quiet by default.

What does change is the load message, which now goes to stderr. Prisma
reads prisma.config.ts on every migrate deploy at container start, so
both loads there pass quiet: true rather than adding two stderr lines to
each boot. The image's /opt/prisma-cli install, which the config resolves
dotenv from, moves to the same 18.0.6.
2026-10-10 01:08:57 +02:00
Marc-André Bombeck 6d2f22e922 Merge pull request #1184 from MBombeck/deps/production-2026-10-09
chore(deps): weekly production updates, next and zod held back
2026-10-10 01:00:02 +02:00
Marc-André Bombeck 1d782d88e2 chore(deps): take the weekly development updates
@testcontainers/postgresql 12.2.0, @types/node 26.6.4, @vitejs/plugin-react
6.1.2, knip 6.40, lefthook 2.2, pdfjs-dist 6.4.299 and shadcn 4.21.

pdfjs-dist is a development dependency that the image ships: the runner
hoists the pinned copy for document rendering. 6.4 asks for
@napi-rs/canvas ^1.0.10, which the production updates bring, so the store
keeps a single canvas build for it. eslint-config-next and
@next/bundle-analyzer stay on the 16.3 line with next.
2026-10-10 00:49:17 +02:00
Marc-André Bombeck ff0a81154c chore(deps): keep next on 16.3.8, 16.4.0 breaks history-driven URL state
With 16.4.0 a document sheet opened through ?doc= stays open after Back,
and the [ / ] day keys sometimes leave ?day= where it was. Three end-to-end
journeys fail 3 of 40 repeated runs on 16.4.0 and 0 of 80 on 16.3.8 with
every other update in place, and CI hit the same journeys on both
viewports. 16.3.8 already carries every published security fix, so
Dependabot skips 16.4.0 for next and the two packages that move with it.
2026-10-10 00:36:36 +02:00
Marc-André Bombeck c2ac1428e5 chore(deps): take the weekly production updates, zod held on 4.4
Next 16.4.0, pg-boss 12.37.1, the AWS SDK 3.1146, @napi-rs/canvas 1.0.10,
@node-rs/argon2 2.2.2, @simplewebauthn/server 14.0.3, TanStack Query and
Virtual, lucide-react, mmdb-lib, nodemailer, otpauth, pg, radix-ui,
react-day-picker, react-hook-form, tailwind-merge and yaml.

pg-boss moves its schema from 42 to 45 (three additive migrations, nullable
columns and one partial index, no table rewrite); the worker applies them on
start as before. The retention test pins the installed version, so it moves
to 12.37.1 after re-checking every source line it asserts.

zod stays on 4.4.3. 4.5 requires seconds in `z.iso.datetime()` and 4.6
rewrites nullable unions in the generated JSON Schema into `type` arrays,
which reshapes the OpenAPI document and the MCP tool schemas. Dependabot
skips zod 4.5 and later until that move is made on purpose.
2026-10-09 23:03:03 +02:00
Marc-André Bombeck 90050815eb Merge pull request #1178 from MBombeck/release/v1.42.0
Release v1.42.0
v1.42.0
2026-10-09 20:46:02 +02:00
Marc-André Bombeck e6a2429df9 fix(documents): keep four tiles across a 1440 px window with the day strip docked 2026-10-09 20:21:10 +02:00
Marc-André Bombeck 3becc235ae docs(release): retake the timeline images with the lab count as its own chip 2026-10-09 19:55:11 +02:00
Marc-André Bombeck d5a153893c Merge v142/release-images into release/v1.42.0 2026-10-09 19:44:09 +02:00
Marc-André Bombeck 27781ef12c Merge v142/last-chips into release/v1.42.0 2026-10-09 19:44:09 +02:00
Marc-André Bombeck d068d6c9e6 fix(day,timeline): put an intake's dose in its name and set a lab day's count apart
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.
2026-10-09 18:55:34 +02:00
Marc-André Bombeck 8a5589c32b docs(changelog): bring the v1.42.0 day view and timeline entries to the shipped state 2026-10-09 18:46:21 +02:00
Marc-André Bombeck 6a27bdfc2d docs(release): retake the v1.42.0 release images with the panel strips
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.
2026-10-09 18:44:33 +02:00
Marc-André Bombeck 1319c63825 docs(release): retake the v1.42.0 release images on the final state
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.
2026-10-09 18:30:29 +02:00
Marc-André Bombeck 891c85a82b chore(demo): seed readiness and health-score history and a start for magnesium
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.
2026-10-09 18:30:29 +02:00
Marc-André Bombeck be51db56bb docs(release): add the v1.42.0 release images
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.
2026-10-09 18:30:29 +02:00
Marc-André Bombeck 1199e009b2 chore(demo): carry ragweed well into October in the seeded pollen 2026-10-09 18:30:29 +02:00
Marc-André Bombeck 145d138c66 chore(demo): seed a multi-year history for the timeline and day view
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.
2026-10-09 18:30:29 +02:00
Marc-André Bombeck 96ee0c7821 test(dashboard): assert the top card switch on its region, not on the day's digest 2026-10-09 18:23:48 +02:00
Marc-André Bombeck 25d0839296 Merge the right-side panel strips, lazy day panel and calendar, and the layout write lock into release/v1.42.0 2026-10-09 18:22:48 +02:00
Marc-André Bombeck d8fb922228 feat(shell): give each docked panel a fixed strip at the right edge
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.
2026-10-09 18:22:11 +02:00
Marc-André Bombeck 41a7f4daea perf(ui): load the date field's calendar when it first opens
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.
2026-10-09 18:22:10 +02:00
Marc-André Bombeck 86fa57d8bf fix(dashboard): queue concurrent layout writes behind a row lock
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.
2026-10-09 17:42:00 +02:00
Marc-André Bombeck 09f84ef418 test(coach): give the live scenario runner room on a loaded machine 2026-10-09 17:33:19 +02:00
Marc-André Bombeck 018841e6d6 Merge the final display polish into release/v1.42.0 2026-10-09 17:25:50 +02:00
Marc-André Bombeck dcfec1f35d Merge the final display polish into release/v1.42.0 2026-10-09 17:25:35 +02:00
Marc-André Bombeck ac95c9c7dd fix(day): Back on the same page still closes the day
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.
2026-10-09 17:16:10 +02:00
Marc-André Bombeck fbcf099718 test(analytics): expect the latest pulse as its latest day's value
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.
2026-10-09 17:09:42 +02:00
Marc-André Bombeck 3a5953bda2 Merge the score history, score ranges and top card toggle into release/v1.42.0 2026-10-09 17:02:56 +02:00
Marc-André Bombeck b94101d0fe chore(i18n): drop the keys of the removed count line, usual label and open-day link
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.
2026-10-09 16:57:08 +02:00
Marc-André Bombeck 67d8657336 Merge keyboard shortcuts and the command palette into release/v1.42.0 2026-10-09 16:53:21 +02:00
Marc-André Bombeck 668f568289 feat(dashboard): let the top card be switched off
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.
2026-10-09 16:52:39 +02:00
Marc-André Bombeck ebad3665fd perf(shell): load the keyboard shortcut list on first open 2026-10-09 16:52:34 +02:00
Marc-André Bombeck 8d1283a32a style: format the separator guard 2026-10-09 16:52:30 +02:00
Marc-André Bombeck 62140a4aee feat(insights): follow every score over time with one range selector
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.
2026-10-09 16:45:39 +02:00
Marc-André Bombeck 27715c06e0 fix(timeline): a name right of its own bar, one band per pair of rows, one line per notable
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.
2026-10-09 16:45:23 +02:00
Marc-André Bombeck b274ee51ad test(timeline): pin the medication rows, the open day, the point card and the page height
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.
2026-10-09 16:41:44 +02:00
Marc-André Bombeck b1a0598c9d fix(timeline): a point reads on its own, the page ends where its content does, an open day stays open
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.
2026-10-09 16:37:14 +02:00
Marc-André Bombeck 715b733164 feat(shell): add a command palette for pages, settings, modules and actions
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.
2026-10-09 16:35:21 +02:00
Marc-André Bombeck df67e285de fix(timeline): names that fit, links to each value's page, a calmer selection bar
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.
2026-10-09 16:25:48 +02:00
Marc-André Bombeck cd72317356 fix(day): one entry per medication, one meaning for "Pulse", a quieter panel
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.
2026-10-09 16:16:42 +02:00