chore(release): v1.31.0 — the record that reacts

This commit is contained in:
Marc-André Bombeck committed 2026-07-19 17:04:40 +02:00
1 parent 29673c1155
commit d40da09558
4 files changed
+48 -6

No files matched your search

+44
View File
@@ -2,6 +2,50 @@
## [Unreleased]
## [1.31.0] — "The record that reacts" — 2026-07-19
A milestone release: the record now visibly responds when something lands,
instead of waiting for a daily rollup to notice.
- **Today shows what just came in.** A completed night's sleep, a workout, a
first weigh-in of the day — the home screen now carries a calm "just in"
note for a few hours after each one, and at most one written line a day per
kind, grounded in your own numbers. Nothing changes if you run without an
AI provider: the note is the garnish, not the feature. The underlying
change that makes this possible without new polling or a flood of
background work: every write path that can produce a genuinely new reading
now emits a single typed event, day-scoped and de-duplicated, that a mass
import or a provider re-sync produces none of — a ten-year import or a
month of catch-up sync stays silent, by construction, not by luck.
- **A finished workout gets its own paragraph.** Opening a recent session now
shows a short, grounded read of that one activity — duration, effort,
where it sits against your own recent sessions for the sport — with no
training advice and no target zones. It is generated once, the first time
you open a fresh session, never on re-sync and never for older workouts
already in your record.
- **"Ask why" reaches every read-only card.** Every per-metric status card,
every derived-score page, and a finished workout now carries a way straight
into the coach, pre-loaded with just that card's own data so the
conversation starts already grounded — not with a blank prompt.
- **The lab-marker card and the offline metric texts speak with the same
voice as everything else** — meaning first, in your own language across
all six locales, checked by an automated tone pass that now runs on every
change to these surfaces so this kind of drift gets caught before it ships
again.
- **The dashboard layout stops discarding a comparison choice on save,** and
four clinical widgets stop disappearing from a saved layout — both from a
save made by a client that does not know those particular fields yet.
- **Mood and BMI reach the app's home screen**, and a medication pen can now
carry the maker and printed strength that show up in the native pen list.
- **Every new AI call this release can cause is metered and bounded**: a
daily cap per kind, a duplicate-content check that makes a re-post free,
and a token reservation that is always reconciled, including on every
failure path — the same discipline the rest of the AI surface already
holds to.
One migration this release (0257); an additional dashboard-widget migration
carried over from the prior line. No breaking changes.
## [1.30.35] — 2026-07-19
- **The metric texts you see without an AI provider now speak your
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: HealthLog API
version: 1.30.35
version: 1.31.0
description: >-
Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
@@ -25,9 +25,7 @@
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
const sendMock = vi.fn(
async (_queue: string, _payload: unknown, _opts: unknown) => "job-id",
);
const sendMock = vi.fn(async () => "job-id");
const annotateMock = vi.fn();
let moduleMap: Record<string, boolean> = {};
let timezone = "Europe/Berlin";
@@ -35,8 +35,8 @@ vi.mock("@/lib/jobs/workout-insight-generate-shared", () => ({
enqueueWorkoutInsight: vi.fn(async () => ({ enqueued: true })),
}));
const createMany = vi.fn(async (_args: unknown) => ({ count: 1 }));
const updateMany = vi.fn(async (_args: unknown) => ({ count: 1 }));
const createMany = vi.fn(async () => ({ count: 1 }));
const updateMany = vi.fn(async () => ({ count: 1 }));
const fakePrisma = { arrivalReaction: { createMany, updateMany } };
const { runDataArrival } = await import("../data-arrival");