mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
chore(release): v1.4.28.1
## [1.4.28.1] — 2026-05-16 — Dashboard-save hotfix "Speichern" on the Dashboard works again for every account whose layout was last persisted before v1.4.28. The v1.4.28 retire of the `glp1` widget id left an orphan entry in `dashboardWidgetsJson` for legacy users, which made the save validator reject the payload; the resolver now filters unknown widget ids on read. ### Fixed - **Dashboard save against legacy layouts.** `resolveDashboardLayout` in `src/lib/dashboard-layout.ts` drops widget ids that are no longer in the registry before returning the layout, so the `dashboardWidgetsJson` round-trip (`GET /api/me/dashboard` → user edit → `PUT /api/me/dashboard`) succeeds even when the persisted JSON still names `glp1` or any other retired widget. ### Tests - `src/lib/__tests__/dashboard-layout.test.ts` gains a case for the retired-id filter (19/19 green).
This commit is contained in:
1 parent
2d111fcb24
commit
232ea43d99
27 files changed
+3662
-167
No files matched your search
@@ -34,7 +34,7 @@ describe("GET /api/version", () => {
|
||||
it("returns the package.json version", async () => {
|
||||
const response = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = (await response.json()) as VersionEnvelope;
|
||||
expect(body.data.version).toMatch(/^\d+\.\d+\.\d+(-[a-z0-9.-]+)?$/i);
|
||||
expect(body.data.version).toMatch(/^\d+\.\d+\.\d+(\.\d+)?(-[a-z0-9.-]+)?$/i);
|
||||
expect(body.data.license).toBe("AGPL-3.0");
|
||||
expect(body.data.repository).toContain("github.com");
|
||||
expect(body.data.changelog).toContain("CHANGELOG");
|
||||
|
||||
@@ -162,6 +162,28 @@ describe("DEFAULT_DASHBOARD_LAYOUT contract", () => {
|
||||
DEFAULT_DASHBOARD_LAYOUT.widgets.map((w) => w.id).sort(),
|
||||
);
|
||||
});
|
||||
|
||||
it("drops widget ids the current build no longer knows about", () => {
|
||||
// v1.4.28 retired the `glp1` tile. Users who saved a layout before
|
||||
// then still have the orphan id in `dashboardWidgetsJson`; the PUT
|
||||
// route's Zod enum would reject the entire blob on the next save
|
||||
// round-trip, surfacing the "Speichern fehlgeschlagen" toast.
|
||||
// The resolver filters unknown ids on read so the GET shape is
|
||||
// current-build-safe and the next save round-trips cleanly.
|
||||
const stored = {
|
||||
version: 1,
|
||||
widgets: [
|
||||
{ id: "weight", visible: true, tileVisible: true, order: 0 },
|
||||
{ id: "glp1", visible: true, tileVisible: true, order: 1 },
|
||||
{ id: "bp", visible: true, tileVisible: true, order: 2 },
|
||||
],
|
||||
};
|
||||
const resolved = resolveDashboardLayout(stored);
|
||||
const ids = resolved.widgets.map((w) => w.id);
|
||||
expect(ids).not.toContain("glp1");
|
||||
expect(ids).toContain("weight");
|
||||
expect(ids).toContain("bp");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -229,13 +229,22 @@ export function resolveDashboardLayout(raw: unknown): DashboardLayout {
|
||||
return DEFAULT_DASHBOARD_LAYOUT;
|
||||
}
|
||||
|
||||
// Drop widget ids the current build does not know about. v1.4.28
|
||||
// retired the `glp1` tile; any user who saved a layout before then
|
||||
// still has the orphan id in `dashboardWidgetsJson`, and the PUT
|
||||
// route's Zod enum rejects the entire blob on the next save round-
|
||||
// trip. Filtering on read keeps the GET shape current-build-safe
|
||||
// and lets the Settings UI re-PUT a clean array.
|
||||
const knownIds = new Set<string>(DASHBOARD_WIDGET_IDS);
|
||||
const filtered = candidate.widgets.filter((w) => knownIds.has(w.id));
|
||||
|
||||
// Merge with defaults so new widgets introduced in later versions show up
|
||||
// automatically (invisible by default, users opt-in).
|
||||
const savedIds = new Set(candidate.widgets.map((w) => w.id));
|
||||
const savedIds = new Set(filtered.map((w) => w.id));
|
||||
const missing = DEFAULT_DASHBOARD_LAYOUT.widgets.filter(
|
||||
(w) => !savedIds.has(w.id),
|
||||
);
|
||||
const maxOrder = Math.max(0, ...candidate.widgets.map((w) => w.order));
|
||||
const maxOrder = Math.max(0, ...filtered.map((w) => w.order));
|
||||
const appended = missing.map((w, i) => ({
|
||||
...w,
|
||||
visible: false, // default-invisible on auto-upgrade
|
||||
@@ -247,7 +256,7 @@ export function resolveDashboardLayout(raw: unknown): DashboardLayout {
|
||||
// never had the field (the v1.4.15 schema upgrade). Users who saved
|
||||
// a layout before this release see no behavioural change until they
|
||||
// explicitly toggle the new strip switch.
|
||||
const normalized = candidate.widgets.map((w) => ({
|
||||
const normalized = filtered.map((w) => ({
|
||||
...w,
|
||||
tileVisible: typeof w.tileVisible === "boolean" ? w.tileVisible : w.visible,
|
||||
}));
|
||||
|
||||
Reference in new issue
Block a user