mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
fix(ai): keep setup invitations away from people who opted out
A person who switched both the Coach and AI analysis off under Settings, Modules still got invited to set AI up: the Insights hint, its copy in Settings, AI, the checklist row and the onboarding panel all keyed on `provider.canConfigure`, which only knows the record kind and the operator switch. The flag itself stays as it is, because the AI usage card and the consent request read it too and still serve document reading, lab photos and medication extraction. The three invitation readers now also check the module map through `hasOptedOutOfAi`.
This commit is contained in:
1 parent
aae029d94e
commit
de8eadbf2f
8 files changed
+108
-19
No files matched your search
@@ -62,8 +62,9 @@ vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: () => {}, replace: () => {} }),
|
||||
}));
|
||||
|
||||
const auth = vi.hoisted(() => ({ modules: {} as Record<string, boolean> }));
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({ user: { id: "u1" } }),
|
||||
useAuth: () => ({ user: { id: "u1", modules: auth.modules } }),
|
||||
}));
|
||||
|
||||
/**
|
||||
@@ -154,6 +155,13 @@ describe("<DoneScreen> AI panel", () => {
|
||||
providerState.canConfigure = true;
|
||||
});
|
||||
|
||||
it("leaves the panel out once the Coach and AI analysis are switched off", () => {
|
||||
auth.modules = { coach: false, insights: false };
|
||||
aiProviderState.data = undefined;
|
||||
expect(render()).not.toContain('data-slot="onboarding-ai-panel"');
|
||||
auth.modules = {};
|
||||
});
|
||||
|
||||
it("keeps every exit so the panel is fully skippable", () => {
|
||||
aiProviderState.data = undefined;
|
||||
const html = render();
|
||||
|
||||
@@ -153,6 +153,28 @@ describe("<GettingStartedChecklist> — rows for an established account", () =>
|
||||
};
|
||||
});
|
||||
|
||||
it("leaves the AI row out once the Coach and AI analysis are switched off", () => {
|
||||
// Nothing configured, so the row would be an open to-do: the person
|
||||
// declined AI in Settings, Modules, and the list does not ask again.
|
||||
providerState = {
|
||||
configured: false,
|
||||
managedBy: null,
|
||||
canConfigure: true,
|
||||
responseTimeoutMs: 60_000,
|
||||
};
|
||||
currentUser = user({ modules: { coach: false, insights: false } });
|
||||
expect(render()).toContain("4 of 5 done");
|
||||
// Either one still on keeps the row.
|
||||
currentUser = user({ modules: { coach: false, insights: true } });
|
||||
expect(render()).toContain("4 of 6 done");
|
||||
providerState = {
|
||||
configured: true,
|
||||
managedBy: "user",
|
||||
canConfigure: true,
|
||||
responseTimeoutMs: 60_000,
|
||||
};
|
||||
});
|
||||
|
||||
it("keeps the supporting queries enabled while the card can render", () => {
|
||||
currentUser = user();
|
||||
render();
|
||||
|
||||
@@ -28,6 +28,7 @@ import { setTourReferrer } from "@/components/onboarding/tour-launcher";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { useGrantAiConsent } from "@/hooks/use-grant-ai-consent";
|
||||
import { hasOptedOutOfAi } from "@/lib/ai/capabilities/types";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { markChecklistExpanded } from "@/lib/onboarding/checklist-storage";
|
||||
import type { OnboardingStateDto } from "@/lib/onboarding/needs";
|
||||
@@ -133,10 +134,12 @@ export function DoneScreen({ state }: { state: OnboardingStateDto }) {
|
||||
// The panel is one optional offer, shown only while this person could set
|
||||
// AI up here: `provider.canConfigure` is false while the operator has AI
|
||||
// switched off and inside a record somebody else owns. HealthLog is
|
||||
// complete without it, so with nothing to offer the screen simply goes
|
||||
// from "done" to the exits.
|
||||
// complete without it, so with nothing to offer (or once the person
|
||||
// switched the Coach and AI analysis off) the screen simply goes from
|
||||
// "done" to the exits.
|
||||
const providerState = useAiProviderState();
|
||||
const showAiPanel = providerState.canConfigure;
|
||||
const showAiPanel =
|
||||
providerState.canConfigure && !hasOptedOutOfAi(user?.modules);
|
||||
const { data: aiProvider } = useQuery<AiProviderStatus>({
|
||||
queryKey: queryKeys.userAiProvider(),
|
||||
queryFn: async () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { LucideIcon } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAccountOnceMounted } from "@/hooks/use-auth";
|
||||
import { hasOptedOutOfAi } from "@/lib/ai/capabilities/types";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useDashboardSnapshot } from "@/lib/queries/use-dashboard-snapshot";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
@@ -263,9 +264,10 @@ export function GettingStartedChecklist() {
|
||||
});
|
||||
|
||||
// The AI row reads the provider state `/api/auth/me` already carries:
|
||||
// present only while AI can be set up from here (`canConfigure`), done once
|
||||
// any provider is configured (a personal key, a local model, an OAuth
|
||||
// sign-in or the operator's shared key). No request of its own.
|
||||
// present only while AI can be set up from here (`canConfigure`) and the
|
||||
// person has not switched the Coach and AI analysis off, done once any
|
||||
// provider is configured (a personal key, a local model, an OAuth sign-in or
|
||||
// the operator's shared key). No request of its own.
|
||||
const aiProvider = useAiProviderState();
|
||||
|
||||
// v1.5: skip these two fetches once the card can't render at
|
||||
@@ -336,7 +338,8 @@ export function GettingStartedChecklist() {
|
||||
(channel) => channel?.enabled === true,
|
||||
);
|
||||
const insightsConfigured = aiProvider.configured;
|
||||
const aiConfigurable = aiProvider.canConfigure;
|
||||
const aiConfigurable =
|
||||
aiProvider.canConfigure && !hasOptedOutOfAi(user?.modules);
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
|
||||
Reference in new issue
Block a user