mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
fix(copy): make delete confirmations, sleep-debt labels, and the glucose band honest
Three surfaces where the words no longer matched the code, plus the missing web control for a preference the server already honours. Delete confirmations. Four dialogs promised permanence on surfaces that render an Undo: the measurements and mood bulk bodies, the lab-result body, and the illness-episode body (which said "cannot be undone" sixty lines below the code that fires the Undo toast). All four now use the same sentence the single-row measurement and mood dialogs already used. A sweep of every other confirmation in the tree found the rest accurate — the biomarker cascade, the mood-tag purge, and the account purge keep their warning because those deletes really are unrecoverable. Sleep debt. v1.19.0 replaced the summed shortfall with a running balance that credits catch-up sleep; the labels still described the old sum, so the word contradicted the number for anyone who slept in after a short night. Corrected in all six locales, plus the places the old semantics had propagated: the Coach system prompt, the OpenAPI field descriptions the native client reads, and four module docblocks. Glucose reference band. `hasDiabetes` selects the tighter reference band and grounds the Coach, but no web surface set it — a web-only account was pinned to the non-diabetic scale with no way out. The control goes on Targets, above the per-metric editor, because it answers the same question that page answers and the two are ordered: it picks the default band, an override below beats it. The copy says what it is — a reference band, not a diagnosis, never inferred from a reading, no clinical action taken on it. The passkey list was reported as always reading "never used". It does not reproduce: the column is selected by the list route and stamped on every verified assertion, both since v1.23.0. Pinned with tests on both halves so the contract cannot quietly regress.
This commit is contained in:
1 parent
b0021a8258
commit
e9d28b399e
21 files changed
+601
-61
No files matched your search
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* `GET /api/auth/passkeys` — the passkey management list.
|
||||
*
|
||||
* The management UI renders a "last used" column per credential, and a
|
||||
* passkey that reads "never used" is something a person acts on: they may
|
||||
* delete a working credential believing it is dead. So the field is part of
|
||||
* the route's contract, not an incidental select — dropping it from the
|
||||
* projection would silently turn every row into "never used" without any
|
||||
* type or lint failure.
|
||||
*
|
||||
* The write half (the stamp on a verified assertion) is pinned in
|
||||
* `src/lib/auth/__tests__/passkey.test.ts`.
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
vi.mock("@/lib/api-handler", () => ({
|
||||
apiHandler: (fn: unknown) => fn,
|
||||
requireAuth: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/db", () => ({
|
||||
prisma: { passkey: { findMany: vi.fn() } },
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/logging/context", () => ({ annotate: vi.fn() }));
|
||||
|
||||
import { GET } from "../route";
|
||||
import { requireAuth } from "@/lib/api-handler";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
const USER = { id: "user-1", username: "u", email: "u@example.com" };
|
||||
|
||||
const ROW = {
|
||||
id: "pk-1",
|
||||
name: "Phone",
|
||||
credentialDeviceType: "multiDevice",
|
||||
credentialBackedUp: true,
|
||||
createdAt: new Date("2026-01-01T00:00:00Z"),
|
||||
lastUsedAt: new Date("2026-07-01T10:00:00Z"),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(requireAuth).mockResolvedValue({ user: USER } as never);
|
||||
vi.mocked(prisma.passkey.findMany).mockResolvedValue([ROW] as never);
|
||||
});
|
||||
|
||||
describe("GET /api/auth/passkeys", () => {
|
||||
it("selects lastUsedAt, scoped to the calling user", async () => {
|
||||
await (GET as unknown as () => Promise<Response>)();
|
||||
|
||||
expect(prisma.passkey.findMany).toHaveBeenCalledTimes(1);
|
||||
const args = vi.mocked(prisma.passkey.findMany).mock.calls[0][0];
|
||||
|
||||
expect(args?.where).toEqual({ userId: USER.id });
|
||||
expect(args?.select).toMatchObject({ lastUsedAt: true });
|
||||
});
|
||||
|
||||
it("emits lastUsedAt in the response envelope", async () => {
|
||||
const res = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = await res.json();
|
||||
|
||||
expect(body.error).toBeNull();
|
||||
expect(body.data).toHaveLength(1);
|
||||
expect(body.data[0]).toHaveProperty("lastUsedAt");
|
||||
expect(new Date(body.data[0].lastUsedAt).toISOString()).toBe(
|
||||
"2026-07-01T10:00:00.000Z",
|
||||
);
|
||||
});
|
||||
|
||||
it("passes a never-used credential through as null, not as a missing key", async () => {
|
||||
vi.mocked(prisma.passkey.findMany).mockResolvedValue([
|
||||
{ ...ROW, lastUsedAt: null },
|
||||
] as never);
|
||||
|
||||
const res = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = await res.json();
|
||||
|
||||
expect(Object.keys(body.data[0])).toContain("lastUsedAt");
|
||||
expect(body.data[0].lastUsedAt).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,8 @@
|
||||
* signals the Sleep page + the iOS client render off the SAME canonical night
|
||||
* reconstruction the Sleep Score uses:
|
||||
*
|
||||
* - `sleepDebt` — cumulative deficit over the rolling window, with a calm
|
||||
* - `sleepDebt` — outstanding balance over the rolling window (surplus
|
||||
* sleep pays it down), with a calm
|
||||
* `partial` state under the night threshold.
|
||||
* - `chronotype` — MCTQ MSF / MSFsc band + social jetlag, with a `learning`
|
||||
* state until enough free-day nights exist.
|
||||
|
||||
Reference in new issue
Block a user