mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
Merge remote-tracking branch 'origin/main' into fix/v1397-tz
This commit is contained in:
commit
ada490c7e9
57 files changed
+1485
-99
No files matched your search
@@ -24,6 +24,8 @@ vi.mock("@/hooks/use-auth", () => ({
|
||||
role: "USER",
|
||||
timezone: "Europe/Berlin",
|
||||
disableCoach: authState.disableCoach,
|
||||
// The server's module map mirrors the record's opt-out.
|
||||
modules: { coach: !authState.disableCoach },
|
||||
},
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
|
||||
@@ -34,6 +34,8 @@ vi.mock("@/hooks/use-auth", () => ({
|
||||
role: "USER",
|
||||
timezone: "Europe/Berlin",
|
||||
disableCoach: !coach.available,
|
||||
// The record's module map, which the section's gate reads.
|
||||
modules: { coach: coach.available },
|
||||
},
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
|
||||
@@ -14,6 +14,13 @@ vi.mock("next/navigation", () => ({
|
||||
usePathname: () => "/settings/coach",
|
||||
}));
|
||||
|
||||
// Past hydration only where a test says so: the gate reads the account once
|
||||
// mounted, and the pre-hydration pass shows every card.
|
||||
const mountState = { mounted: false };
|
||||
vi.mock("@/hooks/use-mounted", () => ({
|
||||
useMounted: () => mountState.mounted,
|
||||
}));
|
||||
|
||||
vi.mock("@tanstack/react-query", () => ({
|
||||
useQuery: () => ({ data: null, isLoading: false, refetch: vi.fn() }),
|
||||
useInfiniteQuery: () => ({ data: undefined, isLoading: false }),
|
||||
@@ -31,10 +38,18 @@ vi.mock("@/hooks/use-ai-capability", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const authState: { disableCoach: boolean } = { disableCoach: false };
|
||||
const authState: {
|
||||
disableCoach: boolean;
|
||||
modules: { coach?: boolean } | undefined;
|
||||
} = { disableCoach: false, modules: undefined };
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({
|
||||
user: { id: "u1", role: "USER", disableCoach: authState.disableCoach },
|
||||
user: {
|
||||
id: "u1",
|
||||
role: "USER",
|
||||
disableCoach: authState.disableCoach,
|
||||
modules: authState.modules,
|
||||
},
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
refetch: vi.fn(),
|
||||
@@ -83,3 +98,24 @@ describe("<CoachSection> — SSR smoke", () => {
|
||||
expect(html).not.toContain("settings.coach.activate.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<CoachSection> — the tuning follows the record, not the account", () => {
|
||||
it("hides the nudge when the record has the Coach off, whatever the viewer's own opt-out says", () => {
|
||||
// A guardian with the Coach on, inside a managed record that has it off:
|
||||
// `/me` publishes the viewer's own `disableCoach` and the RECORD's module
|
||||
// map. The record's answer decides.
|
||||
mountState.mounted = true;
|
||||
authState.disableCoach = false;
|
||||
authState.modules = { coach: false };
|
||||
try {
|
||||
expect(render()).not.toContain('id="coach-nudge"');
|
||||
authState.disableCoach = true;
|
||||
authState.modules = { coach: true };
|
||||
expect(render()).toContain('id="coach-nudge"');
|
||||
} finally {
|
||||
mountState.mounted = false;
|
||||
authState.disableCoach = false;
|
||||
authState.modules = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -17,8 +17,12 @@
|
||||
* 4. Coach memory — durable-fact review + forget controls.
|
||||
*
|
||||
* The nav entry is module-gated on `coach`. The preference + nudge cards
|
||||
* keep their own content gate on `!user.disableCoach`: hiding the Coach hides
|
||||
* its tuning. The memory (facts, stored conversations and reminders) is never
|
||||
* keep their own content gate on the same published module state
|
||||
* (`user.modules.coach`, resolved by the server for the record on screen
|
||||
* from that record's opt-out and the operator's switches): hiding the Coach
|
||||
* hides its tuning. The raw `disableCoach` column is the signed-in person's
|
||||
* own, so inside a managed or shared record it answered for the wrong
|
||||
* person. The memory (facts, stored conversations and reminders) is never
|
||||
* gated: it is the person's own record, readable and deletable whatever the
|
||||
* Coach's state. The reminders card turns read-only while the Coach is
|
||||
* unavailable and, with the Coach hidden, shows only when rows exist.
|
||||
@@ -39,6 +43,7 @@ import { CoachNudgeCard } from "@/components/settings/coach-nudge-card";
|
||||
import { CoachPrefsSection } from "@/components/settings/coach-prefs-section";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useMounted } from "@/hooks/use-mounted";
|
||||
import { isSurfaceVisible } from "@/lib/modules/surface";
|
||||
|
||||
export function CoachSection() {
|
||||
const { isAuthenticated, user } = useAuth();
|
||||
@@ -47,11 +52,13 @@ export function CoachSection() {
|
||||
// `useMounted()` to keep the hydration render matching the SSR HTML.
|
||||
const mounted = useMounted();
|
||||
const authed = mounted && isAuthenticated;
|
||||
// The preference + memory cards ride the same content gate as the rest
|
||||
// of the Coach surface: when the user has hidden the Coach there is no
|
||||
// assistant to tune or remember anything. (`mounted`-gated so the SSR +
|
||||
// hydration renders agree.)
|
||||
const coachEnabled = !mounted || !user?.disableCoach;
|
||||
// The preference + nudge cards ride the same content gate as the rest of
|
||||
// the Coach surface: when the record has the Coach hidden there is no
|
||||
// assistant to tune. Read from the server's module map for the record,
|
||||
// never recomputed from the account's own columns. (`mounted`-gated so the
|
||||
// SSR + hydration renders agree.)
|
||||
const coachEnabled =
|
||||
!mounted || isSurfaceVisible("settings:coach", user?.modules);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
Reference in new issue
Block a user