mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
chore(release): v1.28.55 — the Today view comes to life
Bundles three daily-loop slices onto the Today view: - Coach check-in: a due plan review surfaces as a calm Today card with keep/adjust/let-go, wired to the existing plan lifecycle; silence never changes a plan. - Morning freshness: the sleep-dependent parts of the digest and score refresh the moment last night's sleep arrives (a debounced, timezone-correct per-user job across the Withings, WHOOP and Apple transports), with an honest provisional state until then. Adds migration 0244 (a per-user refresh marker). - Daily push: an opt-in, once-a-day morning notification carrying the day's read, off by default, riding the existing dispatch cascade — a calm note, never an alert. All read the overnight digest; nothing generates on open.
This commit is contained in:
1 parent
04cb80da6a
commit
e272453327
5 files changed
+48
-35
No files matched your search
+19
-1
@@ -2,7 +2,25 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.28.54] — 2026-07-16 — One hero on the dashboard
|
||||
## [1.28.55] — 2026-07-16 — The Today view comes to life
|
||||
|
||||
Three pieces turn the Today view into a daily loop:
|
||||
|
||||
- **A coach check-in.** When a coach plan is due for a look-back, it surfaces as
|
||||
a calm card in Today with keep, adjust, or let-go — one tap. Letting go retires
|
||||
the plan quietly; silence never changes it.
|
||||
- **A morning that reflects last night.** The score and read are prepared
|
||||
overnight, but last night's sleep usually hasn't synced by then. Now, the
|
||||
moment it arrives, the sleep-dependent parts refresh on their own — so by the
|
||||
time you look, the day is up to date. Until sleep is in, Today says so plainly
|
||||
instead of showing a stale score.
|
||||
- **An optional daily nudge.** A once-a-day morning notification with the day's
|
||||
read, off by default and opt-in per channel under Notifications. It carries the
|
||||
refreshed digest when sleep is in, never repeats within a day, and is a calm
|
||||
note — never an alert.
|
||||
|
||||
All three read the digest prepared overnight; nothing is generated when you open
|
||||
the app.
|
||||
|
||||
With the Today view now leading the dashboard, the older opt-in hero card it
|
||||
replaced is retired, so the two never stack. The greeting stays, the tiles and
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: HealthLog API
|
||||
version: 1.28.54
|
||||
version: 1.28.55
|
||||
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.28.54",
|
||||
"version": "1.28.55",
|
||||
"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.28.54";
|
||||
/* @sw-version-fallback */ "v1.28.55";
|
||||
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/*`
|
||||
|
||||
@@ -6,7 +6,10 @@ import type { GenerateOutcome } from "@/lib/insights/comprehensive-generate";
|
||||
const LOCAL_DATE = "2026-07-16";
|
||||
|
||||
function makePrisma(
|
||||
user: { locale: string | null; morningDigestRefreshedOn: string | null } | null,
|
||||
user: {
|
||||
locale: string | null;
|
||||
morningDigestRefreshedOn: string | null;
|
||||
} | null,
|
||||
) {
|
||||
const updateMock = vi.fn(async () => ({}));
|
||||
const prisma = {
|
||||
@@ -30,12 +33,10 @@ describe("runMorningDigestRefresh", () => {
|
||||
locale: "en",
|
||||
morningDigestRefreshedOn: null,
|
||||
});
|
||||
const generate = vi.fn(
|
||||
async (): Promise<GenerateOutcome> => ({
|
||||
status: "generated",
|
||||
providerType: "mock",
|
||||
}),
|
||||
);
|
||||
const generate = vi.fn(async (): Promise<GenerateOutcome> => ({
|
||||
status: "generated",
|
||||
providerType: "mock",
|
||||
}));
|
||||
|
||||
const result = await runMorningDigestRefresh(
|
||||
prisma as never,
|
||||
@@ -57,9 +58,9 @@ describe("runMorningDigestRefresh", () => {
|
||||
locale: "de",
|
||||
morningDigestRefreshedOn: null,
|
||||
});
|
||||
const generate = vi.fn(
|
||||
async (): Promise<GenerateOutcome> => ({ status: "unchanged" }),
|
||||
);
|
||||
const generate = vi.fn(async (): Promise<GenerateOutcome> => ({
|
||||
status: "unchanged",
|
||||
}));
|
||||
|
||||
const result = await runMorningDigestRefresh(
|
||||
prisma as never,
|
||||
@@ -77,12 +78,10 @@ describe("runMorningDigestRefresh", () => {
|
||||
locale: "en",
|
||||
morningDigestRefreshedOn: null,
|
||||
});
|
||||
const generate = vi.fn(
|
||||
async (): Promise<GenerateOutcome> => ({
|
||||
status: "skipped",
|
||||
reason: "no-provider",
|
||||
}),
|
||||
);
|
||||
const generate = vi.fn(async (): Promise<GenerateOutcome> => ({
|
||||
status: "skipped",
|
||||
reason: "no-provider",
|
||||
}));
|
||||
|
||||
const result = await runMorningDigestRefresh(
|
||||
prisma as never,
|
||||
@@ -99,12 +98,10 @@ describe("runMorningDigestRefresh", () => {
|
||||
locale: "en",
|
||||
morningDigestRefreshedOn: LOCAL_DATE,
|
||||
});
|
||||
const generate = vi.fn(
|
||||
async (): Promise<GenerateOutcome> => ({
|
||||
status: "generated",
|
||||
providerType: "mock",
|
||||
}),
|
||||
);
|
||||
const generate = vi.fn(async (): Promise<GenerateOutcome> => ({
|
||||
status: "generated",
|
||||
providerType: "mock",
|
||||
}));
|
||||
|
||||
const result = await runMorningDigestRefresh(
|
||||
prisma as never,
|
||||
@@ -122,12 +119,10 @@ describe("runMorningDigestRefresh", () => {
|
||||
locale: "en",
|
||||
morningDigestRefreshedOn: null,
|
||||
});
|
||||
const generate = vi.fn(
|
||||
async (): Promise<GenerateOutcome> => ({
|
||||
status: "failed",
|
||||
reason: "provider-timeout",
|
||||
}),
|
||||
);
|
||||
const generate = vi.fn(async (): Promise<GenerateOutcome> => ({
|
||||
status: "failed",
|
||||
reason: "provider-timeout",
|
||||
}));
|
||||
|
||||
const result = await runMorningDigestRefresh(
|
||||
prisma as never,
|
||||
@@ -142,9 +137,9 @@ describe("runMorningDigestRefresh", () => {
|
||||
|
||||
it("reports missing-user when the row is gone", async () => {
|
||||
const { prisma, updateMock } = makePrisma(null);
|
||||
const generate = vi.fn(
|
||||
async (): Promise<GenerateOutcome> => ({ status: "unchanged" }),
|
||||
);
|
||||
const generate = vi.fn(async (): Promise<GenerateOutcome> => ({
|
||||
status: "unchanged",
|
||||
}));
|
||||
|
||||
const result = await runMorningDigestRefresh(
|
||||
prisma as never,
|
||||
|
||||
Reference in new issue
Block a user