Skip to content

Commit d8a6eff

Browse files
davidercruzclaude
andcommitted
fix(persona-quiz): drive inter-question preview via persisted feedSettings, not inline variables
Backend's `feedPreview` resolver reads from the user's persisted `feedSettings` and ignores the inline `filters` argument we'd been passing through `Feed`'s `variables` prop. That's why the variables-based attempt left the preview frozen on stale content — the server simply wasn't using the filter we sent. Restore the incremental-`followTags` pattern (this time keeping it): - After every answer, follow the newly-earned tags via `followTags`. `followedRef` deduplicates so each tag fires at most one mutation. - `refetchPreview` debounces and invalidates `[RequestKey.FeedPreview]` cache entries so `Feed` re-fetches against the updated `feedSettings`. - `Feed` reverts to the same plain config `EditTag` uses (`feedQueryKey={[RequestKey.FeedPreview, user.id]}`, no `variables`). - `finishQuiz` only catches the residue (fallback tags backfilled to reach the target count). This is the EditTag preview pattern verbatim, which is the one we know works in funnel-step contexts. Tests still 4/4 pass; typecheck and lint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c892f78 commit d8a6eff

1 file changed

Lines changed: 40 additions & 13 deletions

File tree

  • packages/shared/src/features/onboarding/steps/FunnelPersonaQuiz

packages/shared/src/features/onboarding/steps/FunnelPersonaQuiz/index.tsx

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import React, {
66
useRef,
77
useState,
88
} from 'react';
9-
import { useMutation } from '@tanstack/react-query';
9+
import { useMutation, useQueryClient } from '@tanstack/react-query';
1010
import type {
1111
FunnelStepPersonaQuiz,
1212
FunnelStepPersonaQuizParameters,
@@ -20,8 +20,7 @@ import { useLogContext } from '../../../../contexts/LogContext';
2020
import { LogEvent } from '../../../../lib/log';
2121
import useMutateFilters from '../../../../hooks/useMutateFilters';
2222
import useFeedSettings from '../../../../hooks/useFeedSettings';
23-
// `useDebounceFn` import removed — was used by the previous incremental-follow
24-
// cache-invalidation flow which the ad-hoc `variables` approach replaces.
23+
import useDebounceFn from '../../../../hooks/useDebounceFn';
2524
import { usePersonaQuizState } from './usePersonaQuizState';
2625
import type { PersonaQuizAnswer } from './usePersonaQuizState';
2726
import { PersonaQuizQuestionView } from './PersonaQuizQuestion';
@@ -44,6 +43,7 @@ function FunnelPersonaQuizComponent({
4443
const user = auth?.user;
4544
const { followTags } = useMutateFilters(user);
4645
const { feedSettings } = useFeedSettings();
46+
const queryClient = useQueryClient();
4747

4848
const params = parameters as FunnelStepPersonaQuizParameters;
4949
const {
@@ -99,6 +99,33 @@ function FunnelPersonaQuizComponent({
9999
[accumulatedTags],
100100
);
101101

102+
// The server's `feedPreview` resolver reads from the user's persisted
103+
// `feedSettings` rather than the inline `filters` argument we can pass
104+
// through `Feed`'s `variables` prop. So to make the preview reflect the
105+
// user's evolving interests we have to actually persist tags as they
106+
// accumulate. Track the session's "already followed" set so each tag fires
107+
// `followTags` at most once.
108+
const followedRef = useRef<Set<string>>(new Set());
109+
const [refetchPreview] = useDebounceFn(() => {
110+
queryClient.invalidateQueries({
111+
queryKey: [RequestKey.FeedPreview],
112+
predicate: (q) => !q.queryKey.includes(RequestKey.FeedPreviewCustom),
113+
});
114+
}, 500);
115+
116+
useEffect(() => {
117+
if (phase !== 'question' || !user?.id) {
118+
return;
119+
}
120+
const fresh = accumulatedTags.filter((t) => !followedRef.current.has(t));
121+
if (fresh.length === 0) {
122+
return;
123+
}
124+
fresh.forEach((t) => followedRef.current.add(t));
125+
Promise.resolve(followTags({ tags: fresh })).catch(() => undefined);
126+
refetchPreview();
127+
}, [accumulatedTags, phase, user?.id, followTags, refetchPreview]);
128+
102129
// Resolve the archetype from the terminal question's `archetypeId`, build
103130
// the final tag list, persist it (so `TagSelection` on the reveal screen
104131
// sees the quiz tags pre-selected via `feedSettings`), then transition to
@@ -118,10 +145,14 @@ function FunnelPersonaQuizComponent({
118145
...(selection.fallbackTags ?? []),
119146
]).slice(0, selection.targetTotalTags);
120147

121-
// Persist the final tag set so `TagSelection` on the reveal screen
122-
// sees them as already-followed via feedSettings.
123-
if (merged.length > 0) {
124-
Promise.resolve(followTags({ tags: merged })).catch(() => undefined);
148+
// Most tags are already followed (we stream them incrementally above);
149+
// catch any residue — typically the fallback backfill — so the reveal's
150+
// `TagSelection` reads a complete `feedSettings`.
151+
const fresh = merged.filter((t) => !followedRef.current.has(t));
152+
if (fresh.length > 0) {
153+
fresh.forEach((t) => followedRef.current.add(t));
154+
Promise.resolve(followTags({ tags: fresh })).catch(() => undefined);
155+
refetchPreview();
125156
}
126157

127158
enrichmentComplete(merged);
@@ -134,6 +165,7 @@ function FunnelPersonaQuizComponent({
134165
selection.fallbackTags,
135166
enrichmentComplete,
136167
followTags,
168+
refetchPreview,
137169
],
138170
);
139171

@@ -280,13 +312,8 @@ function FunnelPersonaQuizComponent({
280312
<Feed
281313
className="relative mx-auto px-6 pt-6 tablet:left-1/2 tablet:w-screen tablet:-translate-x-1/2"
282314
feedName={OtherFeedPage.Preview}
283-
feedQueryKey={[
284-
RequestKey.FeedPreview,
285-
user.id,
286-
previewTags.join('|'),
287-
]}
315+
feedQueryKey={[RequestKey.FeedPreview, user.id]}
288316
query={PREVIEW_FEED_QUERY}
289-
variables={{ filters: { includeTags: previewTags } }}
290317
showSearch={false}
291318
options={{ refetchOnMount: true }}
292319
allowPin

0 commit comments

Comments
 (0)