diff --git a/src/app/cycle/page.tsx b/src/app/cycle/page.tsx index a4ab8c9aa..ab7a29e57 100644 --- a/src/app/cycle/page.tsx +++ b/src/app/cycle/page.tsx @@ -4,6 +4,7 @@ import { Suspense, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/hooks/use-auth"; +import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import { PageAuthGate } from "@/components/ui/page-auth-gate"; import { CycleView } from "@/components/cycle/cycle-view"; @@ -22,9 +23,10 @@ import { CycleView } from "@/components/cycle/cycle-view"; */ export default function CyclePage() { const { user, isLoading, isAuthenticated } = useAuth(); + const { inSharedRecord } = useRecordCapabilities(); const router = useRouter(); - const enabled = user?.modules?.cycle !== false; + const enabled = inSharedRecord || user?.modules?.cycle !== false; useEffect(() => { if (isLoading) return; diff --git a/src/app/illness/[id]/page.tsx b/src/app/illness/[id]/page.tsx index 98e4c174c..c0275553a 100644 --- a/src/app/illness/[id]/page.tsx +++ b/src/app/illness/[id]/page.tsx @@ -14,6 +14,7 @@ import { PageAuthGate } from "@/components/ui/page-auth-gate"; import { IllnessEpisodeDetail } from "@/components/illness/illness-episode-detail"; import { BackLink } from "@/components/ui/back-link"; import { useAuth } from "@/hooks/use-auth"; +import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import { useMounted } from "@/hooks/use-mounted"; import { useTranslations } from "@/lib/i18n/context"; @@ -24,11 +25,12 @@ export default function IllnessEpisodePage({ }) { const { id } = use(params); const { user, isAuthenticated, isLoading } = useAuth(); + const { inSharedRecord } = useRecordCapabilities(); const mounted = useMounted(); const router = useRouter(); const { t } = useTranslations(); - const enabled = user?.modules?.illness === true; + const enabled = inSharedRecord || user?.modules?.illness === true; useEffect(() => { if (isLoading) return; diff --git a/src/app/illness/page.tsx b/src/app/illness/page.tsx index 6fef680cc..9511acf3c 100644 --- a/src/app/illness/page.tsx +++ b/src/app/illness/page.tsx @@ -4,6 +4,7 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/hooks/use-auth"; +import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import { PageAuthGate } from "@/components/ui/page-auth-gate"; import { IllnessView } from "@/components/illness/illness-view"; @@ -20,10 +21,11 @@ import { IllnessView } from "@/components/illness/illness-view"; */ export default function IllnessPage() { const { user, isLoading, isAuthenticated } = useAuth(); + const { inSharedRecord } = useRecordCapabilities(); const router = useRouter(); // Born-gated: enabled ONLY when the resolved map says `true`. - const enabled = user?.modules?.illness === true; + const enabled = inSharedRecord || user?.modules?.illness === true; useEffect(() => { if (isLoading) return; diff --git a/src/app/labs/page.tsx b/src/app/labs/page.tsx index 78adf9925..381bb1723 100644 --- a/src/app/labs/page.tsx +++ b/src/app/labs/page.tsx @@ -41,7 +41,7 @@ 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, canManage } = useRecordCapabilities(); + const { canAdd, canManage, inSharedRecord } = useRecordCapabilities(); const mounted = useMounted(); const router = useRouter(); const queryClient = useQueryClient(); @@ -60,7 +60,7 @@ export default function LabsPage() { // absent key reads as enabled, so a direct URL hit only bounces on an // explicit `false`. Every `/api/labs/*` route also enforces the gate // server-side, so this is a UX redirect, not the security boundary. - const enabled = user?.modules?.labs !== false; + const enabled = inSharedRecord || user?.modules?.labs !== false; const refreshVisible = useCallback( () => queryClient.invalidateQueries({ type: "active" }), diff --git a/src/app/medications/page-client.tsx b/src/app/medications/page-client.tsx index 5799fde99..d7dc51dd4 100644 --- a/src/app/medications/page-client.tsx +++ b/src/app/medications/page-client.tsx @@ -168,13 +168,14 @@ export default function MedicationsPageClient() { // to tapping each card in turn — so withholding it made a caregiver with // five morning tablets tap five times for nothing. The customize page stays // withheld; sharing does not cover it at all. - const { canAdd, canManage } = useRecordCapabilities(); + const { canAdd, canManage, inSharedRecord } = useRecordCapabilities(); // v1.18.1 (D3) — medications is an opt-out module. When the account has it // turned off the whole page disappears (the nav entry is hidden by the same // gate) and the list query never fires (the API would 403 anyway). // Default-on: an absent key reads as enabled, so the page only hides on an // explicit `false`. - const medicationsEnabled = user?.modules?.medications !== false; + const medicationsEnabled = + inSharedRecord || user?.modules?.medications !== false; const router = useRouter(); const queryClient = useQueryClient(); const searchParams = useSearchParams(); diff --git a/src/app/mental-wellbeing/page.tsx b/src/app/mental-wellbeing/page.tsx index 970860c68..c5ccc99f2 100644 --- a/src/app/mental-wellbeing/page.tsx +++ b/src/app/mental-wellbeing/page.tsx @@ -4,6 +4,7 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/hooks/use-auth"; +import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import { PageAuthGate } from "@/components/ui/page-auth-gate"; import { MentalWellbeing } from "@/components/mental-health/mental-wellbeing"; @@ -26,9 +27,10 @@ import { MentalWellbeing } from "@/components/mental-health/mental-wellbeing"; */ export default function MentalWellbeingPage() { const { user, isLoading, isAuthenticated } = useAuth(); + const { inSharedRecord } = useRecordCapabilities(); const router = useRouter(); - const enabled = user?.modules?.mentalHealth === true; + const enabled = inSharedRecord || user?.modules?.mentalHealth === true; useEffect(() => { if (isLoading) return; diff --git a/src/app/mood/page.tsx b/src/app/mood/page.tsx index 04e02a1c0..8668a82f7 100644 --- a/src/app/mood/page.tsx +++ b/src/app/mood/page.tsx @@ -21,7 +21,7 @@ import { useTranslations } from "@/lib/i18n/context"; export default function MoodPage() { const { user, isAuthenticated, isLoading } = useAuth(); - const { canManage } = useRecordCapabilities(); + const { canManage, inSharedRecord } = useRecordCapabilities(); const mounted = useMounted(); const router = useRouter(); const queryClient = useQueryClient(); @@ -37,7 +37,7 @@ export default function MoodPage() { // absent key reads as enabled, so a direct URL hit only bounces on an // explicit `false`. Every `/api/mood-entries/*` route also enforces the gate // server-side, so this is a UX redirect, not the security boundary. - const enabled = user?.modules?.mood !== false; + const enabled = inSharedRecord || user?.modules?.mood !== false; // v1.16.4 — PWA pull-to-refresh: a top-anchored touch pull refetches // whatever this page currently has mounted (`type: "active"` scopes the diff --git a/src/components/documents/documents-view.tsx b/src/components/documents/documents-view.tsx index ca4aa87e0..ac220790c 100644 --- a/src/components/documents/documents-view.tsx +++ b/src/components/documents/documents-view.tsx @@ -184,14 +184,15 @@ export function DocumentsView() { // about it are none of them delegated verbs. Inside somebody else's record // the vault reads and nothing more: no upload path, no selection, no bulk // bar, no corpus backfill. - const { canManage } = useRecordCapabilities(); + const { canManage, inSharedRecord, sections } = useRecordCapabilities(); const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const queryClient = useQueryClient(); const coachLaunch = useCoachLaunch(); - const moduleEnabled = user?.modules?.inboundDocuments === true; + const moduleEnabled = + inSharedRecord || user?.modules?.inboundDocuments === true; // UX redirect only — the API routes enforce the gate server-side. useEffect(() => { @@ -377,7 +378,10 @@ export function DocumentsView() { ); const { dropActive } = usePageFileDrop(canManage ? enqueueFiles : undefined); - const episodes = useIllnessEpisodes(true); + const episodes = useIllnessEpisodes( + true, + !inSharedRecord || sections === null || sections.includes("illness"), + ); // Content-search coverage: fire the corpus backfill when indexing is // available and some documents are not yet searchable. The list search diff --git a/src/components/illness/use-illness.ts b/src/components/illness/use-illness.ts index 70be6ef08..4bd5dd642 100644 --- a/src/components/illness/use-illness.ts +++ b/src/components/illness/use-illness.ts @@ -25,9 +25,10 @@ import type { } from "./types"; /** Newest-first episode list. */ -export function useIllnessEpisodes(includeResolved = true) { +export function useIllnessEpisodes(includeResolved = true, enabled = true) { return useQuery({ queryKey: queryKeys.illnessEpisodes(includeResolved), + enabled, queryFn: () => apiGet( `/api/illness/episodes?includeResolved=${includeResolved}`,