Skip to content

Commit 9779fcb

Browse files
davidercruzclaude
andcommitted
feat(persona-quiz): depth-4 sub-personas for data + infra (48 personas)
Deepen the data and infra domains with a 4th branching level so the later answers actually steer the result: each of their 8 leaves now forks into 2 finer sub-personas (e.g. ML Research vs ML Training, Vector Search vs RAG Pipeline, Offensive vs Defensive AppSec), taking the quiz to 48 personas and ~11-question paths there (product/specialty stay at depth-3 / 8 questions). The split content (colour + branch + terminal questions and persona copy) is authored offline and baked into generatePersonaQuiz.mjs via DATA_SPLITS / INFRA_SPLITS; the validation gate still enforces vocabulary, reachability, >=4 branch points, and no duplicate prompts. The progress bar now uses the real per-path depth so shorter domains still fill it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 13076e6 commit 9779fcb

5 files changed

Lines changed: 3120 additions & 707 deletions

File tree

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,32 @@ function FunnelPersonaQuizComponent({
118118
[accumulatedTags],
119119
);
120120

121+
// Distance (in questions) from each node to its furthest terminal. Paths vary
122+
// in length by domain, so the progress bar uses this real depth rather than a
123+
// fixed cap to fill correctly on every path.
124+
const questionDepth = useMemo(() => {
125+
const byId = new Map(questions.map((q) => [q.id, q]));
126+
const memo = new Map<string, number>();
127+
function visit(qid: string): number {
128+
const cached = memo.get(qid);
129+
if (cached !== undefined) {
130+
return cached;
131+
}
132+
const q = byId.get(qid);
133+
if (!q) {
134+
return 1;
135+
}
136+
const nexts = [...new Set(q.options.map((o) => o.next))].filter(
137+
(n): n is string => Boolean(n),
138+
);
139+
const depth = nexts.length ? 1 + Math.max(...nexts.map(visit)) : 1;
140+
memo.set(qid, depth);
141+
return depth;
142+
}
143+
questions.forEach((q) => visit(q.id));
144+
return memo;
145+
}, [questions]);
146+
121147
// The server's `feedPreview` resolver reads from the user's persisted
122148
// `feedSettings` rather than the inline `filters` argument we can pass
123149
// through `Feed`'s `variables` prop. So to make the preview reflect the
@@ -397,7 +423,9 @@ function FunnelPersonaQuizComponent({
397423
<PersonaQuizQuestionView
398424
question={currentQuestion}
399425
step={answers.length + 1}
400-
totalSteps={selection.maxQuestions}
426+
totalSteps={
427+
answers.length + (questionDepth.get(currentQuestion.id) ?? 1)
428+
}
401429
onSelect={handleAnswer}
402430
/>
403431
{answers.length >= 1 && user?.id && previewTags.length > 0 && (

0 commit comments

Comments
 (0)