Ai-settings: invalidate inherited org caches on tenant-level saves - #147
Ai-settings: invalidate inherited org caches on tenant-level saves#147romanivan-flamingo wants to merge 2 commits into
Conversation
Tenant-level saves (CLIENT AI config, default ClientView, guardrails template activation/custom policy) now also drop the org-scoped query caches that resolve effective values from the tenant default, so mounted customer pages update without a reload (ClickUp 86ajn8hnq). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe changes add shared query-key prefixes and broaden cache invalidation for tenant-default and CLIENT AI settings. Organization guardrails policy mutations also invalidate organization guardrails caches. Hook documentation reflects the updated behavior. ChangesAI settings cache invalidation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(app)/settings/ai-settings/components/ai-settings-view.tsx:
- Around line 158-160: Update the save flow containing the client-view query
invalidation to await both save mutations settling before invalidating
clientViewQueryKeys.all, replacing the fail-fast Promise.all behavior with
Promise.allSettled or equivalent. Preserve the existing cleanup/invalidation
behavior, and await queryClient.invalidateQueries if the flow must complete
after active queries refetch.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0585b8e6-a28f-437c-a08b-ac7aa4576f2f
📒 Files selected for processing (8)
src/app/(app)/settings/ai-settings/components/ai-settings-view.tsxsrc/app/(app)/settings/ai-settings/components/guardrails/use-guardrails-policies.tssrc/app/(app)/settings/ai-settings/components/guardrails/use-organization-guardrails.tssrc/app/(app)/settings/ai-settings/hooks/.use-agent-ai-config.mdsrc/app/(app)/settings/ai-settings/hooks/.use-client-view.mdsrc/app/(app)/settings/ai-settings/hooks/use-agent-ai-config.tssrc/app/(app)/settings/ai-settings/hooks/use-client-view.tssrc/app/(app)/settings/ai-settings/hooks/use-organization-ai-config.ts
| // Tenant-default save: also drops per-org entries so customer pages | ||
| // inheriting the default appearance refresh without a reload. | ||
| queryClient.invalidateQueries({ queryKey: clientViewQueryKeys.all }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
fd -a 'ai-settings-view\.tsx|client-view-query|useUpdateClientView|clientViewQueryKeys' . | sed 's#^\./##' | head -200
echo
echo "== target file outline/size =="
wc -l src/app/'(app)'/settings/ai-settings/components/ai-settings-view.tsx
ast-grep outline 'src/app/(app)/settings/ai-settings/components/ai-settings-view.tsx' --view compact || true
echo
echo "== relevant target snippets =="
sed -n '1,220p' 'src/app/(app)/settings/ai-settings/components/ai-settings-view.tsx'
echo
echo "== search for clientViewQueryKeys/useUpdateClientView definitions/usages =="
rg -n "clientViewQueryKeys|useUpdateClientView|invalidateOnSuccess|updateClientView|client-view|clientView" src -SRepository: flamingo-stack/openframe-oss-frontend
Length of output: 18297
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== use-client-view hook =="
sed -n '1,180p' 'src/app/(app)/settings/ai-settings/hooks/use-client-view.ts'
echo
echo "== customers usage snippets =="
for f in \
'src/app/(app)/customers/components/ai-assistant-appearance/customer-ai-assistant-appearance.tsx' \
'src/app/(app)/customers/components/customer-ai-configuration/customer-ai-configuration.tsx' \
'src/app/(app)/customers/components/customer-details-view.tsx'
do
echo "--- $f ---"
wc -l "$f"
rg -n "useUpdateClientView|useClientView|invalidateOnSuccess|commitAvatar|clientView" "$f" -C 8
done
echo
echo "== async sequencing behavioral probe =="
node - <<'JS'
function racePromiseAllBehavior() {
let viewMutationPending = false;
let invalidateCalled = false;
const aiSave = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('AI save failed')), 10);
});
const viewSave = new Promise((resolve) => {
viewMutationPending = true;
setTimeout(() => {
viewMutationPending = false;
resolve({ id: 'view-id' });
}, 50);
});
Promise.all([aiSave, viewSave])
.then(() => {
console.log('Promise.all resolved');
})
.catch(() => {
// This finally runs once aiSave rejects, while viewMutationPending is still true.
invalidateCalled = true;
console.log('finally ran while client-view mutation still pending:', viewMutationPending);
});
viewSave.then(() => {
// useUpdateClientView success handler with invalidateOnSuccess=false does not invalidate again.
console.log('client-view mutation later resolved; no invalidate from later success path');
});
setTimeout(() => console.log('invalidation called:', invalidateCalled), 120);
}
racePromiseAllBehavior();
JSRepository: flamingo-stack/openframe-oss-frontend
Length of output: 18433
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== AI config mutation hook definitions =="
fd -a 'use-agent-ai-config.tsx|use-ai-configuration' src/\(app\)/settings/ai-settings/hooks src | sed 's#^\./##'
for f in 'src/app/(app)/settings/ai-settings/hooks/use-agent-ai-config.tsx' 'src/app/(app)/settings/ai-settings/hooks/use-update-ai-configuration.tsx'
do
echo "--- $f ---"
if [ -f "$f" ]; then
wc -l "$f"
rg -n "useUpdateClientAiConfig|useUpdate.*AiConfig|useMutation|invalidate|onSuccess" "$f" -C 5
fi
done
echo
echo "== queryClient.invalidateQueries behavior probe =="
sed -n '1,220p' node_modules/@tanstack/query-core/build/legacy/queryClient.js 2>/dev/null || true
node - <<'JS'
const fs = require('fs');
const pkg = fs.readFileSync('package.json', 'utf8');
console.log('query package from lock/package context unavailable in read-only source inspection');
JSRepository: flamingo-stack/openframe-oss-frontend
Length of output: 8798
🌐 Web query:
TanStack Query invalidateQueries refetchQueries active queries background Promise returns
💡 Result:
In TanStack Query, invalidateQueries and refetchQueries serve different purposes, though both interact with the query cache [1][2]. invalidateQueries When you call invalidateQueries, TanStack Query marks the matching queries as stale [3][4]. This stale state immediately overrides any existing staleTime configuration [3][5]. By default, it also automatically triggers a background refetch for any queries currently marked as "active" (those with mounted observers) [3][1][6]. You can control which queries are refetched using the refetchType option, which defaults to 'active' [1][7][6]. refetchQueries Unlike invalidation, which is a "smarter" approach that merely marks data as stale and triggers refetching, refetchQueries forces an immediate refetch of the matching queries regardless of their stale state or whether they have active observers [8]. It is a more imperative command to update data [1][2]. Behavior with Active/Background Queries - Active Queries: Both methods can target active queries [3][1][9]. When refetching occurs in the background, TanStack Query continues to show the existing (stale) data until the new data is fetched, avoiding a "hard" loading state [10]. - Promise Returns: Both invalidateQueries and refetchQueries return a Promise [10][6]. If you await these calls, the Promise will resolve only after the triggered refetches have finished [10]. Summary of Options - refetchType: Use this option in invalidateQueries to change the default behavior (e.g., set to 'all' to refetch inactive queries too, or 'none' to only mark as invalid without refetching) [1][7][6]. - Filters: Both methods support QueryFilters to precisely target which queries should be acted upon (e.g., using queryKey, predicate functions, or activity status) [3][1][9].
Citations:
- 1: https://tanstack.com/query/latest/docs/reference/QueryClient?from=reactQueryV3
- 2: https://github.com/tanstack/query/blob/main/docs/reference/QueryClient.md
- 3: https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation
- 4: Documentation mismatch: invalidateQueries default behavior claims "all" but only refetches "active" queries TanStack/query#9531
- 5: https://tanstack.com/query/v4/docs/framework/react/guides/query-invalidation
- 6: https://tanstack.com/query/v5/docs/reference/QueryClient
- 7: https://tanstack.com/query/v4/docs/reference/QueryClient
- 8: refetch vs invalidating query TanStack/query#2468
- 9: https://tanstack-query.mintlify.app/guides/filters
- 10: Is it possible to await for refetches from `invalidateQueries`? TanStack/query#4521
Wait for both saves before invalidating client-view queries.
Promise.all rejects on the first failure, so this finally can invalidate clientViewQueryKeys.all while updateClientView is still pending. Later, useUpdateClientView(..., { invalidateOnSuccess: false }) does not invalidate when the view save resolves. As a result, active customer pages can refetch the old client view from an invalidation triggered before the pending save completes.
Use Promise.allSettled or equivalent so both mutations decide before invalidating. Await the invalidation if the save flow needs wait completion after active query refetches.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`(app)/settings/ai-settings/components/ai-settings-view.tsx around
lines 158 - 160, Update the save flow containing the client-view query
invalidation to await both save mutations settling before invalidating
clientViewQueryKeys.all, replacing the fail-fast Promise.all behavior with
Promise.allSettled or equivalent. Preserve the existing cleanup/invalidation
behavior, and await queryClient.invalidateQueries if the flow must complete
after active queries refetch.
Summary
Part of the live-customization work (ClickUp 86ajn8hnq): tenant-level AI-settings saves invalidated only their own react-query key, while org-scoped queries return the effective config (tenant defaults while
inheritDefaultis on) - so mounted customer pages kept showing pre-save values until a full reload.Changes
organization-client-ai-configcachesclient-viewfamily, including the manual invalidation on the avatar-commit path (invalidateOnSuccess: false)organization-guardrailscachesallkeys to the org-scoped query-key factories; updated the two sidecar docsOut of scope
Server push (NATS
config.changed) and per-setting in-flight semantics are a BE spec delivered separately; FE subscription and UI hints follow once BE lands.Testing
npm run type-checkcleanbiome checkclean on changed files🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation