mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Add a notification for a dose somebody else marked
The owner of a shared record gets an alert when a delegate marks one of their doses. It is the one exception to the rule that bookkeeping stays in the activity view: whether the tablets were taken is a fact about the owner's own body, and they should not have to go looking for it. Default ON, against the opt-in precedent of PERSONAL_RECORD, because the event only exists once the owner has invited somebody and given them write access. The per-channel row in the matrix is the valve for anyone who finds it noisy, and both positions of that preference are covered.
This commit is contained in:
1 parent
1efd337f53
commit
427e25d1ae
2 files changed
+70
No files matched your search
@@ -381,6 +381,58 @@ describe("dispatchNotification — per-event default policy (v1.4.25 W16c)", ()
|
||||
|
||||
expect(sendViaNtfyMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// v1.36.x — the delegated-dose event goes the other way from
|
||||
// PERSONAL_RECORD: ON without a row, because the owner invited the person
|
||||
// whose action produces it. Both positions of the preference are asserted,
|
||||
// so "default ON" cannot be true only because the dispatcher ignores the
|
||||
// event entirely.
|
||||
it("SHARED_RECORD_INTAKE defaults ON when no preference row exists", async () => {
|
||||
const channel = makeChannel({
|
||||
type: "NTFY",
|
||||
config: JSON.stringify({
|
||||
serverUrl: "https://ntfy.example",
|
||||
topic: "t",
|
||||
}),
|
||||
preferences: [],
|
||||
});
|
||||
vi.mocked(prisma.notificationChannel.findMany).mockResolvedValueOnce([
|
||||
channel,
|
||||
] as never);
|
||||
sendViaNtfyMock.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
await dispatchNotification({
|
||||
eventType: "SHARED_RECORD_INTAKE",
|
||||
userId: "u-1",
|
||||
title: "Wren marked a dose",
|
||||
message: "Ramipril: marked as taken by Wren.",
|
||||
});
|
||||
|
||||
expect(sendViaNtfyMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("SHARED_RECORD_INTAKE is suppressed by an explicit per-channel opt-out", async () => {
|
||||
const channel = makeChannel({
|
||||
type: "NTFY",
|
||||
config: JSON.stringify({
|
||||
serverUrl: "https://ntfy.example",
|
||||
topic: "t",
|
||||
}),
|
||||
preferences: [{ eventType: "SHARED_RECORD_INTAKE", enabled: false }],
|
||||
});
|
||||
vi.mocked(prisma.notificationChannel.findMany).mockResolvedValueOnce([
|
||||
channel,
|
||||
] as never);
|
||||
|
||||
await dispatchNotification({
|
||||
eventType: "SHARED_RECORD_INTAKE",
|
||||
userId: "u-1",
|
||||
title: "Wren marked a dose",
|
||||
message: "Ramipril: marked as taken by Wren.",
|
||||
});
|
||||
|
||||
expect(sendViaNtfyMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("dispatchNotification — MOOD_REMINDER single source of truth (v1.7.0)", () => {
|
||||
|
||||
@@ -112,6 +112,16 @@ export const EVENT_TYPES = [
|
||||
// morning window, so a concerning reading always stays on
|
||||
// `MEASUREMENT_ANOMALY` and this never bypasses Focus.
|
||||
"DAILY_BRIEFING",
|
||||
// v1.36.x — somebody with a write grant on this record marked one of the
|
||||
// owner's doses. The only notification a delegated write produces: every
|
||||
// other shared-record write is bookkeeping and stays in the owner's activity
|
||||
// view, while "somebody said I took my tablets" is a fact about the owner's
|
||||
// own body that they should not have to go looking for. Addressed to the
|
||||
// OWNER — the delegate learns what they did from the response in their own
|
||||
// session, not from a push (there is no channel that could carry it: the
|
||||
// dispatcher addresses a userId, and under record substitution that userId
|
||||
// is the owner by design). Default ON, see EVENT_DEFAULT_ENABLED.
|
||||
"SHARED_RECORD_INTAKE",
|
||||
] as const;
|
||||
export type EventType = (typeof EVENT_TYPES)[number];
|
||||
|
||||
@@ -179,6 +189,14 @@ export const EVENT_DEFAULT_ENABLED: Record<EventType, boolean> = {
|
||||
// channel that has no explicit enabled row, so a fresh account never
|
||||
// receives a morning push it did not ask for.
|
||||
DAILY_BRIEFING: false,
|
||||
// v1.36.x — ON by default, deliberately against the PERSONAL_RECORD opt-in
|
||||
// precedent. The event cannot exist unless the owner invited a second person
|
||||
// and gave them write access, so there is no backfill class of volume to
|
||||
// protect against; and an owner who has to discover a setting before they
|
||||
// learn that somebody else marks their doses was never told at all. The row
|
||||
// sits in the /notifications matrix from day one, so an owner who finds it
|
||||
// noisy silences it per channel with one switch.
|
||||
SHARED_RECORD_INTAKE: true,
|
||||
};
|
||||
|
||||
export const CHANNEL_TYPE_LABELS: Record<ChannelType, string> = {
|
||||
|
||||
Reference in new issue
Block a user