diff --git a/messages/de.json b/messages/de.json index ae2bbef23..52c9e2293 100644 --- a/messages/de.json +++ b/messages/de.json @@ -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", diff --git a/messages/en.json b/messages/en.json index 0c7e01d4f..00826c3db 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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", diff --git a/messages/es.json b/messages/es.json index c0eb52e19..ecc6d0e7c 100644 --- a/messages/es.json +++ b/messages/es.json @@ -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", diff --git a/messages/fr.json b/messages/fr.json index 1ae591455..69fbcdfb8 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -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", diff --git a/messages/it.json b/messages/it.json index 9e30bf5ca..211211b5a 100644 --- a/messages/it.json +++ b/messages/it.json @@ -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", diff --git a/messages/ko.json b/messages/ko.json index 0197d663d..5069c1d45 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -12719,6 +12719,9 @@ "rejectedDoseUpdate": "{name} 님이 복용 기록을 수정하려다 거절됐어요", "rejectedDoseImport": "{name} 님이 복용 기록을 불러오려다 거절됐어요", "rejectedTrackedValue": "{name} 님이 추적 값을 기록하려다 거절됐어요", + "rejectedTrackedValueTypeCreate": "{name} 님이 추적 값 종류를 추가하려다 거절됐어요", + "rejectedTrackedValueTypeUpdate": "{name} 님이 추적 값 종류를 수정하려다 거절됐어요", + "rejectedMedicationLayout": "{name} 님이 약 목록 순서를 바꾸려다 거절됐어요", "deniedDoseImport": "{name} 님이 시작한 복용 기록 불러오기를 대기열에 넣지 못했어요", "anamnesisFactCreate": "{name} 님이 건강 배경 항목을 추가했어요", "customMetricCreate": "{name} 님이 추적 값 종류를 추가했어요", diff --git a/messages/nl.json b/messages/nl.json index 86a5440ec..f091c3c1d 100644 --- a/messages/nl.json +++ b/messages/nl.json @@ -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", diff --git a/messages/pl.json b/messages/pl.json index 916c2239a..947424424 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -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", diff --git a/messages/pt-BR.json b/messages/pt-BR.json index fcd90cced..277297c97 100644 --- a/messages/pt-BR.json +++ b/messages/pt-BR.json @@ -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", diff --git a/src/__tests__/audit-write-path-guard.test.ts b/src/__tests__/audit-write-path-guard.test.ts index 43f6d5bf9..32fda84fc 100644 --- a/src/__tests__/audit-write-path-guard.test.ts +++ b/src/__tests__/audit-write-path-guard.test.ts @@ -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"); }); diff --git a/src/__tests__/baselines/source-file-lines.json b/src/__tests__/baselines/source-file-lines.json index 822bd738b..434124a03 100644 --- a/src/__tests__/baselines/source-file-lines.json +++ b/src/__tests__/baselines/source-file-lines.json @@ -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, diff --git a/src/__tests__/baselines/source-function-lines.json b/src/__tests__/baselines/source-function-lines.json index d7c1b5530..4dcbcf91c 100644 --- a/src/__tests__/baselines/source-function-lines.json +++ b/src/__tests__/baselines/source-function-lines.json @@ -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, diff --git a/src/__tests__/medication-intake-tracking-guard.test.ts b/src/__tests__/medication-intake-tracking-guard.test.ts index 0558a4887..d84b41aae 100644 --- a/src/__tests__/medication-intake-tracking-guard.test.ts +++ b/src/__tests__/medication-intake-tracking-guard.test.ts @@ -115,10 +115,17 @@ const MAY_READ_EVERY_ROW: Record = { "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": diff --git a/src/lib/record-activity/activity-verb.ts b/src/lib/record-activity/activity-verb.ts index 1b37e5254..cbaae53b6 100644 --- a/src/lib/record-activity/activity-verb.ts +++ b/src/lib/record-activity/activity-verb.ts @@ -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; } }