mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
chore(release): v1.28.0 — document intelligence
Marks the document line as one coherent milestone: the vault becomes a place to understand files, not just store them — read, indexed, searched, shared, and answered — with a local-first / consented AI posture and a vendor-blind egress notice held at every step. Ships alongside a full correctness, resilience, and quality sweep down to the low-priority findings.
This commit is contained in:
1 parent
1c7ab080f0
commit
b143576627
7 files changed
+490
-3
No files matched your search
@@ -0,0 +1,150 @@
|
||||
// Hero spacing screenshot harness (WQHD) — uncommitted, repo root for module resolution.
|
||||
import { chromium } from "@playwright/test";
|
||||
import {
|
||||
buildMockSnapshot,
|
||||
LONG_HEADLINE_BRIEFING,
|
||||
MOCK_SCORE_RINGS,
|
||||
} from "./e2e/utils/mock-dashboard-snapshot.ts";
|
||||
|
||||
const OUT =
|
||||
process.env.OUT_DIR ??
|
||||
"/private/tmp/claude-501/-Users-marc-Projects-HealthLog/78b12842-5404-4de0-9a00-4fdac6a4a7ed/scratchpad/hero";
|
||||
const TAG = process.env.TAG ?? "before";
|
||||
|
||||
import { existsSync } from "node:fs";
|
||||
const STATE = `${OUT}/state.json`;
|
||||
const browser = await chromium.launch();
|
||||
const ctxOpts = {
|
||||
viewport: { width: 360, height: 780 },
|
||||
deviceScaleFactor: 1,
|
||||
};
|
||||
let ctx;
|
||||
if (existsSync(STATE)) {
|
||||
ctx = await browser.newContext({ ...ctxOpts, storageState: STATE });
|
||||
} else {
|
||||
ctx = await browser.newContext(ctxOpts);
|
||||
const login = await ctx.request.post("http://localhost:3000/api/auth/login", {
|
||||
data: { email: "e2e@healthlog.test", password: "ZJ4hN8x!Pq3vMr2C" },
|
||||
});
|
||||
if (login.status() !== 200)
|
||||
throw new Error(`login failed: ${login.status()} ${await login.text()}`);
|
||||
await ctx.storageState({ path: STATE });
|
||||
}
|
||||
const page = await ctx.newPage();
|
||||
|
||||
const snapshot = buildMockSnapshot({
|
||||
heroVisible: true,
|
||||
briefing: LONG_HEADLINE_BRIEFING,
|
||||
scoreRings: MOCK_SCORE_RINGS,
|
||||
});
|
||||
snapshot.user.username = "Marc";
|
||||
snapshot.healthScore = { score: 78, band: "green", components: [] };
|
||||
|
||||
await page.route(/\/api\/dashboard\/snapshot(\?|$)/, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ data: snapshot, error: null }),
|
||||
}),
|
||||
);
|
||||
|
||||
page.on("response", (r) => {
|
||||
if (r.url().includes("dashboard/snapshot"))
|
||||
console.log("SNAPSHOT RESPONSE:", r.status(), r.url());
|
||||
});
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page
|
||||
.waitForSelector('[data-verdict-variant], [data-slot="dashboard-hero"]', {
|
||||
timeout: 20000,
|
||||
})
|
||||
.catch(() => {});
|
||||
await page.waitForTimeout(1500);
|
||||
console.log(
|
||||
"BODY SLOTS:",
|
||||
await page.evaluate(() =>
|
||||
Array.from(document.querySelectorAll("[data-slot]"))
|
||||
.slice(0, 12)
|
||||
.map((e) => e.getAttribute("data-slot")),
|
||||
),
|
||||
);
|
||||
console.log("URL:", page.url());
|
||||
|
||||
const hero = page.locator('[data-slot="dashboard-hero"]').first();
|
||||
const heroBox = await hero.boundingBox().catch(() => null);
|
||||
await page.screenshot({ path: `${OUT}/${TAG}-full-360.png` });
|
||||
if (heroBox) {
|
||||
await page.screenshot({
|
||||
path: `${OUT}/${TAG}-hero-360.png`,
|
||||
clip: {
|
||||
x: Math.max(0, heroBox.x - 8),
|
||||
y: Math.max(0, heroBox.y - 8),
|
||||
width: Math.min(360, heroBox.width + 16),
|
||||
height: heroBox.height + 16,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Geometry: paddings of the hero card + ring row gaps + greeting offset
|
||||
const geo = await page.evaluate(() => {
|
||||
const hero = document.querySelector('[data-slot="dashboard-hero"]');
|
||||
if (!hero) return { error: "no hero" };
|
||||
const hb = hero.getBoundingClientRect();
|
||||
const cs = getComputedStyle(hero);
|
||||
const first = hero.firstElementChild?.getBoundingClientRect();
|
||||
const kids = Array.from(hero.querySelectorAll("*"));
|
||||
// ring row: element containing multiple svg rings
|
||||
const rings = kids
|
||||
.filter((el) => el.querySelectorAll?.("svg circle").length >= 2)
|
||||
.pop();
|
||||
const ringBox = rings?.getBoundingClientRect();
|
||||
const ringKids = rings
|
||||
? Array.from(rings.children).map((c) => c.getBoundingClientRect())
|
||||
: [];
|
||||
const gaps = [];
|
||||
for (let i = 1; i < ringKids.length; i++)
|
||||
gaps.push(
|
||||
Math.round(ringKids[i].x - (ringKids[i - 1].x + ringKids[i - 1].width)),
|
||||
);
|
||||
const greeting = kids.find(
|
||||
(el) =>
|
||||
/Willkommen|willkommen|Hallo/.test(el.textContent || "") &&
|
||||
el.children.length === 0,
|
||||
);
|
||||
const gB = greeting?.getBoundingClientRect();
|
||||
// lowest content bottom inside hero
|
||||
let maxBottom = 0;
|
||||
kids.forEach((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.height > 0 && r.bottom > maxBottom && r.bottom <= hb.bottom + 1)
|
||||
maxBottom = r.bottom;
|
||||
});
|
||||
let minTop = Infinity;
|
||||
kids.forEach((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.height > 0 && r.top < minTop && r.top >= hb.top - 1) minTop = r.top;
|
||||
});
|
||||
return {
|
||||
heroPadding: {
|
||||
t: cs.paddingTop,
|
||||
r: cs.paddingRight,
|
||||
b: cs.paddingBottom,
|
||||
l: cs.paddingLeft,
|
||||
},
|
||||
contentInsets: {
|
||||
top: Math.round(minTop - hb.top),
|
||||
bottom: Math.round(hb.bottom - maxBottom),
|
||||
},
|
||||
ringGaps: gaps,
|
||||
ringRow: ringBox
|
||||
? {
|
||||
top: Math.round(ringBox.top - hb.top),
|
||||
right: Math.round(hb.right - ringBox.right),
|
||||
}
|
||||
: null,
|
||||
greetingTopInHero: gB ? Math.round(gB.top - hb.top) : null,
|
||||
heroSize: { w: Math.round(hb.width), h: Math.round(hb.height) },
|
||||
};
|
||||
});
|
||||
console.log(JSON.stringify(geo, null, 1));
|
||||
|
||||
await browser.close();
|
||||
@@ -0,0 +1,150 @@
|
||||
// Hero spacing screenshot harness (WQHD) — uncommitted, repo root for module resolution.
|
||||
import { chromium } from "@playwright/test";
|
||||
import {
|
||||
buildMockSnapshot,
|
||||
LONG_HEADLINE_BRIEFING,
|
||||
MOCK_SCORE_RINGS,
|
||||
} from "./e2e/utils/mock-dashboard-snapshot.ts";
|
||||
|
||||
const OUT =
|
||||
process.env.OUT_DIR ??
|
||||
"/private/tmp/claude-501/-Users-marc-Projects-HealthLog/78b12842-5404-4de0-9a00-4fdac6a4a7ed/scratchpad/hero";
|
||||
const TAG = process.env.TAG ?? "before";
|
||||
|
||||
import { existsSync } from "node:fs";
|
||||
const STATE = `${OUT}/state.json`;
|
||||
const browser = await chromium.launch();
|
||||
const ctxOpts = {
|
||||
viewport: { width: 390, height: 844 },
|
||||
deviceScaleFactor: 1,
|
||||
};
|
||||
let ctx;
|
||||
if (existsSync(STATE)) {
|
||||
ctx = await browser.newContext({ ...ctxOpts, storageState: STATE });
|
||||
} else {
|
||||
ctx = await browser.newContext(ctxOpts);
|
||||
const login = await ctx.request.post("http://localhost:3000/api/auth/login", {
|
||||
data: { email: "e2e@healthlog.test", password: "ZJ4hN8x!Pq3vMr2C" },
|
||||
});
|
||||
if (login.status() !== 200)
|
||||
throw new Error(`login failed: ${login.status()} ${await login.text()}`);
|
||||
await ctx.storageState({ path: STATE });
|
||||
}
|
||||
const page = await ctx.newPage();
|
||||
|
||||
const snapshot = buildMockSnapshot({
|
||||
heroVisible: true,
|
||||
briefing: LONG_HEADLINE_BRIEFING,
|
||||
scoreRings: MOCK_SCORE_RINGS,
|
||||
});
|
||||
snapshot.user.username = "Marc";
|
||||
snapshot.healthScore = { score: 78, band: "green", components: [] };
|
||||
|
||||
await page.route(/\/api\/dashboard\/snapshot(\?|$)/, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ data: snapshot, error: null }),
|
||||
}),
|
||||
);
|
||||
|
||||
page.on("response", (r) => {
|
||||
if (r.url().includes("dashboard/snapshot"))
|
||||
console.log("SNAPSHOT RESPONSE:", r.status(), r.url());
|
||||
});
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page
|
||||
.waitForSelector('[data-verdict-variant], [data-slot="dashboard-hero"]', {
|
||||
timeout: 20000,
|
||||
})
|
||||
.catch(() => {});
|
||||
await page.waitForTimeout(1500);
|
||||
console.log(
|
||||
"BODY SLOTS:",
|
||||
await page.evaluate(() =>
|
||||
Array.from(document.querySelectorAll("[data-slot]"))
|
||||
.slice(0, 12)
|
||||
.map((e) => e.getAttribute("data-slot")),
|
||||
),
|
||||
);
|
||||
console.log("URL:", page.url());
|
||||
|
||||
const hero = page.locator('[data-slot="dashboard-hero"]').first();
|
||||
const heroBox = await hero.boundingBox().catch(() => null);
|
||||
await page.screenshot({ path: `${OUT}/${TAG}-full-390.png` });
|
||||
if (heroBox) {
|
||||
await page.screenshot({
|
||||
path: `${OUT}/${TAG}-hero-390.png`,
|
||||
clip: {
|
||||
x: Math.max(0, heroBox.x - 8),
|
||||
y: Math.max(0, heroBox.y - 8),
|
||||
width: Math.min(390, heroBox.width + 16),
|
||||
height: heroBox.height + 16,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Geometry: paddings of the hero card + ring row gaps + greeting offset
|
||||
const geo = await page.evaluate(() => {
|
||||
const hero = document.querySelector('[data-slot="dashboard-hero"]');
|
||||
if (!hero) return { error: "no hero" };
|
||||
const hb = hero.getBoundingClientRect();
|
||||
const cs = getComputedStyle(hero);
|
||||
const first = hero.firstElementChild?.getBoundingClientRect();
|
||||
const kids = Array.from(hero.querySelectorAll("*"));
|
||||
// ring row: element containing multiple svg rings
|
||||
const rings = kids
|
||||
.filter((el) => el.querySelectorAll?.("svg circle").length >= 2)
|
||||
.pop();
|
||||
const ringBox = rings?.getBoundingClientRect();
|
||||
const ringKids = rings
|
||||
? Array.from(rings.children).map((c) => c.getBoundingClientRect())
|
||||
: [];
|
||||
const gaps = [];
|
||||
for (let i = 1; i < ringKids.length; i++)
|
||||
gaps.push(
|
||||
Math.round(ringKids[i].x - (ringKids[i - 1].x + ringKids[i - 1].width)),
|
||||
);
|
||||
const greeting = kids.find(
|
||||
(el) =>
|
||||
/Willkommen|willkommen|Hallo/.test(el.textContent || "") &&
|
||||
el.children.length === 0,
|
||||
);
|
||||
const gB = greeting?.getBoundingClientRect();
|
||||
// lowest content bottom inside hero
|
||||
let maxBottom = 0;
|
||||
kids.forEach((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.height > 0 && r.bottom > maxBottom && r.bottom <= hb.bottom + 1)
|
||||
maxBottom = r.bottom;
|
||||
});
|
||||
let minTop = Infinity;
|
||||
kids.forEach((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.height > 0 && r.top < minTop && r.top >= hb.top - 1) minTop = r.top;
|
||||
});
|
||||
return {
|
||||
heroPadding: {
|
||||
t: cs.paddingTop,
|
||||
r: cs.paddingRight,
|
||||
b: cs.paddingBottom,
|
||||
l: cs.paddingLeft,
|
||||
},
|
||||
contentInsets: {
|
||||
top: Math.round(minTop - hb.top),
|
||||
bottom: Math.round(hb.bottom - maxBottom),
|
||||
},
|
||||
ringGaps: gaps,
|
||||
ringRow: ringBox
|
||||
? {
|
||||
top: Math.round(ringBox.top - hb.top),
|
||||
right: Math.round(hb.right - ringBox.right),
|
||||
}
|
||||
: null,
|
||||
greetingTopInHero: gB ? Math.round(gB.top - hb.top) : null,
|
||||
heroSize: { w: Math.round(hb.width), h: Math.round(hb.height) },
|
||||
};
|
||||
});
|
||||
console.log(JSON.stringify(geo, null, 1));
|
||||
|
||||
await browser.close();
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
// Hero spacing screenshot harness (WQHD) — uncommitted, repo root for module resolution.
|
||||
import { chromium } from "@playwright/test";
|
||||
import {
|
||||
buildMockSnapshot,
|
||||
LONG_HEADLINE_BRIEFING,
|
||||
MOCK_SCORE_RINGS,
|
||||
} from "./e2e/utils/mock-dashboard-snapshot.ts";
|
||||
|
||||
const OUT =
|
||||
process.env.OUT_DIR ??
|
||||
"/private/tmp/claude-501/-Users-marc-Projects-HealthLog/78b12842-5404-4de0-9a00-4fdac6a4a7ed/scratchpad/hero";
|
||||
const TAG = process.env.TAG ?? "before";
|
||||
|
||||
import { existsSync } from "node:fs";
|
||||
const STATE = `${OUT}/state.json`;
|
||||
const browser = await chromium.launch();
|
||||
const ctxOpts = {
|
||||
viewport: { width: 2560, height: 1440 },
|
||||
deviceScaleFactor: 1,
|
||||
};
|
||||
let ctx;
|
||||
if (existsSync(STATE)) {
|
||||
ctx = await browser.newContext({ ...ctxOpts, storageState: STATE });
|
||||
} else {
|
||||
ctx = await browser.newContext(ctxOpts);
|
||||
const login = await ctx.request.post("http://localhost:3000/api/auth/login", {
|
||||
data: { email: "e2e@healthlog.test", password: "ZJ4hN8x!Pq3vMr2C" },
|
||||
});
|
||||
if (login.status() !== 200)
|
||||
throw new Error(`login failed: ${login.status()} ${await login.text()}`);
|
||||
await ctx.storageState({ path: STATE });
|
||||
}
|
||||
const page = await ctx.newPage();
|
||||
|
||||
const snapshot = buildMockSnapshot({
|
||||
heroVisible: true,
|
||||
briefing: LONG_HEADLINE_BRIEFING,
|
||||
scoreRings: MOCK_SCORE_RINGS,
|
||||
});
|
||||
snapshot.user.username = "Marc";
|
||||
snapshot.healthScore = { score: 78, band: "green", components: [] };
|
||||
|
||||
await page.route(/\/api\/dashboard\/snapshot(\?|$)/, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ data: snapshot, error: null }),
|
||||
}),
|
||||
);
|
||||
|
||||
page.on("response", (r) => {
|
||||
if (r.url().includes("dashboard/snapshot"))
|
||||
console.log("SNAPSHOT RESPONSE:", r.status(), r.url());
|
||||
});
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page
|
||||
.waitForSelector('[data-verdict-variant], [data-slot="dashboard-hero"]', {
|
||||
timeout: 20000,
|
||||
})
|
||||
.catch(() => {});
|
||||
await page.waitForTimeout(1500);
|
||||
console.log(
|
||||
"BODY SLOTS:",
|
||||
await page.evaluate(() =>
|
||||
Array.from(document.querySelectorAll("[data-slot]"))
|
||||
.slice(0, 12)
|
||||
.map((e) => e.getAttribute("data-slot")),
|
||||
),
|
||||
);
|
||||
console.log("URL:", page.url());
|
||||
|
||||
const hero = page.locator('[data-slot="dashboard-hero"]').first();
|
||||
const heroBox = await hero.boundingBox().catch(() => null);
|
||||
await page.screenshot({ path: `${OUT}/${TAG}-full-2560.png` });
|
||||
if (heroBox) {
|
||||
await page.screenshot({
|
||||
path: `${OUT}/${TAG}-hero-2560.png`,
|
||||
clip: {
|
||||
x: Math.max(0, heroBox.x - 8),
|
||||
y: Math.max(0, heroBox.y - 8),
|
||||
width: Math.min(2560, heroBox.width + 16),
|
||||
height: heroBox.height + 16,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Geometry: paddings of the hero card + ring row gaps + greeting offset
|
||||
const geo = await page.evaluate(() => {
|
||||
const hero = document.querySelector('[data-slot="dashboard-hero"]');
|
||||
if (!hero) return { error: "no hero" };
|
||||
const hb = hero.getBoundingClientRect();
|
||||
const cs = getComputedStyle(hero);
|
||||
const first = hero.firstElementChild?.getBoundingClientRect();
|
||||
const kids = Array.from(hero.querySelectorAll("*"));
|
||||
// ring row: element containing multiple svg rings
|
||||
const rings = kids
|
||||
.filter((el) => el.querySelectorAll?.("svg circle").length >= 2)
|
||||
.pop();
|
||||
const ringBox = rings?.getBoundingClientRect();
|
||||
const ringKids = rings
|
||||
? Array.from(rings.children).map((c) => c.getBoundingClientRect())
|
||||
: [];
|
||||
const gaps = [];
|
||||
for (let i = 1; i < ringKids.length; i++)
|
||||
gaps.push(
|
||||
Math.round(ringKids[i].x - (ringKids[i - 1].x + ringKids[i - 1].width)),
|
||||
);
|
||||
const greeting = kids.find(
|
||||
(el) =>
|
||||
/Willkommen|willkommen|Hallo/.test(el.textContent || "") &&
|
||||
el.children.length === 0,
|
||||
);
|
||||
const gB = greeting?.getBoundingClientRect();
|
||||
// lowest content bottom inside hero
|
||||
let maxBottom = 0;
|
||||
kids.forEach((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.height > 0 && r.bottom > maxBottom && r.bottom <= hb.bottom + 1)
|
||||
maxBottom = r.bottom;
|
||||
});
|
||||
let minTop = Infinity;
|
||||
kids.forEach((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.height > 0 && r.top < minTop && r.top >= hb.top - 1) minTop = r.top;
|
||||
});
|
||||
return {
|
||||
heroPadding: {
|
||||
t: cs.paddingTop,
|
||||
r: cs.paddingRight,
|
||||
b: cs.paddingBottom,
|
||||
l: cs.paddingLeft,
|
||||
},
|
||||
contentInsets: {
|
||||
top: Math.round(minTop - hb.top),
|
||||
bottom: Math.round(hb.bottom - maxBottom),
|
||||
},
|
||||
ringGaps: gaps,
|
||||
ringRow: ringBox
|
||||
? {
|
||||
top: Math.round(ringBox.top - hb.top),
|
||||
right: Math.round(hb.right - ringBox.right),
|
||||
}
|
||||
: null,
|
||||
greetingTopInHero: gB ? Math.round(gB.top - hb.top) : null,
|
||||
heroSize: { w: Math.round(hb.width), h: Math.round(hb.height) },
|
||||
};
|
||||
});
|
||||
console.log(JSON.stringify(geo, null, 1));
|
||||
|
||||
await browser.close();
|
||||
@@ -2,6 +2,43 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.28.0] — 2026-07-09 — Document intelligence
|
||||
|
||||
A milestone that closes the document line end to end. The vault stopped being a
|
||||
place to _store_ files and became a place to _understand_ them — read,
|
||||
searched, shared, and answered — with the privacy posture held at every step.
|
||||
The document capabilities shipped incrementally across v1.27.17–v1.27.33; this
|
||||
release marks them, together with a full correctness/resilience/quality pass, as
|
||||
one coherent whole.
|
||||
|
||||
### The document intelligence era
|
||||
|
||||
- **Vault** — every letter, report, and scan in one place, encrypted at rest,
|
||||
byte-classified serving (inline vs download), opt-in.
|
||||
- **AI reading + automatic search** — documents are read and indexed
|
||||
automatically on upload; a local reader keeps the file on the machine, an AI
|
||||
provider reads richer (including scans) with consent. Content search matches
|
||||
whole words _inside_ documents over an encrypted blind token index — nothing
|
||||
readable is ever stored.
|
||||
- **Sharing** — hand a clinician a time-boxed, revocable link with a mobile QR
|
||||
code; camera metadata (EXIF/GPS) stripped on the way out.
|
||||
- **Chat about a document** — ask a document questions in plain language,
|
||||
grounded and cited, with a security model that treats the document as
|
||||
untrusted: fenced as data, no tools, no health-record context, numeric-grounded,
|
||||
never a diagnosis.
|
||||
- **AI provider governance** — for documents, a local-first / no-training-API /
|
||||
subscription-last provider order, explicit consent for any external egress,
|
||||
and a vendor-blind notice before a document leaves the machine.
|
||||
|
||||
### Correctness, resilience and polish
|
||||
|
||||
The same line carried a full audit sweep: DST-safe medication windows and
|
||||
user-timezone compliance; graceful degradation (worker timeouts, per-metric sync
|
||||
freshness, offline-write safety, per-source isolation, honest "unreadable"
|
||||
markers); no more dashboard dead-ends and honest error/empty states; accessibility
|
||||
contrast and semantics; and cold-start + performance fixes — down to the low-priority
|
||||
findings.
|
||||
|
||||
## [1.27.33] — 2026-07-09 — Chat about a document
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: HealthLog API
|
||||
version: 1.27.33
|
||||
version: 1.28.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.27.33",
|
||||
"version": "1.28.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.27.33";
|
||||
/* @sw-version-fallback */ "v1.28.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/*`
|
||||
|
||||
Reference in new issue
Block a user