diff --git a/src/__tests__/sharing-legacy-client-contract.test.ts b/src/__tests__/sharing-legacy-client-contract.test.ts index 67eaa3d0b..63dd4f71e 100644 --- a/src/__tests__/sharing-legacy-client-contract.test.ts +++ b/src/__tests__/sharing-legacy-client-contract.test.ts @@ -161,12 +161,11 @@ describe("what the canonical readers make of the same payload", () => { expect(capabilities.sections).toEqual(fixture.sections); expect(capabilities.recordKind).toBe(fixture.recordKind); expect(capabilities.inSharedRecord).toBe(true); - // Resolved server-side and rendered, never re-derived: `canAdd` follows - // `canWrite`, and `canManage` follows the published `manageableDomains` - // list rather than the level — a delegate's management reach is the - // server's answer, section by section. + // Resolved server-side and rendered, never re-derived: `canWrite` is the + // published level, and both per-section answers follow the published + // lists rather than the level — a delegate's reach is the server's + // answer, section by section. expect(capabilities.canWrite).toBe(fixture.canWrite); - expect(capabilities.canAdd).toBe(fixture.canWrite); const manageable = active?.manageableDomains ?? []; expect(capabilities.canManage).toBe(manageable.length > 0); expect(manageable.length > 0).toBe(fixture.level === "manage"); diff --git a/src/app/labs/page.tsx b/src/app/labs/page.tsx index 768d7e5b5..8d6710f6e 100644 --- a/src/app/labs/page.tsx +++ b/src/app/labs/page.tsx @@ -44,7 +44,8 @@ export default function LabsPage() { // Scanning a report is not: it files a document and commits through the OCR // route, and neither is delegated. So a WRITE delegate keeps the manual add // and loses the choice menu around it. - const { canAdd, inSharedRecord } = useRecordCapabilities(); + const { canWriteDomain, inSharedRecord } = useRecordCapabilities(); + const canAddLab = canWriteDomain("labs"); const mounted = useMounted(); const router = useRouter(); const queryClient = useQueryClient(); @@ -152,7 +153,7 @@ export default function LabsPage() { - ) : canAdd ? ( + ) : canAddLab ? ( diff --git a/src/components/labs/lab-list.tsx b/src/components/labs/lab-list.tsx index 2f077bee4..e11c4ed98 100644 --- a/src/components/labs/lab-list.tsx +++ b/src/components/labs/lab-list.tsx @@ -93,7 +93,8 @@ function LabRangeBarSlot({ reading }: { reading: LabResultDto }) { export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { const { t } = useTranslations(); - const { canAdd } = useRecordCapabilities(); + const { canWriteDomain } = useRecordCapabilities(); + const canAddLab = canWriteDomain("labs"); const { prefs } = useModuleListPrefs("labs"); // v1.22 — a short, factual line under each marker heading describing what the @@ -184,7 +185,7 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { title={t("labs.emptyTitle")} description={t("labs.emptyDescription")} action={ - onAddFirst && canAdd ? ( + onAddFirst && canAddLab ? ( ) : undefined } diff --git a/src/components/layout/__tests__/capture-picker-discard.test.tsx b/src/components/layout/__tests__/capture-picker-discard.test.tsx index a2c0b3819..f701b3a94 100644 --- a/src/components/layout/__tests__/capture-picker-discard.test.tsx +++ b/src/components/layout/__tests__/capture-picker-discard.test.tsx @@ -18,7 +18,6 @@ vi.mock("@/hooks/use-record-capabilities", () => ({ useRecordCapabilities: () => ({ inSharedRecord: false, canWrite: false, - canAdd: true, canManage: true, canWriteDomain: () => true, canManageDomain: () => true, diff --git a/src/components/layout/__tests__/capture-picker.test.tsx b/src/components/layout/__tests__/capture-picker.test.tsx index 52a3fb878..ecb0d3463 100644 --- a/src/components/layout/__tests__/capture-picker.test.tsx +++ b/src/components/layout/__tests__/capture-picker.test.tsx @@ -11,7 +11,6 @@ vi.mock("@/hooks/use-record-capabilities", () => ({ useRecordCapabilities: () => ({ inSharedRecord: false, canWrite: false, - canAdd: true, canManage: true, canWriteDomain: () => true, canManageDomain: () => true, diff --git a/src/components/layout/capture-picker.tsx b/src/components/layout/capture-picker.tsx index 8e52a9318..6232e6f3d 100644 --- a/src/components/layout/capture-picker.tsx +++ b/src/components/layout/capture-picker.tsx @@ -55,11 +55,10 @@ type CaptureKind = "measurement" | "medication" | "mood"; * * One question per kind, asked of that kind's own section. The picker used to * ask a coarse one as well — a closed list of kinds a WRITE grant admits, - * offered whenever `canAdd` was true — and `canAdd` is bound to the grant's - * LEVEL with no scope term at all. A WRITE grant scoped to `["labs"]` - * published `canAdd: true` and got a weight form the server refuses; one - * scoped to `["documents"]`, which takes no delegated write at any level, got - * both. `writableDomains` is already the level × scope × route-table + * offered whenever the grant could write anywhere — and that answer is the + * grant's LEVEL with no scope term at all. A WRITE grant scoped to `["labs"]` + * read as true and got a weight form the server refuses; one scoped to + * `["documents"]`, which takes no delegated write at any level, got both. `writableDomains` is already the level × scope × route-table * intersection, so asking it per section answers both cases and the mood * entry's MANAGE-only create as well. */ diff --git a/src/components/measurement-reminders/__tests__/vorsorge-dashboard-card-error-state.test.tsx b/src/components/measurement-reminders/__tests__/vorsorge-dashboard-card-error-state.test.tsx index 584e016f4..74b48d6a2 100644 --- a/src/components/measurement-reminders/__tests__/vorsorge-dashboard-card-error-state.test.tsx +++ b/src/components/measurement-reminders/__tests__/vorsorge-dashboard-card-error-state.test.tsx @@ -29,7 +29,7 @@ vi.mock("@/hooks/use-encounters", () => ({ vi.mock("@/hooks/use-record-capabilities", () => ({ useRecordCapabilities: () => ({ canManage: true, - canAdd: true, + canWriteDomain: () => true, canManageDomain: () => true, }), })); diff --git a/src/components/measurements/measurement-list.tsx b/src/components/measurements/measurement-list.tsx index efa88d127..d85ad6a8c 100644 --- a/src/components/measurements/measurement-list.tsx +++ b/src/components/measurements/measurement-list.tsx @@ -292,8 +292,9 @@ export function MeasurementList({ // v1.36.x — inside somebody else's record, only the add path survives: // entering a reading is an admitted delegated write, editing and deleting // one are not. The row controls are dropped rather than disabled. - const { canAdd, canManage, inSharedRecord, canWrite } = + const { canWriteDomain, canManage, inSharedRecord, canWrite } = useRecordCapabilities(); + const canAddMeasurement = canWriteDomain("measurements"); const activeRecord = user?.accountAccess?.active ?? null; // Named only when the sentence below is owed: a delegate who may add but // not change. In the caller's own record, and for a read-only delegate who @@ -1068,7 +1069,7 @@ export function MeasurementList({ > {t("measurements.emptyResetFilter")} - ) : onAddFirst && canAdd ? ( + ) : onAddFirst && canAddMeasurement ? (