mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
feat(settings): make medications a toggleable module
Medications graduated from the always-on core set (weight / blood pressure / pulse stay core) to an opt-out module. The surface gates off the resolved module map — the left-nav entry, the dashboard medication widget, and the dedicated Medikamente settings entry hide when the account turns the module off. The medication data routes stay exempt from the gate (same data-layer reasoning as mood / labs): an importer, a sync, and the user's ability to clean up entries keep working while the surface is hidden, and re-enabling finds the schedule and intake history intact. Wire the module-route gate inventory, the dashboard widget/summary maps, the PATCH allowlist, and the nav model; regenerate the OpenAPI contract for the new module key.
This commit is contained in:
1 parent
930c4a75dd
commit
559366e308
13 files changed
+119
-28
No files matched your search
@@ -115,7 +115,7 @@ describe("visibleNavDestinations module gate", () => {
|
||||
expect(on.indexOf("/cycle")).toBeLessThan(on.indexOf("/insights"));
|
||||
});
|
||||
|
||||
it("drops a module-gated entry (mood / labs / coach / achievements / insights) when its module is disabled", () => {
|
||||
it("drops a module-gated entry (mood / labs / coach / achievements / insights / medications) when its module is disabled", () => {
|
||||
const disabled = visibleNavDestinations({
|
||||
mood: false,
|
||||
labs: false,
|
||||
@@ -124,12 +124,15 @@ describe("visibleNavDestinations module gate", () => {
|
||||
// v1.18.0 — Insights is now `requiresModule: "insights"`, so the
|
||||
// top-level /insights entry drops when the module is off.
|
||||
insights: false,
|
||||
// v1.18.1 (D3) — medications graduated to a toggleable module.
|
||||
medications: false,
|
||||
}).map((d) => d.href);
|
||||
expect(disabled).not.toContain("/mood");
|
||||
expect(disabled).not.toContain("/labs");
|
||||
expect(disabled).not.toContain("/coach");
|
||||
expect(disabled).not.toContain("/achievements");
|
||||
expect(disabled).not.toContain("/insights");
|
||||
expect(disabled).not.toContain("/medications");
|
||||
});
|
||||
|
||||
it("keeps every module-gated entry when its module is enabled", () => {
|
||||
@@ -139,12 +142,14 @@ describe("visibleNavDestinations module gate", () => {
|
||||
coach: true,
|
||||
achievements: true,
|
||||
insights: true,
|
||||
medications: true,
|
||||
}).map((d) => d.href);
|
||||
expect(enabled).toContain("/mood");
|
||||
expect(enabled).toContain("/labs");
|
||||
expect(enabled).toContain("/coach");
|
||||
expect(enabled).toContain("/achievements");
|
||||
expect(enabled).toContain("/insights");
|
||||
expect(enabled).toContain("/medications");
|
||||
});
|
||||
|
||||
it("fails open: a missing key or an undefined map keeps the gated entry", () => {
|
||||
@@ -159,7 +164,8 @@ describe("visibleNavDestinations module gate", () => {
|
||||
expect(hrefs).toContain("/coach");
|
||||
expect(hrefs).toContain("/achievements");
|
||||
}
|
||||
// Core destinations always render regardless of the map.
|
||||
// Core / fail-open destinations render regardless of the map. Medications
|
||||
// is module-gated (v1.18.1 D3) but fails open on a missing key.
|
||||
expect(emptyMap).toContain("/measurements");
|
||||
expect(emptyMap).toContain("/medications");
|
||||
expect(emptyMap).toContain("/insights");
|
||||
|
||||
@@ -49,8 +49,8 @@ export interface NavDestination {
|
||||
* v1.18.0 — gate the entry on a per-user module toggle. When set, the
|
||||
* entry is dropped unless the account's resolved module map (from
|
||||
* `GET /api/auth/me`'s `modules`) has the key enabled. Core destinations
|
||||
* (weight / BP / pulse / medications + always-on pages) carry no key and
|
||||
* always render. `cycle` and `coach` are delegated keys (cycle → gender +
|
||||
* (weight / BP / pulse + always-on pages) carry no key and always render.
|
||||
* `cycle` and `coach` are delegated keys (cycle → gender +
|
||||
* opt-in, coach → operator flag + per-user opt-out); the auth/me map
|
||||
* already reflects that delegation, so reading them here is correct and
|
||||
* not a re-derivation.
|
||||
@@ -83,6 +83,9 @@ export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
|
||||
tKey: "nav.medications",
|
||||
icon: Pill,
|
||||
tourId: "nav-medications",
|
||||
// v1.18.1 (D3) — medications graduated from a CORE domain to a toggleable
|
||||
// module; the nav entry now drops when the account turns the module off.
|
||||
requiresModule: "medications",
|
||||
},
|
||||
{
|
||||
href: "/cycle",
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
* core keys) and then invalidates the `authMe()` query so the nav, Insights
|
||||
* pills, and dashboard tiles re-gate live off `useAuth().user.modules`.
|
||||
*
|
||||
* The four CORE domains (weight, blood pressure, pulse, medications) render
|
||||
* as a separate read-only "always on" group — locked switches with a short
|
||||
* note — so the always-on measurement engine + meds read as deliberately
|
||||
* fixed, never disableable. Palette stays neutral throughout; this is a
|
||||
* calm configuration surface, not an alarm panel.
|
||||
* The three CORE domains (weight, blood pressure, pulse) render as a
|
||||
* separate read-only "always on" group — locked switches with a short note —
|
||||
* so the always-on measurement engine reads as deliberately fixed, never
|
||||
* disableable. Palette stays neutral throughout; this is a calm
|
||||
* configuration surface, not an alarm panel. (v1.18.1 D3 — medications
|
||||
* graduated to the toggleable group above.)
|
||||
*
|
||||
* State source is `useAuth().user.modules` (the resolved `/auth/me` map).
|
||||
* A module is enabled unless its key is explicitly `false` — default-on.
|
||||
@@ -71,6 +72,8 @@ const MODULE_ICONS: Record<ModuleKey, LucideIcon> = {
|
||||
achievements: Award,
|
||||
coach: MessageCircleHeart,
|
||||
insights: Sparkles,
|
||||
// v1.18.1 (D3) — medications graduated from CORE to a toggleable module.
|
||||
medications: Pill,
|
||||
doctorReport: FileText,
|
||||
};
|
||||
|
||||
@@ -79,7 +82,6 @@ const CORE_ICONS: Record<CoreDomainKey, LucideIcon> = {
|
||||
weight: Scale,
|
||||
bloodPressure: HeartPulse,
|
||||
pulse: Activity,
|
||||
medications: Pill,
|
||||
};
|
||||
|
||||
export function ModulesSection() {
|
||||
|
||||
@@ -124,12 +124,15 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
// v1.18.0 (S5) — Medikamente: medication-specific settings (list view +
|
||||
// order + injection sites). Medications is a CORE domain, so the entry is
|
||||
// always shown. Was a hidden child of the Layout hub.
|
||||
// order + injection sites). Was a hidden child of the Layout hub.
|
||||
// v1.18.1 (D3) — medications graduated from a CORE domain to a toggleable
|
||||
// module, so this entry now hides when the account turns the module off
|
||||
// (the medication data routes stay live; this only hides the surface).
|
||||
{
|
||||
slug: "medications",
|
||||
titleKey: "settings.sections.medications.title",
|
||||
icon: Pill,
|
||||
moduleGate: "medications",
|
||||
},
|
||||
// v1.18.0 (S5) — Stimmung: the mood-tag management surface gets its own
|
||||
// nav entry, shown only when the mood module is enabled. Was a hidden
|
||||
|
||||
Reference in new issue
Block a user