The visit form's lab picker offered the newest 200 results, the condition
picker the newest 100 conditions, the document pickers the newest thousand
documents and the document sheet's visit picker the newest 100 visits.
On a long record everything older could not be linked.
Each picker now opens on the records around the date of the thing being
linked, pages older and newer from there, and searches through the list
route itself. Records already linked always show, from the labels the
record's own read carries and from what was picked in the sheet.
The lab list takes a q filter over analyte and panel, the condition list
from/to/q/offset/sortDir, and the visit list an offset.
MCP `search` and `fetch` link a metric as `/insights?metric=<id>` and a lab
value as `/labs?analyte=<name>`. Neither page read its query, so every link
opened the overview or the full list.
`/insights` now resolves the id server-side, in whichever spelling the
result carries (a Coach source key such as `hrv` or `bp`, a signal or
metric-status key such as `GRIP_STRENGTH`, a measurement type, or a
sub-page slug), and redirects to the metric's page, or to its value list
when no page focuses it. An id it cannot place renders the overview as
before.
`/labs` asks for the newest reading stored under the exact analyte name
and opens its marker's page; a name with no reading keeps the list and
drops the query.
The visit form's lab and episode pickers fetched with their own page
sizes under the same keys as the Labs list and the illness pages, which
fetch with others. Whichever read landed first served the other: the
picker could see 50 episodes instead of 100, or the Labs list a 200-row
page as the whole list. The picker reads now have keys of their own,
under the lab and illness roots so writes still evict them.
The detail page fetched a single limit=500 window and sorted client-side,
silently truncating any marker that crossed 500 readings. Switch to an
offset-paginated useInfiniteQuery: it loads a page at a time, a 'Load more'
control reveals the rest, and the chart and stat strip render the
accumulated set so loading more extends the trend.
Add an opt-in in-browser OCR path so a user whose provider cannot read
images (ChatGPT-OAuth/Codex, a text-only model) can still scan a lab
report. The capability probe gains a `mode` field: `vision` when the
provider reads the image directly, `text` when local OCR is enabled and
any provider is configured, or null. The extract route grows a text
branch that accepts the in-browser-OCR'd text as JSON and structures it
through the existing schema + review/commit flow; the raw image never
reaches the server in text mode. New `labsLocalOcrEnabled` user column
(migration 0184, default off) gates it, with a dedicated PATCH endpoint.
Add the Scan a report entry to the Labs page, gated on the capability probe.
The review dialog uploads a photo or PDF, then shows each proposed reading for
per-row confirm, edit, or discard with duplicate, new-biomarker, and
low-confidence hints in the labs design language. Only confirmed rows commit.
Add the OCR query key and the six-locale strings.
Add a user-scoped Biomarker catalog so a marker is defined once — name,
unit, lower/upper bound, optional encrypted context, panel — and every
later reading just links it. New /api/biomarkers CRUD plus a structured
/api/labs create path: a biomarkerId resolves the unit + reference range
server-side, so the list and detail DTOs carry the resolved values and
the client never recomputes the range. Lab reads include the linked
marker and fall back to the legacy free-text fields for unlinked rows.
Add a lab-result restore endpoint for the delete-Undo affordance, a
shared serialiser, a static common-panel seed list, and tests for
catalog validation, structured-entry resolution, and the context codec.
The annual-blood-panel auto-resolve that findLatestLabResultForAnalytes was
meant to feed never got wired into the Vorsorge reminder, so the export sits
with zero callers and the labAnalyteTrend query-key has no consumer (the
sparkline is prop-fed and there is no labs trend endpoint). Remove both so the
knip gate is clean; the Vorsorge lab read-back can come back with the wiring
that needs it.
Record discrete biomarker readings (HbA1c, LDL, ferritin, TSH, …) with
their value, unit, reference range and sample date. The store pairs with
the annual blood-panel reminder, which until now had nowhere to record
the result it reminds the user to obtain.
The list/create/update/delete endpoints narrow userId from the session,
build the write data field-by-field, soft-delete via deletedAt, and
encrypt the optional note at rest. A neutral three-state reference-range
classifier (in-range / below / above / unknown) is computed
server-side so every surface renders the same calm verdict.
findLatestLabResultForAnalytes exposes the read point the blood-panel
reminder consults to mark itself satisfied, without coupling the two.