fix(activity): word the refusals that now reach a delegate's feed

The custom-metric create and update refusals and the medication layout
refusal are filed through auditLog now, so a delegate's refused request
reaches the owner's activity feed, where it read as the generic change.
Each gets its own sentence in every bundle; the refusal sentences live in
their own function. The audit write guard states its floor, and the
schedule-reader guard names the medication create and edit, which answer
through medicationDetail.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 17:19:21 +02:00
1 parent fb9cd9b965
commit a0dce10c1a
14 files changed
+106 -54

No files matched your search

+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} wollte eine Einnahme ändern und wurde abgewiesen",
"rejectedDoseImport": "{name} wollte Einnahmen importieren und wurde abgewiesen",
"rejectedTrackedValue": "{name} wollte einen eigenen Messwert eintragen und wurde abgewiesen",
"rejectedTrackedValueTypeCreate": "{name} wollte eine eigene Messgröße anlegen und wurde abgewiesen",
"rejectedTrackedValueTypeUpdate": "{name} wollte eine eigene Messgröße ändern und wurde abgewiesen",
"rejectedMedicationLayout": "{name} wollte die Medikamentenliste neu anordnen und wurde abgewiesen",
"deniedDoseImport": "Ein von {name} gestarteter Einnahme-Import konnte nicht eingereiht werden",
"anamnesisFactCreate": "{name} hat einen Fakt zur Vorgeschichte eingetragen",
"customMetricCreate": "{name} hat eine eigene Messgröße angelegt",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} tried to change a dose and was refused",
"rejectedDoseImport": "{name} tried to import doses and was refused",
"rejectedTrackedValue": "{name} tried to log a tracked value and was refused",
"rejectedTrackedValueTypeCreate": "{name} tried to add a tracked value type and was refused",
"rejectedTrackedValueTypeUpdate": "{name} tried to change a tracked value type and was refused",
"rejectedMedicationLayout": "{name} tried to rearrange the medication list and was refused",
"deniedDoseImport": "A dose import {name} started could not be queued",
"anamnesisFactCreate": "{name} added a health background fact",
"customMetricCreate": "{name} added a tracked value type",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} intentó cambiar una dosis y fue rechazado",
"rejectedDoseImport": "{name} intentó importar dosis y fue rechazado",
"rejectedTrackedValue": "{name} intentó registrar un valor propio y fue rechazado",
"rejectedTrackedValueTypeCreate": "{name} intentó añadir un tipo de valor propio y fue rechazado",
"rejectedTrackedValueTypeUpdate": "{name} intentó cambiar un tipo de valor propio y fue rechazado",
"rejectedMedicationLayout": "{name} intentó reordenar la lista de medicamentos y fue rechazado",
"deniedDoseImport": "Una importación de dosis iniciada por {name} no se pudo poner en cola",
"anamnesisFactCreate": "{name} añadió un dato de antecedentes de salud",
"customMetricCreate": "{name} añadió un tipo de valor propio",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} a tenté de modifier une prise et a été refusé",
"rejectedDoseImport": "{name} a tenté d'importer des prises et a été refusé",
"rejectedTrackedValue": "{name} a tenté d'enregistrer une valeur personnalisée et a été refusé",
"rejectedTrackedValueTypeCreate": "{name} a tenté d'ajouter un type de valeur personnalisée et a été refusé",
"rejectedTrackedValueTypeUpdate": "{name} a tenté de modifier un type de valeur personnalisée et a été refusé",
"rejectedMedicationLayout": "{name} a tenté de réorganiser la liste des médicaments et a été refusé",
"deniedDoseImport": "Un import de prises lancé par {name} n'a pas pu être mis en file",
"anamnesisFactCreate": "{name} a ajouté un élément d'antécédents de santé",
"customMetricCreate": "{name} a ajouté un type de valeur personnalisée",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} ha provato a modificare una dose ed è stato rifiutato",
"rejectedDoseImport": "{name} ha provato a importare delle dosi ed è stato rifiutato",
"rejectedTrackedValue": "{name} ha provato a registrare un valore personalizzato ed è stato rifiutato",
"rejectedTrackedValueTypeCreate": "{name} ha provato ad aggiungere un tipo di valore personalizzato ed è stato rifiutato",
"rejectedTrackedValueTypeUpdate": "{name} ha provato a modificare un tipo di valore personalizzato ed è stato rifiutato",
"rejectedMedicationLayout": "{name} ha provato a riordinare l'elenco dei farmaci ed è stato rifiutato",
"deniedDoseImport": "Un'importazione di dosi avviata da {name} non è stata messa in coda",
"anamnesisFactCreate": "{name} ha aggiunto un dato di anamnesi",
"customMetricCreate": "{name} ha aggiunto un tipo di valore personalizzato",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} 님이 복용 기록을 수정하려다 거절됐어요",
"rejectedDoseImport": "{name} 님이 복용 기록을 불러오려다 거절됐어요",
"rejectedTrackedValue": "{name} 님이 추적 값을 기록하려다 거절됐어요",
"rejectedTrackedValueTypeCreate": "{name} 님이 추적 값 종류를 추가하려다 거절됐어요",
"rejectedTrackedValueTypeUpdate": "{name} 님이 추적 값 종류를 수정하려다 거절됐어요",
"rejectedMedicationLayout": "{name} 님이 약 목록 순서를 바꾸려다 거절됐어요",
"deniedDoseImport": "{name} 님이 시작한 복용 기록 불러오기를 대기열에 넣지 못했어요",
"anamnesisFactCreate": "{name} 님이 건강 배경 항목을 추가했어요",
"customMetricCreate": "{name} 님이 추적 값 종류를 추가했어요",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} probeerde een dosis te wijzigen, maar dat werd geweigerd",
"rejectedDoseImport": "{name} probeerde doses te importeren, maar dat werd geweigerd",
"rejectedTrackedValue": "{name} probeerde een bijgehouden waarde vast te leggen, maar dat werd geweigerd",
"rejectedTrackedValueTypeCreate": "{name} probeerde een type bijgehouden waarde toe te voegen, maar dat werd geweigerd",
"rejectedTrackedValueTypeUpdate": "{name} probeerde een type bijgehouden waarde te wijzigen, maar dat werd geweigerd",
"rejectedMedicationLayout": "{name} probeerde de medicijnlijst anders te ordenen, maar dat werd geweigerd",
"deniedDoseImport": "Een dosisimport die {name} had gestart, kon niet in de wachtrij worden gezet",
"anamnesisFactCreate": "{name} heeft een gegeven aan de gezondheidsachtergrond toegevoegd",
"customMetricCreate": "{name} heeft een type bijgehouden waarde toegevoegd",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} próbował zmienić przyjęcie dawki i został odrzucony",
"rejectedDoseImport": "{name} próbował zaimportować przyjęcia dawek i został odrzucony",
"rejectedTrackedValue": "{name} próbował zapisać własny pomiar i został odrzucony",
"rejectedTrackedValueTypeCreate": "{name} próbował dodać własny typ pomiaru i został odrzucony",
"rejectedTrackedValueTypeUpdate": "{name} próbował zmienić własny typ pomiaru i został odrzucony",
"rejectedMedicationLayout": "{name} próbował zmienić kolejność listy leków i został odrzucony",
"deniedDoseImport": "Import przyjęć rozpoczęty przez {name} nie mógł zostać zakolejkowany",
"anamnesisFactCreate": "{name} dodał fakt z wywiadu zdrowotnego",
"customMetricCreate": "{name} dodał własny typ pomiaru",
+3
View File
@@ -12719,6 +12719,9 @@
"rejectedDoseUpdate": "{name} tentou alterar uma dose e foi recusado",
"rejectedDoseImport": "{name} tentou importar doses e foi recusado",
"rejectedTrackedValue": "{name} tentou registrar um valor acompanhado e foi recusado",
"rejectedTrackedValueTypeCreate": "{name} tentou adicionar um tipo de valor acompanhado e foi recusado",
"rejectedTrackedValueTypeUpdate": "{name} tentou alterar um tipo de valor acompanhado e foi recusado",
"rejectedMedicationLayout": "{name} tentou reorganizar a lista de medicamentos e foi recusado",
"deniedDoseImport": "Uma importação de doses que {name} iniciou não pôde ser enfileirada",
"anamnesisFactCreate": "{name} adicionou um fato do histórico de saúde",
"customMetricCreate": "{name} adicionou um tipo de valor acompanhado",
@@ -39,6 +39,8 @@ describe("audit rows go through auditLog()", () => {
.map(([rel]) => rel);
it("finds the writers it polices", () => {
expect(files.length).toBeGreaterThan(3000);
expect(writers.length).toBeGreaterThanOrEqual(2);
expect(writers).toContain("lib/auth/audit.ts");
});
+3 -13
View File
@@ -1,16 +1,11 @@
{
"$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 101,
"total": 116897,
"count": 91,
"total": 101342,
"entries": {
"app/medications/page-client.tsx": 839,
"app/page-client.tsx": 2324,
"components/admin/backups-section.tsx": 1462,
"components/charts/health-chart.tsx": 2517,
"components/charts/mood-chart.tsx": 1166,
"components/cycle/log-day-sheet.tsx": 1639,
"components/documents/document-detail-sheet.tsx": 1304,
"components/documents/documents-view.tsx": 1223,
"components/insights/coach-panel/chat-bubble.tsx": 888,
"components/insights/coach-panel/coach-conversation.tsx": 1286,
"components/insights/coach-panel/use-coach.ts": 1486,
@@ -18,17 +13,12 @@
"components/insights/health-score-card.tsx": 1023,
"components/insights/insights-tab-strip.tsx": 1086,
"components/layout/sidebar-nav.tsx": 855,
"components/measurement-reminders/vorsorge-section.tsx": 1536,
"components/measurements/measurement-form.tsx": 815,
"components/measurements/measurement-list.tsx": 2019,
"components/medications/detail/medication-detail-tabs.tsx": 1160,
"components/medications/dose-history-ledger.tsx": 1052,
"components/medications/medication-table.tsx": 853,
"components/medications/wizard/wizard-payload.ts": 1414,
"components/mood/mood-list.tsx": 1408,
"components/records/life-circumstances-manager.tsx": 1080,
"components/settings/dashboard-layout-section.tsx": 1033,
"components/settings/managed-record-settings-section.tsx": 1071,
"components/timeline/timeline-chart.tsx": 1174,
"components/timeline/timeline-geometry.ts": 1395,
"lib/ai/anthropic-client.ts": 804,
@@ -92,7 +82,7 @@
"lib/measurements/import-apple-health-export.ts": 1722,
"lib/medications/scheduling/cadence.ts": 978,
"lib/medications/scheduling/recurrence.ts": 1108,
"lib/medications/scheduling/slot-upsert.ts": 926,
"lib/medications/scheduling/slot-upsert.ts": 912,
"lib/notifications/senders/apns.ts": 1100,
"lib/oura/sync.ts": 900,
"lib/rollups/measurement-rollups.ts": 1496,
@@ -1,7 +1,7 @@
{
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 355,
"total": 114858,
"total": 114773,
"entries": {
"app/achievements/page.tsx :: AchievementsPage": 206,
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
@@ -15,16 +15,16 @@
"app/api/cycle/calendar/route.ts :: apiHandler(arg)": 230,
"app/api/cycle/day-logs/bulk/route.ts :: postBulk": 213,
"app/api/cycle/insights/route.ts :: apiHandler(arg)": 226,
"app/api/devices/route.ts :: apiHandler(arg)": 231,
"app/api/devices/route.ts :: apiHandler(arg)": 226,
"app/api/documents/inbound/bulk/route.ts :: apiHandler(arg)": 207,
"app/api/encounters/[id]/route.ts :: apiHandler(arg) #2": 269,
"app/api/environment/route.ts :: apiHandler(arg)": 201,
"app/api/export/health-record/route.ts :: apiHandler(arg)": 241,
"app/api/ingest/medication/route.ts :: apiHandler(arg)": 267,
"app/api/ingest/medication/route.ts :: apiHandler(arg)": 262,
"app/api/insights/cards/route.ts :: apiHandler(arg)": 211,
"app/api/integrations/status/route.ts :: apiHandler(arg)": 451,
"app/api/labs/[id]/route.ts :: apiHandler(arg) #2": 239,
"app/api/labs/ocr/commit/route.ts :: commitOcrRows": 285,
"app/api/labs/ocr/commit/route.ts :: commitOcrRows": 280,
"app/api/labs/route.ts :: postLabResult": 242,
"app/api/mcp/oauth/token/route.ts :: POST": 250,
"app/api/mcp/oauth/token/route.ts :: POST > withBackgroundEvent(arg)": 248,
@@ -346,7 +346,7 @@
"lib/notifications/senders/web-push.ts :: sendViaWebPush": 268,
"lib/personal-records/pr-detection-worker.ts :: detectPersonalRecordsForUser": 239,
"lib/personal-records/pr-direction.ts :: getPRDirection": 202,
"lib/record-activity/activity-verb.ts :: recordActivityVerbLine": 395,
"lib/record-activity/activity-verb.ts :: recordActivityVerbLine": 325,
"lib/strava/sync.ts :: runStravaSync": 219,
"lib/support/report.ts :: collectSupportReportFacts": 332,
"lib/targets/build-response.ts :: buildTargetsResponse": 330,
@@ -115,10 +115,17 @@ const MAY_READ_EVERY_ROW: Record<string, string> = {
"units consumed by a recorded dose",
"lib/medications/intake-slot-dedup.ts":
"folds duplicate rows of doses already recorded",
// The medication create and edit write the schedule rows and answer
// through `medicationDetail` (read-medication.ts), which shapes them with
// scheduleWireFields and derives the due state the switch allows.
"lib/medications/create-medication.ts":
"writes the schedules; answers through medicationDetail",
"lib/medications/update-medication/update-medication.ts":
"writes the schedules; answers through medicationDetail",
// The medication edit: the schedule count and the primary schedule's id
// for the reminder-window bridge, and the replaced rows archived as an era
// whose payload is empty for a stretch with tracking off. Nothing due comes
// out of either; the edit answers through scheduleWireFields.
// out of either; the edit answers through medicationDetail.
"lib/medications/update-medication/preflight.ts":
"schedule count and the primary schedule's id, derives nothing due",
"lib/medications/update-medication/write.ts":
+61 -35
View File
@@ -61,6 +61,8 @@ export function recordActivityVerbLine(
action: string,
name: string,
): string {
const refused = refusedAttemptVerb(t, action, name);
if (refused !== null) return refused;
switch (action) {
case "measurement.create":
return t("recordSharing.activityVerb.measurementCreate", { name });
@@ -342,6 +344,54 @@ export function recordActivityVerbLine(
case "health-record.export":
return t("recordSharing.activityVerb.healthRecordExport", { name });
/* ---------------------------------------------------------------- */
/* Acts a delegable module performs outside its own route file. */
/* */
/* The two below are filed by library modules the routes import — a */
/* background worker's completion row and a lifecycle reconciliation */
/* — and a guard that walked only `route.ts` could not see either. */
/* Both carry the actor through, so both reach the owner's feed. */
/* ---------------------------------------------------------------- */
case "medication.intake.import":
return t("recordSharing.activityVerb.medicationIntakeImportFinished", {
name,
});
case "medication.oneShot.reconciled":
return t("recordSharing.activityVerb.medicationOneShotReconciled", {
name,
});
case "anamnesis.fact.create":
return t("recordSharing.activityVerb.anamnesisFactCreate", { name });
case "customMetric.create":
return t("recordSharing.activityVerb.customMetricCreate", { name });
case "customMetric.update":
return t("recordSharing.activityVerb.customMetricUpdate", { name });
case "customMetric.delete":
return t("recordSharing.activityVerb.customMetricDelete", { name });
case "documents.inbound.store":
return t("recordSharing.activityVerb.documentStore", { name });
case "documents.inbound.update":
return t("recordSharing.activityVerb.documentUpdate", { name });
case "documents.inbound.discard":
return t("recordSharing.activityVerb.documentDiscard", { name });
default:
return t("recordSharing.activity.acted", { name });
}
}
/**
* The sentence for an attempt that was refused, or null when `action` is no
* refusal. Asked first by `recordActivityVerbLine`.
*/
function refusedAttemptVerb(
t: Translator,
action: string,
name: string,
): string | null {
switch (action) {
/* ---------------------------------------------------------------- */
/* Attempts that were REFUSED, and things that happened around them. */
/* */
@@ -407,47 +457,23 @@ export function recordActivityVerbLine(
return t("recordSharing.activityVerb.rejectedDoseImport", { name });
case "custom-metric.entry.create.validation-failed":
return t("recordSharing.activityVerb.rejectedTrackedValue", { name });
case "custom-metric.metric.create.validation-failed":
return t("recordSharing.activityVerb.rejectedTrackedValueTypeCreate", {
name,
});
case "custom-metric.metric.update.validation-failed":
return t("recordSharing.activityVerb.rejectedTrackedValueTypeUpdate", {
name,
});
case "medication.layout.validation-failed":
return t("recordSharing.activityVerb.rejectedMedicationLayout", { name });
// Not a validation failure: the request was well-formed and admitted,
// and the queue would not take it. The owner sees an import that never
// ran rather than one that was refused, because those need different
// next steps.
case "medication.intake.import.kickoff.denied":
return t("recordSharing.activityVerb.deniedDoseImport", { name });
/* ---------------------------------------------------------------- */
/* Acts a delegable module performs outside its own route file. */
/* */
/* The two below are filed by library modules the routes import — a */
/* background worker's completion row and a lifecycle reconciliation */
/* — and a guard that walked only `route.ts` could not see either. */
/* Both carry the actor through, so both reach the owner's feed. */
/* ---------------------------------------------------------------- */
case "medication.intake.import":
return t("recordSharing.activityVerb.medicationIntakeImportFinished", {
name,
});
case "medication.oneShot.reconciled":
return t("recordSharing.activityVerb.medicationOneShotReconciled", {
name,
});
case "anamnesis.fact.create":
return t("recordSharing.activityVerb.anamnesisFactCreate", { name });
case "customMetric.create":
return t("recordSharing.activityVerb.customMetricCreate", { name });
case "customMetric.update":
return t("recordSharing.activityVerb.customMetricUpdate", { name });
case "customMetric.delete":
return t("recordSharing.activityVerb.customMetricDelete", { name });
case "documents.inbound.store":
return t("recordSharing.activityVerb.documentStore", { name });
case "documents.inbound.update":
return t("recordSharing.activityVerb.documentUpdate", { name });
case "documents.inbound.discard":
return t("recordSharing.activityVerb.documentDiscard", { name });
default:
return t("recordSharing.activity.acted", { name });
return null;
}
}