mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
Merge pull request #1136 from MBombeck/release/v1.40.0
chore(release): v1.40.0
This commit is contained in:
8 files changed
+82
-21
No files matched your search
+74
-10
@@ -1,19 +1,83 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
## [1.40.0] — 2026-10-04
|
||||
|
||||
Back up the encryption key and refuse a key that cannot open the data;
|
||||
several courses and custom categories for medications; custom symptoms;
|
||||
document reads in the background; sick notes; lab unit refusal and charts
|
||||
that follow the source priority; blood pressure no longer loses its
|
||||
diastolic value; document previews work again.
|
||||
|
||||
### Added
|
||||
|
||||
- **Encryption key backup step (migration 0365).** Admin step with key id,
|
||||
fingerprint and per-platform guidance (`HEALTHLOG_PLATFORM`), an optional
|
||||
"check my copy" that compares and discards the pasted key, a reminder that
|
||||
returns when the key changes.
|
||||
- **Boot key check.** A canary per key id, probed oldest values first across
|
||||
string and Bytes columns; a proven mismatch answers 503
|
||||
`encryption.key_mismatch` everywhere except `/api/health` and
|
||||
`/api/version`, MCP included, and starts no worker; an unproven result
|
||||
serves, warns and re-checks. `ENCRYPTION_KEY_CHECK=warn` as a last resort.
|
||||
- **Medication courses and custom categories (#1024, #1041, migration
|
||||
0368).** Several courses per medication; compliance, streak, cadence and
|
||||
dose history count every course and never the gaps; existing dated
|
||||
medications become one course each. Own categories as `custom:<id>` with
|
||||
`categoryLabel`; the runtime category table is now in the schema.
|
||||
- **Custom symptoms (#981, migration 0369).** Up to eight definitions, events
|
||||
with a 0 to 10 intensity, a capture kind, one correlation channel per
|
||||
symptom, a Coach block; gated on the Illness module.
|
||||
- **Document AI as background runs (#1090, migrations 0366 and 0370).**
|
||||
Index, summary, suggestions and extraction accept `Prefer: respond-async`;
|
||||
the lab scan always runs in the background; `GET /api/ai-runs/{id}`.
|
||||
- **`SICK_NOTE` document kind.**
|
||||
- **Natural durations (#1101)** in the sleep field.
|
||||
- **Reasoning effort per provider (#1126).** The Local and OpenAI-compatible
|
||||
forms gain a Reasoning select (Default, Off, Low, Medium, High), stored on
|
||||
the provider's chain entry; Default sends nothing, Off sends
|
||||
`reasoning_effort: "none"`. A rejection of that field no longer switches
|
||||
the endpoint's JSON mode off.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Blood pressure saved without its diastolic value.** The iPhone app (up to
|
||||
1.0.3, and TestFlight 1.1.0) sends the two halves of a manual reading as two
|
||||
- **Blood pressure saved without its diastolic value (iOS #15, migration
|
||||
0371).** The iPhone app sends the two halves of a manual reading as two
|
||||
requests under one `Idempotency-Key`, and the server answered the second
|
||||
with the first one's cached response instead of saving it. The replay cache
|
||||
now stores a SHA-256 fingerprint of the request body (migration 0371,
|
||||
`idempotency_keys.request_fingerprint`) and replays only the same body; a
|
||||
different body under the same key is saved normally. Readings saved before
|
||||
this fix stay incomplete: delete the incomplete reading and enter it again
|
||||
with both values and the original time. Operators can list affected
|
||||
readings with the query in `docs/ops/blood-pressure-missing-diastolic.md`.
|
||||
with the first one's cached response. The replay cache now stores a SHA-256
|
||||
fingerprint of method, path and body (JSON canonicalised, a multipart
|
||||
boundary normalised) and replays only the same request; a different body
|
||||
under the same key is saved. Readings saved before this stay incomplete;
|
||||
`docs/ops/blood-pressure-missing-diastolic.md` lists them.
|
||||
- **Document previews and scanned PDFs (#1124).** The image hoisted an older
|
||||
`@napi-rs/canvas` binary next to the pinned loader, so every thumbnail
|
||||
failed; the build now hoists the pinned version and fails on a mismatch.
|
||||
PDFs render with pdfjs font, cmap and wasm data and a registered fallback
|
||||
font instead of absent system fonts, and an all-blank render reports
|
||||
`raster-failed`. The thumbnail job logs why a preview is missing.
|
||||
- **Portable backups.** Cycle free text, custom mood labels, visit notes
|
||||
and vaccination notes are carried readable and re-encrypted on restore
|
||||
instead of as the source server's ciphertext; custom mood tags restore
|
||||
without mood entries; a round-trip guard fails on any ciphertext in a
|
||||
portable file. A medication update and its course share one transaction.
|
||||
- **Lab unit mismatch (#1094).** A reading in another unit than its marker is
|
||||
refused (`labs.unit.mismatch`) on every write path; a marker's unit is
|
||||
locked while it has readings (`biomarkers.unit.locked`).
|
||||
- **Source priority in series (#1108).** Pulse, HRV and SpO2 series read the
|
||||
ladder's source per day; unranked types keep every source.
|
||||
- **Sign-in over plain http (#1097).** Refused with
|
||||
`auth.session.insecure_transport` before a password, code or challenge is
|
||||
used; the sign-in page says so first.
|
||||
- **Chart window (#1102).** Ends at the end of today in the profile time zone.
|
||||
- **Workouts.** The insight cap survives a delete (migration 0367); a changed
|
||||
`manual:` resubmit updates the workout and its paragraph.
|
||||
- **Display.** Clinician decimals in the owner's locale, stated dates east of
|
||||
UTC+12, archive month grouping, lab list columns without a range, icon
|
||||
picker touch targets and labels, symptom entry load errors.
|
||||
|
||||
### Changed
|
||||
|
||||
- Quick-entry forms load on demand; the four eager route baselines are
|
||||
lowered to the new measurements.
|
||||
|
||||
## [1.39.9] — 2026-10-03
|
||||
|
||||
|
||||
@@ -129,9 +129,9 @@ Actively developed — new releases roughly weekly, issue reports and PRs welcom
|
||||
|
||||
HealthLog started as something I built for myself. That it has turned into a project other people use, care about and help shape still surprises me, and I am grateful for every part of it.
|
||||
|
||||
Thank you to everyone who sent code that shipped: [@TimonBed](https://github.com/TimonBed), [@Antiheld86](https://github.com/Antiheld86), [@BernhardBuckel](https://github.com/BernhardBuckel), [@balajiv113](https://github.com/balajiv113), [@aucun6352](https://github.com/aucun6352), [@mathewcsims](https://github.com/mathewcsims) and [@muhdusama](https://github.com/muhdusama). A special thanks to [@BernhardBuckel](https://github.com/BernhardBuckel), who has been working through the lab screens one careful pull request at a time, with tests and a plan before every change.
|
||||
Thank you to everyone who sent code that shipped: [@TimonBed](https://github.com/TimonBed), [@Antiheld86](https://github.com/Antiheld86), [@BernhardBuckel](https://github.com/BernhardBuckel), [@balajiv113](https://github.com/balajiv113), [@aucun6352](https://github.com/aucun6352), [@mathewcsims](https://github.com/mathewcsims) and [@muhdusama](https://github.com/muhdusama).
|
||||
|
||||
Thank you to everyone who opened an issue, asked a question in the discussions or wrote me an email. Most of what got better in the last months started with someone describing what went wrong for them, often with a log, a screenshot and a second try when the first fix was not enough.
|
||||
Thank you to everyone who opened an issue, asked a question in the discussions or wrote me an email, and especially to [@lutzkind](https://github.com/lutzkind), [@HercoGC](https://github.com/HercoGC), [@mills1975](https://github.com/mills1975), [@sreeramachandramurthy](https://github.com/sreeramachandramurthy), [@CritLoren](https://github.com/CritLoren), [@mbreitkreuz](https://github.com/mbreitkreuz), [@k-nacion](https://github.com/k-nacion), [@NebuPookins](https://github.com/NebuPookins), [@Nazza01](https://github.com/Nazza01), [@Cnote43](https://github.com/Cnote43) and [@juliaanvaneijndt](https://github.com/juliaanvaneijndt), whose reports shaped HealthLog again and again. Most of what got better in the last months started with someone describing what went wrong for them, often with a log, a screenshot and a second try when the first fix was not enough.
|
||||
|
||||
And thank you to everyone who supported the project with a coffee or by buying the iPhone app. I don't name anyone here, but I read every message that comes with it. It pays for the servers and the Apple developer account, and it tells me that HealthLog is worth the evenings.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: HealthLog API
|
||||
version: 1.39.9
|
||||
version: 1.40.0
|
||||
description: >-
|
||||
Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "healthlog",
|
||||
"version": "1.39.9",
|
||||
"version": "1.40.0",
|
||||
"description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.",
|
||||
"license": "PolyForm-Noncommercial-1.0.0",
|
||||
"homepage": "https://healthlog.dev",
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ try {
|
||||
// v1.4.38.4 → v1.4.42. Do not hand-edit; bump `package.json` and rebuild.
|
||||
const CACHE_VERSION =
|
||||
(typeof self !== "undefined" && self.__APP_VERSION__) ||
|
||||
/* @sw-version-fallback */ "v1.39.9";
|
||||
/* @sw-version-fallback */ "v1.40.0";
|
||||
const STATIC_CACHE = `healthlog-static-${CACHE_VERSION}`;
|
||||
const PAGE_CACHE = `healthlog-pages-${CACHE_VERSION}`;
|
||||
// v1.18.6 — read-only data cache for a curated allowlist of safe GET `/api/*`
|
||||
|
||||
@@ -116,7 +116,7 @@ export function ManualWorkoutForm({
|
||||
// a submit after an edit updates the row the first submit stored.
|
||||
const [externalId] = useState(newManualWorkoutExternalId);
|
||||
const [draft, setDraft] = useState<ManualWorkoutDraft>(() =>
|
||||
emptyManualWorkoutDraft(new Date(), timezone),
|
||||
emptyManualWorkoutDraft(),
|
||||
);
|
||||
const [errors, setErrors] = useState<
|
||||
Partial<Record<ManualWorkoutField, string>>
|
||||
|
||||
@@ -242,7 +242,7 @@ describe("buildManualWorkoutEntry — validation", () => {
|
||||
describe("the form's defaults", () => {
|
||||
it("opens with a blank start; the field caps at the current minute", () => {
|
||||
expect(wallClockNow(NOW, "Europe/Berlin")).toBe("2026-09-15T14:00");
|
||||
expect(emptyManualWorkoutDraft(NOW, "Asia/Seoul")).toMatchObject({
|
||||
expect(emptyManualWorkoutDraft()).toMatchObject({
|
||||
sportType: "",
|
||||
start: "",
|
||||
hours: "",
|
||||
|
||||
@@ -118,10 +118,7 @@ export function wallClockNow(now: Date, timezone: string): string {
|
||||
return `${p.year}-${pad(p.month)}-${pad(p.day)}T${pad(p.hour)}:${pad(p.minute)}`;
|
||||
}
|
||||
|
||||
export function emptyManualWorkoutDraft(
|
||||
now: Date,
|
||||
timezone: string,
|
||||
): ManualWorkoutDraft {
|
||||
export function emptyManualWorkoutDraft(): ManualWorkoutDraft {
|
||||
return {
|
||||
sportType: "",
|
||||
// Blank on purpose (#1085): a blank start means "it just ended", so the
|
||||
|
||||
Reference in new issue
Block a user