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:
Marc-André Bombeck committed 2026-09-24 12:11:14 +02:00
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) => {
+6 -2
View File
@@ -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) => {