mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
fix(ai): refresh the account's AI answer after every change to its inputs
Every AI surface reads its capability from the ai block on /api/auth/me, but setting up or removing a provider, signing in to or out of Codex, saving the fallback chain, the central-Codex opt-in, the local-OCR switch, a minted consent receipt, the admin's server key and central Codex, and a guardian's save inside a managed record evicted only their own reads. The surfaces stayed on the old answer until a reload. All of these writes, and the ones that already refreshed the payload (consent, modules, Hide Coach, the operator's switches), now evict one bundle, aiInputDependentKeys. A structural test holds every client write to those endpoints to it.
This commit is contained in:
1 parent
03ecb5bd52
commit
676bb2eff7
23 files changed
+260
-62
No files matched your search
@@ -14,7 +14,11 @@ import { Label } from "@/components/ui/label";
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import { apiGet, apiPut } from "@/lib/api/api-fetch";
|
||||
import { PasswordInput } from "./_shared";
|
||||
|
||||
@@ -74,6 +78,8 @@ export function AiServerKeySection() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.adminAiServerKey(),
|
||||
});
|
||||
// The server key is a provider input of the admin's own AI answer.
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
toast.success(t("admin.aiServerKey.saved"));
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
|
||||
@@ -7,7 +7,11 @@ import { toast } from "sonner";
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import { SettingsToggle } from "./_shared";
|
||||
import { apiGet, apiPut } from "@/lib/api/api-fetch";
|
||||
|
||||
@@ -74,7 +78,7 @@ function useUpdateAssistantFlags() {
|
||||
// Every record's resolved AI capabilities ride the account payload,
|
||||
// which is the only thing the web reads them from; bust it so the
|
||||
// operator sees the change within the session.
|
||||
client.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
void invalidateKeys(client, aiInputDependentKeys);
|
||||
toast.success(t("common.saved"));
|
||||
},
|
||||
onError: (err) => {
|
||||
|
||||
@@ -30,7 +30,11 @@ import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { apiFetchRaw, apiGet } from "@/lib/api/api-fetch";
|
||||
import { formatDateTime } from "@/lib/format";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
|
||||
interface CentralCodexStatus {
|
||||
status: string;
|
||||
@@ -100,6 +104,7 @@ export function CentralCodexSection() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.adminCentralCodex(),
|
||||
});
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
return;
|
||||
}
|
||||
if (!cancelled) setTimeout(tick, intervalMs);
|
||||
@@ -135,6 +140,7 @@ export function CentralCodexSection() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.adminCentralCodex(),
|
||||
});
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
} catch (err) {
|
||||
toast.error(
|
||||
err instanceof Error ? err.message : t("admin.centralCodex.error"),
|
||||
|
||||
@@ -8,7 +8,11 @@ import { toast } from "sonner";
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import {
|
||||
MODULE_KEYS,
|
||||
MODULE_REGISTRY,
|
||||
@@ -66,7 +70,7 @@ function useUpdateModuleAvailability() {
|
||||
client.setQueryData(queryKeys.adminModuleAvailability(), data);
|
||||
// The resolved `/api/auth/me` module map depends on this operator
|
||||
// layer; bust it so the operator sees the change within the session.
|
||||
client.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
void invalidateKeys(client, aiInputDependentKeys);
|
||||
toast.success(t("common.saved"));
|
||||
},
|
||||
onError: (err) => {
|
||||
|
||||
@@ -16,7 +16,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiFetch, apiGet, apiPatch, apiPost } from "@/lib/api/api-fetch";
|
||||
import { ocrImageToText } from "@/lib/labs/local-ocr";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import type {
|
||||
OcrCapabilityDto,
|
||||
OcrCommitResponseDto,
|
||||
@@ -149,6 +153,8 @@ export function useUpdateLabsLocalOcr() {
|
||||
queryClient.setQueryData(queryKeys.labsLocalOcr(), data);
|
||||
// The toggle changes whether text-mode scanning is available.
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.ocrCapability() });
|
||||
// It is also a provider input of the `labsOcr` capability on `/me`.
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,11 @@ import { useOnboardingAnswer } from "@/components/onboarding/use-onboarding-flow
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useAccountSwitch } from "@/hooks/use-account-switch";
|
||||
import { setTourReferrer } from "@/components/onboarding/tour-launcher";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import { apiFetchRaw, apiGet } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { markChecklistExpanded } from "@/lib/onboarding/checklist-storage";
|
||||
@@ -162,9 +166,7 @@ export function DoneScreen({ state }: { state: OnboardingStateDto }) {
|
||||
},
|
||||
onSuccess: async () => {
|
||||
setJustGranted(true);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.userAiProvider(),
|
||||
});
|
||||
await invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.aiConsentReceipt("ai_full"),
|
||||
});
|
||||
|
||||
@@ -39,7 +39,11 @@ import { QueryErrorRow } from "@/components/ui/query-error-row";
|
||||
import { apiFetchRaw } from "@/lib/api/api-fetch";
|
||||
import { formatDateTime } from "@/lib/format";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
|
||||
type ConsentReceiptWire = {
|
||||
id: string;
|
||||
@@ -86,10 +90,7 @@ export function AiConsentCard({
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.aiConsentReceipt("ai_full"),
|
||||
});
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.insightsProviderChain(),
|
||||
});
|
||||
await invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -110,10 +111,7 @@ export function AiConsentCard({
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.aiConsentReceipt("ai_full"),
|
||||
});
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.insightsProviderChain(),
|
||||
});
|
||||
await invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -292,7 +290,7 @@ function DocumentReadingConsentRow({
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.aiConsentReceipt("ai_extraction"),
|
||||
});
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
await invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,11 @@ import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { apiFetchRaw } from "@/lib/api/api-fetch";
|
||||
import { formatDateTime } from "@/lib/format";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
|
||||
import { AdminOpenAIProviderForm } from "./admin-openai-provider-form";
|
||||
import { AiConsentCard } from "./ai-consent-card";
|
||||
@@ -127,10 +131,20 @@ export function AiInsightsCard({
|
||||
// settings UI. Runs once per mount of the AI-settings surface.
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) return;
|
||||
void apiFetchRaw("/api/consent/ai/web", { method: "POST" }).catch(() => {
|
||||
/* best-effort heal — the explicit grant path stays available */
|
||||
});
|
||||
}, [isAuthenticated]);
|
||||
// A heal that minted a receipt changed an input of the account's AI
|
||||
// capability answer, so `/me` re-resolves; a no-op heal leaves it alone.
|
||||
void apiFetchRaw("/api/consent/ai/web", { method: "POST" })
|
||||
.then(async (res) => {
|
||||
if (!res.ok) return;
|
||||
const body = (await res.json()) as { data?: { minted?: boolean } };
|
||||
if (body.data?.minted) {
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* best-effort heal — the explicit grant path stays available */
|
||||
});
|
||||
}, [isAuthenticated, queryClient]);
|
||||
|
||||
// The Select is URL-driven so the SSR test can pick the branch and
|
||||
// a deep link works. Default = `?provider=…` query param when the
|
||||
|
||||
@@ -15,7 +15,7 @@ import { NativeSelect } from "@/components/ui/native-select";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import {
|
||||
ANTHROPIC_MODEL_PRESETS,
|
||||
@@ -79,8 +79,7 @@ export function AnthropicProviderForm({
|
||||
setOk(true);
|
||||
setMsg(t("settings.ai.saved"));
|
||||
setApiKey("");
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.userAiProvider() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
onError: (e) => {
|
||||
setOk(false);
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import type { InsightsSettings } from "./shared";
|
||||
|
||||
@@ -51,11 +51,9 @@ export function CentralCodexSwitch({
|
||||
}),
|
||||
onSuccess: () => {
|
||||
// The opt-in changes which providers can serve this user — refresh the
|
||||
// settings summary (which carries the flag) and every insight read.
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.insightsSettings(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
// settings summary (which carries the flag), the account's AI
|
||||
// capability answer, and every insight read.
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
setPendingEnable(false);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ConfirmButton } from "@/components/ui/confirm-button";
|
||||
import { apiFetchRaw } from "@/lib/api/api-fetch";
|
||||
import { formatDateTime } from "@/lib/format";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import type { InsightsSettings } from "./shared";
|
||||
|
||||
@@ -55,7 +55,7 @@ export function CodexProviderForm({
|
||||
url.searchParams.delete("codex_error");
|
||||
window.history.replaceState({}, "", url.toString());
|
||||
if (oauthOutcome.kind === "connected") {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
}
|
||||
}, [oauthOutcome, queryClient]);
|
||||
|
||||
@@ -112,7 +112,7 @@ export function CodexProviderForm({
|
||||
setDevicePolling(false);
|
||||
setMsg(t("settings.codexConnected"));
|
||||
setMsgType("success");
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
return;
|
||||
}
|
||||
if (!cancelled) setTimeout(tick, intervalMs);
|
||||
@@ -151,7 +151,7 @@ export function CodexProviderForm({
|
||||
}
|
||||
setMsg(t("settings.codexDisconnected"));
|
||||
setMsgType("success");
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
} catch (err) {
|
||||
setMsg(err instanceof Error ? err.message : t("settings.savingError"));
|
||||
setMsgType("error");
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import { uiToLegacyProviderEnum, type UserAIProvider } from "./shared";
|
||||
|
||||
@@ -63,8 +63,7 @@ export function CompatProviderForm({
|
||||
setOk(true);
|
||||
setMsg(t("settings.ai.saved"));
|
||||
setApiKey("");
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.userAiProvider() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
onError: (e) => {
|
||||
setOk(false);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* a pure UI inversion over the same column.
|
||||
*
|
||||
* Persists via `PATCH /api/auth/me/disable-coach`; the response invalidates
|
||||
* `queryKeys.authMe()` so every Coach mount point on the client
|
||||
* `aiInputDependentKeys` (the account payload first) so every Coach mount point on the client
|
||||
* (`<LayoutCoachFab>`, `<LayoutCoachMount>`, the inline `<CoachLaunchButton>`
|
||||
* pill, the `/targets` page CTAs) re-renders with the new `user.disableCoach`
|
||||
* value on the next React Query refetch tick — no full reload required.
|
||||
@@ -39,7 +39,7 @@ import { apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useMounted } from "@/hooks/use-mounted";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
export function DisableCoachCard({
|
||||
isAuthenticated,
|
||||
@@ -115,7 +115,7 @@ export function DisableCoachCard({
|
||||
// Surface the new value to every Coach gate via the shared
|
||||
// `useAuth()` query — the gates re-render once React Query
|
||||
// re-fetches /api/auth/me with the updated `disableCoach` field.
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
setOptimistic(null);
|
||||
scheduleClear();
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ import { NativeSelect } from "@/components/ui/native-select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { apiPut } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import {
|
||||
DEFAULT_CHAIN,
|
||||
@@ -99,7 +99,7 @@ export function FallbackChainCard({
|
||||
onSuccess: () => {
|
||||
setOk(true);
|
||||
setMsg(t("settings.ai.providerChain.saved"));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
onError: (e) => {
|
||||
setOk(false);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { NativeSelect } from "@/components/ui/native-select";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import {
|
||||
CUSTOM_MODEL_SENTINEL,
|
||||
@@ -79,8 +79,7 @@ export function LocalProviderForm({
|
||||
setOk(true);
|
||||
setMsg(t("settings.ai.saved"));
|
||||
setApiKey("");
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.userAiProvider() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
onError: (e) => {
|
||||
setOk(false);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { NativeSelect } from "@/components/ui/native-select";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import {
|
||||
CUSTOM_MODEL_SENTINEL,
|
||||
@@ -86,8 +86,7 @@ export function OpenAIProviderForm({
|
||||
setOk(true);
|
||||
setMsg(t("settings.ai.saved"));
|
||||
setApiKey("");
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.userAiProvider() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
onError: (e) => {
|
||||
setOk(false);
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { aiInputDependentKeys, invalidateKeys } from "@/lib/query-keys";
|
||||
|
||||
import type { UserAIProvider } from "./shared";
|
||||
|
||||
@@ -69,7 +69,7 @@ export function ResponseTimeoutCard({
|
||||
onSuccess: () => {
|
||||
setOk(true);
|
||||
setMsg(t("settings.ai.saved"));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.userAiProvider() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
onError: (e) => {
|
||||
setOk(false);
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
type InsightsSectionId,
|
||||
} from "@/lib/insights-layout";
|
||||
import { MODULE_REGISTRY } from "@/lib/modules/registry";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import {
|
||||
assertRecordSettingsResponseForRecord,
|
||||
MANAGED_RECORD_SETTINGS_MODULE_DEFAULTS,
|
||||
@@ -1074,6 +1078,10 @@ export function ManagedRecordSettingsSection({
|
||||
queryKeys.recordSettingsFamily(recordId, family),
|
||||
response,
|
||||
);
|
||||
// The record's module map, Hide Coach and profile (gender gates the
|
||||
// cycle module) are inputs of the `/me` answer for this record, its AI
|
||||
// capabilities included.
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -44,7 +44,11 @@ import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { formatDate, formatDateTime } from "@/lib/format";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import { apiFetchRaw, apiGet, apiPatch } from "@/lib/api/api-fetch";
|
||||
|
||||
interface McpTokenInfo {
|
||||
@@ -211,7 +215,7 @@ function McpEnableCard() {
|
||||
mutationFn: async (next: boolean) =>
|
||||
apiPatch("/api/auth/me/modules", { mcp: next }),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
toast.success(t("settings.sections.modules.saved"));
|
||||
},
|
||||
onError: () => toast.error(t("settings.sections.modules.error")),
|
||||
|
||||
@@ -71,7 +71,11 @@ import {
|
||||
withBaseToken,
|
||||
isConflict,
|
||||
} from "@/lib/api/optimistic-token";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
aiInputDependentKeys,
|
||||
invalidateKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import {
|
||||
MODULE_REGISTRY,
|
||||
isCodeDisabledModule,
|
||||
@@ -170,7 +174,8 @@ export function ModulesSection() {
|
||||
// Insights pills, dashboard tiles, quick-add, search. The two delegated
|
||||
// keys also feed dedicated settings surfaces, so evict their reads too
|
||||
// (mirrors the canonical Coach / cycle cards' own invalidation).
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.authMe() });
|
||||
// The module map is also an input of the AI capability answer.
|
||||
void invalidateKeys(queryClient, aiInputDependentKeys);
|
||||
if (moduleDelegatesTo(vars.key) === "cycle") {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.cyclePrefs(),
|
||||
|
||||
Reference in new issue
Block a user