From 507d4e6facf3014a928783d000d44694cabeafce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 22:19:47 +0100 Subject: [PATCH] refactor(medications): drop the side-effect slot nobody wires The card menu took an optional side-effect item for the GLP-1 card, but no page ever passed the handler, so the item never rendered; side effects are logged on the detail page. The slot, its message key in ten locales and the tests that passed it by hand go. One test pinned behaviour only the dead slot produced: a delegate who may write but not manage kept a menu trigger because the test supplied the handler. In the app that delegate never had one, and the test now says so. --- messages/de.json | 1 - messages/en.json | 1 - messages/es.json | 1 - messages/fr.json | 1 - messages/it.json | 1 - messages/ko.json | 1 - messages/nl.json | 1 - messages/pl.json | 1 - messages/pt-BR.json | 1 - messages/ru.json | 1 - .../baselines/version-stamp-comments.json | 6 +- .../delegated-write-affordances.test.tsx | 12 ++-- .../__tests__/medication-card-glp1.test.tsx | 46 +-------------- .../medication-card-symmetry.test.tsx | 7 +-- .../medications/glp1-medication-card.tsx | 12 +--- .../medications/medication-card-menu.tsx | 59 ++++++------------- 16 files changed, 30 insertions(+), 122 deletions(-) diff --git a/messages/de.json b/messages/de.json index c3db429d5..b44fe4387 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "heute", "glp1NextInjectionTomorrow": "morgen", "glp1RotationSuggested": "Empfohlen: {site}", - "glp1LogSideEffect": "Nebenwirkung loggen", "sideEffects": { "section": "Nebenwirkungen", "addCta": "Erfassen", diff --git a/messages/en.json b/messages/en.json index d897c04e6..8dee2871b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "today", "glp1NextInjectionTomorrow": "tomorrow", "glp1RotationSuggested": "Recommended next: {site}", - "glp1LogSideEffect": "Log side effect", "sideEffects": { "section": "Side effects", "addCta": "Log", diff --git a/messages/es.json b/messages/es.json index c6c9875ea..34261c62a 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "hoy", "glp1NextInjectionTomorrow": "mañana", "glp1RotationSuggested": "Sugerido: {site}", - "glp1LogSideEffect": "Registrar efecto secundario", "sideEffects": { "section": "Efectos secundarios", "addCta": "Registrar", diff --git a/messages/fr.json b/messages/fr.json index 95779eaa4..63b352e78 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "aujourd’hui", "glp1NextInjectionTomorrow": "demain", "glp1RotationSuggested": "Conseillé : {site}", - "glp1LogSideEffect": "Consigner un effet secondaire", "sideEffects": { "section": "Effets secondaires", "addCta": "Consigner", diff --git a/messages/it.json b/messages/it.json index 1bc25ec4c..940aeb2e9 100644 --- a/messages/it.json +++ b/messages/it.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "oggi", "glp1NextInjectionTomorrow": "domani", "glp1RotationSuggested": "Suggerita: {site}", - "glp1LogSideEffect": "Registra effetto collaterale", "sideEffects": { "section": "Effetti collaterali", "addCta": "Registra", diff --git a/messages/ko.json b/messages/ko.json index 5aeef7e5d..4d05bd352 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "오늘", "glp1NextInjectionTomorrow": "내일", "glp1RotationSuggested": "다음 추천 부위: {site}", - "glp1LogSideEffect": "부작용 기록", "sideEffects": { "section": "부작용", "addCta": "기록", diff --git a/messages/nl.json b/messages/nl.json index e0a3f8535..66e9ad010 100644 --- a/messages/nl.json +++ b/messages/nl.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "vandaag", "glp1NextInjectionTomorrow": "morgen", "glp1RotationSuggested": "Volgende aanbevolen plek: {site}", - "glp1LogSideEffect": "Bijwerking vastleggen", "sideEffects": { "section": "Bijwerkingen", "addCta": "Vastleggen", diff --git a/messages/pl.json b/messages/pl.json index d35c7b385..a41d3296f 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "dziś", "glp1NextInjectionTomorrow": "jutro", "glp1RotationSuggested": "Sugerowane: {site}", - "glp1LogSideEffect": "Zapisz działanie niepożądane", "sideEffects": { "section": "Skutki uboczne", "addCta": "Dodaj", diff --git a/messages/pt-BR.json b/messages/pt-BR.json index 05e875766..36ea5e553 100644 --- a/messages/pt-BR.json +++ b/messages/pt-BR.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "hoje", "glp1NextInjectionTomorrow": "amanhã", "glp1RotationSuggested": "Próximo recomendado: {site}", - "glp1LogSideEffect": "Registrar efeito colateral", "sideEffects": { "section": "Efeitos colaterais", "addCta": "Registrar", diff --git a/messages/ru.json b/messages/ru.json index c96b8a0b2..431bdbac2 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -1481,7 +1481,6 @@ "glp1NextInjectionToday": "сегодня", "glp1NextInjectionTomorrow": "завтра", "glp1RotationSuggested": "Рекомендуется следующее: {site}", - "glp1LogSideEffect": "Записать побочный эффект", "sideEffects": { "section": "Побочные эффекты", "addCta": "Записать", diff --git a/src/__tests__/baselines/version-stamp-comments.json b/src/__tests__/baselines/version-stamp-comments.json index 0afbd80b7..95b72ddd6 100644 --- a/src/__tests__/baselines/version-stamp-comments.json +++ b/src/__tests__/baselines/version-stamp-comments.json @@ -1,7 +1,7 @@ { "$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.", "count": 2881, - "total": 9188, + "total": 9185, "entries": { "__tests__/account-payload-consumer-guard.test.ts": 2, "__tests__/acting-account-boundary-guard.test.ts": 7, @@ -1128,7 +1128,7 @@ "components/medications/__tests__/inventory-section-actions.test.tsx": 1, "components/medications/__tests__/inventory-section-summary.test.tsx": 2, "components/medications/__tests__/log-injection-site-dialog.test.tsx": 1, - "components/medications/__tests__/medication-card-glp1.test.tsx": 14, + "components/medications/__tests__/medication-card-glp1.test.tsx": 12, "components/medications/__tests__/medication-card-header.test.tsx": 1, "components/medications/__tests__/medication-card-next-due.test.tsx": 4, "components/medications/__tests__/medication-card-symmetry.test.tsx": 4, @@ -1165,7 +1165,7 @@ "components/medications/log-injection-site-dialog.tsx": 2, "components/medications/log-intake-dialog.tsx": 4, "components/medications/medication-card-header.tsx": 5, - "components/medications/medication-card-menu.tsx": 3, + "components/medications/medication-card-menu.tsx": 2, "components/medications/medication-card.tsx": 37, "components/medications/medication-categories-sheet.tsx": 1, "components/medications/medication-category-add-row.tsx": 1, diff --git a/src/components/__tests__/delegated-write-affordances.test.tsx b/src/components/__tests__/delegated-write-affordances.test.tsx index 7517ab847..623e22bde 100644 --- a/src/components/__tests__/delegated-write-affordances.test.tsx +++ b/src/components/__tests__/delegated-write-affordances.test.tsx @@ -320,20 +320,16 @@ describe("marking a dose", () => { describe("the medication card menu", () => { const node = ( - {}} - onOpenHistory={() => {}} - onLogSideEffect={() => {}} - /> + {}} onOpenHistory={() => {}} /> ); it("renders its trigger in the caller's own record", () => { expect(render(OWN_RECORD, node)).toContain(" { - // One item survives for them — noting a side effect — so the menu stays. - expect(render(WRITABLE, node)).toContain(" { + // Editing and the history editor are owner work; nothing survives. + expect(render(WRITABLE, node)).toBe(""); }); it("disappears entirely for a read-only delegate", () => { diff --git a/src/components/medications/__tests__/medication-card-glp1.test.tsx b/src/components/medications/__tests__/medication-card-glp1.test.tsx index 5a405f085..b043d2e9e 100644 --- a/src/components/medications/__tests__/medication-card-glp1.test.tsx +++ b/src/components/medications/__tests__/medication-card-glp1.test.tsx @@ -465,49 +465,9 @@ describe(" — GLP-1 variant rendering", () => { expect(html).not.toContain("Low stock"); }); - it("folds the side-effect quick-log into the same overflow kebab", () => { - // v1.7.2 — the side-effect quick-log folds into the SAME overflow - // menu as edit / history / advanced, so the GLP-1 header carries one - // kebab whether or not the hand-off is wired (parity with the generic - // card). SSR doesn't open Radix Portal content, so we assert the - // single trigger surfaces and smoke-check the handler payload. - const client = makeClient(); - seedCompliance(client, med7p5.id); - seedGlp1Details(client, med7p5.id, {}); - - const handler = vi.fn(); - const html = render( - {}} - onOpenHistory={() => {}} - onLogSideEffect={handler} - />, - client, - ); - - // Exactly one kebab trigger with the localised aria-label. - const triggers = html.match(/aria-label="More options"/g) ?? []; - expect(triggers).toHaveLength(1); - // The side-effect button must no longer ride alongside - // Eingenommen / Übersprungen in the primary actions row. - expect(html).not.toMatch( - /class="[^"]*flex[^"]*gap-2[^"]*"[^>]*>[\s\S]*?Log side effect/, - ); - - // Invoke the supplied handler the way the menu item's onClick - // would — pins the prefill payload contract for MoodEntry. - handler(med7p5); - expect(handler).toHaveBeenCalledWith( - expect.objectContaining({ name: "Mounjaro", treatmentClass: "GLP1" }), - ); - }); - - it("still renders the single kebab when onLogSideEffect is not supplied", () => { - // v1.7.2 — the kebab is unconditional now (it always carries edit - // / history / advanced); the side-effect item is the only opt-in - // part. Mounjaro and Ramipril therefore share the same one-kebab - // header shape regardless of the hand-off prop. + it("renders the single overflow kebab", () => { + // The kebab carries edit and history, so Mounjaro and Ramipril share + // the same one-kebab header shape. const client = makeClient(); seedCompliance(client, med7p5.id); seedGlp1Details(client, med7p5.id, {}); diff --git a/src/components/medications/__tests__/medication-card-symmetry.test.tsx b/src/components/medications/__tests__/medication-card-symmetry.test.tsx index 5b8ab213f..1343f7103 100644 --- a/src/components/medications/__tests__/medication-card-symmetry.test.tsx +++ b/src/components/medications/__tests__/medication-card-symmetry.test.tsx @@ -190,9 +190,8 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => { } }); - it("Mounjaro keeps the single kebab even when onLogSideEffect is supplied (GLP-1-only)", () => { - // The side-effect quick-log folds into the SAME overflow menu, so the - // GLP-1 header shape stays byte-symmetric with the generic card: one + it("Mounjaro carries the same single kebab as the generic card", () => { + // The GLP-1 header shape stays byte-symmetric with the generic card: one // kebab trigger, no extra control. const client = makeClient(); seedCompliance(client, mounjaro.id); @@ -203,7 +202,6 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => { medication={mounjaro} onEdit={() => {}} onOpenHistory={() => {}} - onLogSideEffect={() => {}} />, client, ); @@ -294,7 +292,6 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => { medication={mounjaro} onEdit={() => {}} onOpenHistory={() => {}} - onLogSideEffect={() => {}} />, client, ); diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index 5024217cd..91502183c 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -178,7 +178,6 @@ interface Glp1MedicationCardProps { * card. The parent owns the navigation. */ onOpenHistory: (med: Glp1Medication) => void; - onLogSideEffect?: (med: Glp1Medication) => void; /** * v1.29.x — briefly ringed when this card is the Today digest's "Log * dose" deep-link target (`/medications?highlight=`). @@ -215,7 +214,6 @@ export function Glp1MedicationCard({ medication, onEdit, onOpenHistory, - onLogSideEffect, highlighted = false, }: Glp1MedicationCardProps) { const queryClient = useQueryClient(); @@ -478,19 +476,11 @@ export function Glp1MedicationCard({ // The four former header icon-buttons (open / edit / history / // advanced) collapse into a SINGLE overflow menu so the GLP-1 card // stays symmetric with the generic medication card. The card header - // itself links to the detail page (the former chevron target). The - // optional side-effect quick-log would fold in as a last menu item - // when `onLogSideEffect` is wired, but the medications list page does - // not wire it — side-effect logging lives on the detail page's - // SideEffectsSection — so the menu renders the same items as the - // generic card. + // itself links to the detail page (the former chevron target). const headerActions = ( onEdit(medication)} onOpenHistory={() => onOpenHistory(medication)} - onLogSideEffect={ - onLogSideEffect ? () => onLogSideEffect(medication) : undefined - } /> ); diff --git a/src/components/medications/medication-card-menu.tsx b/src/components/medications/medication-card-menu.tsx index b26c9c0b1..b9a75fbf1 100644 --- a/src/components/medications/medication-card-menu.tsx +++ b/src/components/medications/medication-card-menu.tsx @@ -14,13 +14,8 @@ * target as the name tap); the structural wizard lives under the detail * page's Erweitert → Lebenszyklus row. * - * The optional `onLogSideEffect` slot exists for the GLP-1 card to fold a - * "Log side effect" item into the SAME menu, but it is NOT wired on the - * medications list page — both card variants there receive only the - * edit / history / advanced handlers, so the item never renders. Side- - * effect logging lives on the detail page's ``. When - * the slot is left undefined the two card variants stay byte-symmetric in - * the header (one kebab, identical items). + * Side-effect logging lives on the detail page's ``, so + * the two card variants carry the same menu (one kebab, identical items). * * The trigger is a 44px ghost icon-button (WCAG 2.5.5); every * `DropdownMenuItem` already carries `min-h-11` from the shadcn @@ -33,14 +28,13 @@ */ import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; -import { History, MoreVertical, Pencil, Stethoscope } from "lucide-react"; +import { History, MoreVertical, Pencil } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useTranslations } from "@/lib/i18n/context"; @@ -48,25 +42,19 @@ import { useTranslations } from "@/lib/i18n/context"; export interface MedicationCardMenuProps { onEdit: () => void; onOpenHistory: () => void; - /** GLP-1-only: folds the side-effect quick-log into the same menu. */ - onLogSideEffect?: () => void; } export function MedicationCardMenu({ onEdit, onOpenHistory, - onLogSideEffect, }: MedicationCardMenuProps) { const { t } = useTranslations(); - // v1.36.x — noting a side effect is an admitted delegated write; editing the - // medication and opening its history editor are not. When nothing in the - // menu survives, the trigger goes with it rather than opening onto an empty - // sheet. The card itself is untouched: no wash, no badge, no colour change. - const { canWriteDomain, canManageDomain } = useRecordCapabilities(); - const canAddSideEffect = canWriteDomain("medications"); - const canManageMedications = canManageDomain("medications"); - const showSideEffect = onLogSideEffect != null && canAddSideEffect; - if (!canManageMedications && !showSideEffect) return null; + // Editing the medication and opening its history editor are owner work, not + // delegated writes. Without either, the trigger goes too rather than opening + // onto an empty menu. The card itself is untouched: no wash, no badge, no + // colour change. + const { canManageDomain } = useRecordCapabilities(); + if (!canManageDomain("medications")) return null; return ( @@ -89,27 +77,14 @@ export function MedicationCardMenu({ className="w-56" data-slot="medication-card-menu" > - {canManageMedications && ( - <> - - - {t("common.edit")} - - - - {t("medications.detail.header.historyLabel")} - - - )} - {showSideEffect && onLogSideEffect && ( - <> - {canManageMedications && } - - - {t("medications.glp1LogSideEffect")} - - - )} + + + {t("common.edit")} + + + + {t("medications.detail.header.historyLabel")} + );