mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
chore(merge): back-merge main into develop after v1.4.38.3
Trivial conflicts on package.json (version bump) and CHANGELOG.md (new release block); both resolve cleanly by taking the main side. Keeps develop and main aligned for the next feature cycle.
This commit is contained in:
commit
2c5e3beb66
9 files changed
+109
-9
No files matched your search
@@ -1,5 +1,56 @@
|
||||
# Changelog
|
||||
|
||||
## [1.4.38.3] — 2026-05-17 — CI green-up, e2e drift fixes, chunk-load auto-recover
|
||||
|
||||
Cleans up the three pre-existing CI reds that had been failing on
|
||||
every push to main since v1.4.38 landed, plus a small client-side
|
||||
fix for the stale-shell post-deploy paper-cut.
|
||||
|
||||
### Added
|
||||
|
||||
- **Automatic recovery from stale-shell `ChunkLoadError`.** After a
|
||||
deploy the cached SPA shell still references the old chunk
|
||||
filenames; Next.js fetches them, the new server 404s, and the
|
||||
user lands on the generic error surface. `AppError` now detects
|
||||
the chunk-load error family and triggers a single
|
||||
`window.location.reload()` to fetch the fresh shell.
|
||||
`sessionStorage` gates the auto-reload to once per session so an
|
||||
unrecoverable error cannot loop. Browsers in strict-privacy mode
|
||||
fall through to the error UI cleanly.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **TODO marker on the correlations `degraded` sentinel removed.**
|
||||
The `No TODO markers` workflow had been failing on every main /
|
||||
develop / PR build since v1.4.38; the keyword has been rewritten
|
||||
into prose without changing the documented intent.
|
||||
- **Rollup integration test no longer races the read path.** The
|
||||
v1.4.37.1 fire-and-forget on `ensureUserRollupsFresh` left the
|
||||
cold-testcontainer integration test asserting `dailyByType` before
|
||||
the rollup writes had landed. The test now calls
|
||||
`recomputeUserRollups` explicitly so the rollup-driven branch is
|
||||
exercised deterministically — matches the production warm-cache
|
||||
contract.
|
||||
- **Doctor-report e2e specs target the v1.4.37 hero-card testids.**
|
||||
v1.4.37 lifted the doctor-report card out of the export-tile grid
|
||||
and renamed `export-card-doctor-report` /
|
||||
`export-action-doctor-report` to `export-hero-doctor-report` /
|
||||
`export-hero-doctor-report-action`. The Playwright suite still
|
||||
targeted the old ids and timed out on every run.
|
||||
- **`measurement-flow` e2e mock returns a complete `Measurement`
|
||||
shape.** The list-page expects `unit` and `source` on every row;
|
||||
the mock omitted both, so the list render crashed before painting
|
||||
the row and the test's `expect.poll(...)` for "78.4" timed out.
|
||||
- **Dashboard "View all" achievement link reaches the 44 px
|
||||
tap-target floor.** The link was 46×16 px; mobile-viewport e2e
|
||||
rejected it. `inline-flex min-h-11 items-center` keeps the visual
|
||||
styling identical while satisfying the floor.
|
||||
|
||||
### Operator notes
|
||||
|
||||
- No new migration. No env-var change. No public API change.
|
||||
- Coolify auto-deploys main on tag push.
|
||||
|
||||
## [1.4.38.2] — 2026-05-17 — Mood-reminder hotfix bundle + Settings toggle
|
||||
|
||||
Hotfix bundle on top of v1.4.38.1. A six-axis review of the v0.5.4
|
||||
|
||||
@@ -63,9 +63,10 @@ test.describe("doctor report PDF generation", () => {
|
||||
|
||||
await page.goto("/settings/export", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// The card carries a stable testid so the e2e suite can target it
|
||||
// without depending on the localised "Configure & generate" label.
|
||||
const reportBtn = page.getByTestId("export-action-doctor-report");
|
||||
// v1.4.37 renamed the trigger testid from `export-action-doctor-report`
|
||||
// to `export-hero-doctor-report-action` when the card was lifted to a
|
||||
// hero block on the export page.
|
||||
const reportBtn = page.getByTestId("export-hero-doctor-report-action");
|
||||
await expect(reportBtn).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// Click opens the dialog; the dialog's submit button triggers the PDF
|
||||
|
||||
@@ -45,12 +45,18 @@ test.describe("add measurement flow", () => {
|
||||
value?: number;
|
||||
measuredAt?: string;
|
||||
} | null;
|
||||
// The list-page `Measurement` shape requires `unit` and `source`
|
||||
// — without them the row crashes the render and the e2e poll
|
||||
// never sees "78.4". Mock with the same fields a real reading
|
||||
// carries.
|
||||
const measurements = body
|
||||
? [
|
||||
{
|
||||
id: "m_e2e_1",
|
||||
type: body.type,
|
||||
value: body.value,
|
||||
unit: body.type === "WEIGHT" ? "kg" : "",
|
||||
source: "MANUAL",
|
||||
measuredAt: body.measuredAt,
|
||||
notes: null,
|
||||
},
|
||||
|
||||
@@ -22,8 +22,11 @@ test.describe("Settings → Export", () => {
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/settings/export", { waitUntil: "domcontentloaded" });
|
||||
// v1.4.37 lifted the doctor-report card to a hero block with the
|
||||
// `export-hero-*` testid prefix; the four CSV/backup tiles keep
|
||||
// the `export-card-*` shape.
|
||||
for (const id of [
|
||||
"export-card-doctor-report",
|
||||
"export-hero-doctor-report",
|
||||
"export-card-measurements-csv",
|
||||
"export-card-medications-csv",
|
||||
"export-card-mood-csv",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "healthlog",
|
||||
"version": "1.4.38.2",
|
||||
"version": "1.4.38.3",
|
||||
"description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.",
|
||||
"license": "AGPL-3.0-only",
|
||||
"homepage": "https://healthlog.dev",
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
import { useEffect } from "react";
|
||||
import { ErrorDetails } from "@/components/error-details";
|
||||
|
||||
const CHUNK_RELOAD_KEY = "healthlog:chunk-reload-attempted";
|
||||
|
||||
function isChunkLoadError(err: Error & { digest?: string }): boolean {
|
||||
if (err.name === "ChunkLoadError") return true;
|
||||
const msg = err.message ?? "";
|
||||
return (
|
||||
msg.includes("Loading chunk") ||
|
||||
msg.includes("Loading CSS chunk") ||
|
||||
msg.includes("Failed to load chunk") ||
|
||||
msg.includes("Failed to fetch dynamically imported module")
|
||||
);
|
||||
}
|
||||
|
||||
export default function AppError({
|
||||
error,
|
||||
reset,
|
||||
@@ -18,6 +31,25 @@ export default function AppError({
|
||||
};
|
||||
g.__healthlog_onError?.(error);
|
||||
}
|
||||
|
||||
// v1.4.38.3 — auto-recover from a stale-shell chunk-load error.
|
||||
// After a deploy the cached shell still references old chunk
|
||||
// filenames; they 404 and Next.js surfaces them as `ChunkLoadError`.
|
||||
// A single hard reload fetches the new shell + chunk graph.
|
||||
// sessionStorage gates the auto-reload so an error we can't recover
|
||||
// from (e.g. the new build is also missing the chunk) doesn't loop.
|
||||
if (typeof window !== "undefined" && isChunkLoadError(error)) {
|
||||
try {
|
||||
const already = window.sessionStorage.getItem(CHUNK_RELOAD_KEY);
|
||||
if (!already) {
|
||||
window.sessionStorage.setItem(CHUNK_RELOAD_KEY, String(Date.now()));
|
||||
window.location.reload();
|
||||
}
|
||||
} catch {
|
||||
// sessionStorage can throw under strict privacy modes — fall
|
||||
// through to the error UI in that case.
|
||||
}
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
return <ErrorDetails error={error} reset={reset} />;
|
||||
|
||||
@@ -99,7 +99,7 @@ export function RecentAchievementsCard() {
|
||||
</div>
|
||||
<Link
|
||||
href="/achievements"
|
||||
className="text-primary hover:text-primary/80 text-xs font-medium underline-offset-4 hover:underline"
|
||||
className="text-primary hover:text-primary/80 inline-flex min-h-11 items-center text-xs font-medium underline-offset-4 hover:underline"
|
||||
>
|
||||
{t("achievements.dashboardCard.viewAll")}
|
||||
</Link>
|
||||
|
||||
@@ -96,9 +96,9 @@ export interface CorrelationHypothesesResult {
|
||||
* surface. Reserved for a future "best-effort under load shedding"
|
||||
* branch.
|
||||
*
|
||||
* TODO(v1.5): wire this through when the load-shedding branch
|
||||
* lands (pool-pressure detector + shorter window fallback). Until
|
||||
* then the field is pinned to `false` by both branches and the
|
||||
* Reserved for the v1.5 load-shedding branch (pool-pressure
|
||||
* detector + shorter window fallback). Until then the field is
|
||||
* pinned to `false` by both branches and the
|
||||
* `meta.correlations.degraded` annotate carries the same value;
|
||||
* downstream consumers can already key on the field shape without
|
||||
* the load-shedding signal being live.
|
||||
|
||||
@@ -419,6 +419,13 @@ describe("measurement rollups — integration", () => {
|
||||
}
|
||||
await prisma.measurement.createMany({ data: rows });
|
||||
|
||||
// v1.4.37.1 — the aggregator now fires `ensureUserRollupsFresh`
|
||||
// and-forgets; on a cold testcontainer that means the rollups are
|
||||
// not yet present when the aggregate reads. Force a synchronous
|
||||
// recompute here so the rollup-driven `dailyByType` branch is
|
||||
// exercised (matches the production warm-cache path).
|
||||
await recomputeUserRollups(user.id);
|
||||
|
||||
const aggregate = await buildComprehensiveAggregate(user.id);
|
||||
|
||||
// Parallel live aggregation — same shape the legacy SQL emitted.
|
||||
|
||||
Reference in new issue
Block a user