Skip to content

add quiz and progress - #16

Merged
MiradoAndreas merged 1 commit into
mainfrom
add-quiz-and-progress
Jul 28, 2026
Merged

add quiz and progress#16
MiradoAndreas merged 1 commit into
mainfrom
add-quiz-and-progress

Conversation

@MiradoAndreas

@MiradoAndreas MiradoAndreas commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added interactive quizzes to roadmap nodes, including multiple-choice questions, scoring, and pass/fail results.
    • Roadmap progress is now tracked, with nodes unlocking in sequence and completed nodes visibly marked.
    • Added locked-node states, completion indicators, and unlock/completion animations.
    • Added a node action dialog for learning or starting quizzes.
  • Bug Fixes
    • Improved roadmap node selection handling across desktop and mobile views.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds roadmap node ordering, persisted quiz and completion data, AI-generated French quizzes, pass-based unlocking, progress-aware node visuals, and a dialog-driven quiz interaction flow.

Changes

Roadmap progress and quizzes

Layer / File(s) Summary
Progress data and ordered unlocking
convex/lib/nodeOrder.ts, convex/roadmap/progress.ts, convex/roadmap/queries.ts, convex/schema.ts
Stores quizzes and node completion, derives canonical node order, and returns completed and linearly unlocked node IDs.
Quiz generation and submission
convex/roadmap/action.ts, convex/roadmap/internal.ts, convex/roadmap/mutation.ts
Retrieves or generates five-question quizzes, persists them, evaluates answers at an 80% threshold, and records passed nodes.
Progress-aware roadmap rendering
src/modules/roadmap/lib/*, src/modules/roadmap/ui/components/roadmap-flow*.tsx, src/app/globals.css
Passes progress into flow construction and displays locked, completed, recommendation, and transition animation states.
Node selection and quiz interaction
src/modules/roadmap/stores/quiz-ui-store.ts, src/modules/roadmap/ui/components/node-action-dialog.tsx, src/modules/my-roadmap/ui/sections/*, src/modules/my-roadmap/ui/views/*
Adds quiz dialog state, loads and submits quizzes, routes node clicks through menu or learning actions, and uses the minimal roadmap selection type.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Learner
  participant RoadmapDisplaySection
  participant NodeActionDialog
  participant getOrCreateNodeQuiz
  participant submitNodeQuiz
  Learner->>RoadmapDisplaySection: select unlocked roadmap node
  RoadmapDisplaySection->>NodeActionDialog: open node menu
  NodeActionDialog->>getOrCreateNodeQuiz: load node quiz
  getOrCreateNodeQuiz-->>NodeActionDialog: return questions
  Learner->>NodeActionDialog: submit answers
  NodeActionDialog->>submitNodeQuiz: evaluate answers
  submitNodeQuiz-->>NodeActionDialog: return score and pass status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding quiz functionality and roadmap progress tracking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-quiz-and-progress

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (5)
src/modules/roadmap/stores/quiz-ui-store.ts (1)

41-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

isSubmitting isn't reset on close.

Every other field is cleared here and in backToMenu, but isSubmitting is left as-is. It's recovered by the finally in handleSubmit today; resetting it here makes the store self-consistent regardless of caller.

🤖 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/modules/roadmap/stores/quiz-ui-store.ts` around lines 41 - 47, Update
closeDialog to reset isSubmitting to false alongside the other cleared quiz UI
state, matching the reset behavior in backToMenu and keeping the store
consistent regardless of how the dialog closes.
convex/roadmap/action.ts (2)

132-136: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No error handling or timeout around the model call.

Unlike generateQuestionsAction/submitAnswersAndGenerateRoadmap, this path lets provider errors propagate raw to the client. The dialog's .then(...).finally(...) has no .catch, so a failure leaves an empty quiz with no feedback. Wrap in try/catch and throw a ConvexError with a user-facing French message.

🤖 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 `@convex/roadmap/action.ts` around lines 132 - 136, Wrap the generateObject
call in the quiz-generation flow with try/catch, including an appropriate
timeout consistent with the existing model-call patterns. Catch provider
failures and throw a ConvexError containing a clear user-facing French message
so the dialog receives actionable feedback instead of a raw error.

82-92: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Constrain correctIndex to an integer.

z.number().min(0).max(3) accepts non-integer values like 1.5; switch to z.int().min(0).max(3) so the generated quiz only accepts a valid option index.

♻️ Proposed change
-        correctIndex: z.number().min(0).max(3),
+        correctIndex: z.int().min(0).max(3),
🤖 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 `@convex/roadmap/action.ts` around lines 82 - 92, Update the correctIndex field
in quizSchema to use Zod’s integer validator while preserving the existing 0–3
bounds, so only valid option indices are accepted.
convex/roadmap/mutation.ts (1)

57-73: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Scope the completion lookup by user.

The by_roadmap_and_node lookup ignores userId even though the inserted row carries one. It works today only because a roadmap has a single owner; if roadmaps ever become shared, one user's completion silently suppresses another's. Consider an index keyed on userId too.

🤖 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 `@convex/roadmap/mutation.ts` around lines 57 - 73, Update the completion
lookup in the passed branch to scope roadmapNodeProgress by userId as well as
roadmapId and nodeId. Use an index and query predicate that include
identity.subject, and update the schema/index definition if needed; preserve the
existing insert and duplicate-prevention behavior for each individual user.
src/modules/roadmap/ui/components/node-action-dialog.tsx (1)

207-230: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Answer options lack radio-group semantics.

Each option is a plain <button>; the selected state is conveyed only by border/background colour, so assistive tech can't announce which option is chosen, and there's no grouping between a question and its options. Use role="radiogroup" with role="radio" + aria-checked, and label the group with the question.

♻️ Sketch
-                <div key={questionIndex} className="flex flex-col gap-y-2">
-                  <p className="text-sm font-medium">
+                <fieldset key={questionIndex} className="flex flex-col gap-y-2">
+                  <legend className="text-sm font-medium">
                     {questionIndex + 1}. {q.question}
-                  </p>
-                  <div className="flex flex-col gap-y-1.5">
+                  </legend>
+                  <div role="radiogroup" className="flex flex-col gap-y-1.5">
                     {q.options.map((option, optionIndex) => (
                       <button
                         key={optionIndex}
                         type="button"
+                        role="radio"
+                        aria-checked={answers[questionIndex] === optionIndex}
                         onClick={() => setAnswer(questionIndex, optionIndex)}
🤖 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/modules/roadmap/ui/components/node-action-dialog.tsx` around lines 207 -
230, Update the question/options markup in the questions.map block to expose
radio-group semantics: label each options container with its question, assign
role="radiogroup" to that container, and assign role="radio" with aria-checked
reflecting answers[questionIndex] to each option button. Preserve the existing
selection handler and visual styling.
🤖 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 `@convex/roadmap/internal.ts`:
- Around line 22-35: The saveNodeQuiz mutation must be idempotent for each
roadmapId/nodeId pair: in convex/roadmap/internal.ts lines 22-35, query the
by_roadmap_and_node index and return the existing record’s _id before inserting;
in convex/roadmap/action.ts lines 117-145, preserve the fast-path read but use
the mutation’s returned id rather than assuming a new row was inserted.

In `@convex/roadmap/mutation.ts`:
- Around line 30-46: Enforce ordered unlocking on both server entry points: in
convex/roadmap/mutation.ts at lines 30-46, derive the unlocked set using the
same logic as getRoadmapProgress and reject nodeId before scoring when it is not
unlocked; in convex/roadmap/action.ts at lines 107-120, apply the same check
before invoking the model so locked nodes cannot trigger generation. Preserve
the existing ownership and quiz validation.

In `@convex/roadmap/progress.ts`:
- Around line 11-16: Update saveNodeQuiz to query the by_roadmap_and_node index
for an existing quiz matching roadmapId and nodeId, return that record without
inserting when found, and insert only when no record exists so concurrent calls
do not create duplicate cached quizzes.

In `@src/modules/my-roadmap/ui/sections/roadmap-display-section.tsx`:
- Around line 59-78: Update both direct onNodeClick calls in handleNodeClick to
pass a narrowed RoadmapNodeSelection-shaped object with node.id and a data.label
string, rather than the full `@xyflow/react` Node. Apply this in the completedSet
branch and the fallback branch while preserving the existing menu behavior and
click flow.

In `@src/modules/roadmap/lib/roadmap-node-selection.ts`:
- Line 1: Update the stale source path reference associated with
roadmap-node-selection.ts so it uses src/modules/roadmap/lib/ rather than
src/modules/my-roadmap/lib/. Preserve the file name and remaining path
unchanged.

In `@src/modules/roadmap/ui/components/node-action-dialog.tsx`:
- Around line 60-69: Add error state to the dialog component and handle failures
in both the quiz-loading promise after getOrCreateNodeQuiz and the handleSubmit
try block. Capture each error, update the error state with a user-visible
message, and retain the existing loading cleanup in finally so failures do not
leave unhandled rejections or silent empty states.
- Around line 53-58: Update the quiz state reset in the useEffect so result is
cleared whenever entering quiz mode, before the quizId early-return guard can
skip it. Preserve the existing quizId loading guard and ensure re-entering the
quiz renders questions instead of the previous score screen.

---

Nitpick comments:
In `@convex/roadmap/action.ts`:
- Around line 132-136: Wrap the generateObject call in the quiz-generation flow
with try/catch, including an appropriate timeout consistent with the existing
model-call patterns. Catch provider failures and throw a ConvexError containing
a clear user-facing French message so the dialog receives actionable feedback
instead of a raw error.
- Around line 82-92: Update the correctIndex field in quizSchema to use Zod’s
integer validator while preserving the existing 0–3 bounds, so only valid option
indices are accepted.

In `@convex/roadmap/mutation.ts`:
- Around line 57-73: Update the completion lookup in the passed branch to scope
roadmapNodeProgress by userId as well as roadmapId and nodeId. Use an index and
query predicate that include identity.subject, and update the schema/index
definition if needed; preserve the existing insert and duplicate-prevention
behavior for each individual user.

In `@src/modules/roadmap/stores/quiz-ui-store.ts`:
- Around line 41-47: Update closeDialog to reset isSubmitting to false alongside
the other cleared quiz UI state, matching the reset behavior in backToMenu and
keeping the store consistent regardless of how the dialog closes.

In `@src/modules/roadmap/ui/components/node-action-dialog.tsx`:
- Around line 207-230: Update the question/options markup in the questions.map
block to expose radio-group semantics: label each options container with its
question, assign role="radiogroup" to that container, and assign role="radio"
with aria-checked reflecting answers[questionIndex] to each option button.
Preserve the existing selection handler and visual styling.
🪄 Autofix (Beta)

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: f55cbf31-a981-4b48-b3ef-a550bcc5a055

📥 Commits

Reviewing files that changed from the base of the PR and between 41a7ab2 and 2c9ce34.

⛔ Files ignored due to path filters (1)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (18)
  • convex/lib/nodeOrder.ts
  • convex/roadmap/action.ts
  • convex/roadmap/internal.ts
  • convex/roadmap/mutation.ts
  • convex/roadmap/progress.ts
  • convex/roadmap/queries.ts
  • convex/schema.ts
  • src/app/globals.css
  • src/modules/my-roadmap/ui/sections/learn-chat-section.tsx
  • src/modules/my-roadmap/ui/sections/roadmap-display-section.tsx
  • src/modules/my-roadmap/ui/views/roadmap-learn-view.tsx
  • src/modules/roadmap/lib/build-roadmap-flow.ts
  • src/modules/roadmap/lib/roadmap-flow-node-animation.ts
  • src/modules/roadmap/lib/roadmap-node-selection.ts
  • src/modules/roadmap/stores/quiz-ui-store.ts
  • src/modules/roadmap/ui/components/node-action-dialog.tsx
  • src/modules/roadmap/ui/components/roadmap-flow-nodes.tsx
  • src/modules/roadmap/ui/components/roadmap-flow.tsx

Comment on lines +22 to +35
export const saveNodeQuiz = internalMutation({
args: {
roadmapId: v.id("roadmaps"),
nodeId: v.string(),
questions: v.array(quizQuestionValidator),
},
handler: async (ctx, { roadmapId, nodeId, questions }) => {
return await ctx.db.insert("roadmapNodeQuizzes", {
roadmapId,
nodeId,
questions,
});
},
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Non-idempotent saveNodeQuiz lets concurrent quiz requests create duplicate rows. The action reads and writes in two separate transactions, so overlapping requests for the same (roadmapId, nodeId) both miss and both insert; afterwards getNodeQuiz's .unique() throws permanently for that node.

  • convex/roadmap/internal.ts#L22-L35: re-query by_roadmap_and_node inside saveNodeQuiz and return the existing _id instead of inserting.
  • convex/roadmap/action.ts#L117-L145: keep the fast-path read, but rely on the idempotent mutation's returned id rather than assuming the insert created a fresh row.
📍 Affects 2 files
  • convex/roadmap/internal.ts#L22-L35 (this comment)
  • convex/roadmap/action.ts#L117-L145
🤖 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 `@convex/roadmap/internal.ts` around lines 22 - 35, The saveNodeQuiz mutation
must be idempotent for each roadmapId/nodeId pair: in convex/roadmap/internal.ts
lines 22-35, query the by_roadmap_and_node index and return the existing
record’s _id before inserting; in convex/roadmap/action.ts lines 117-145,
preserve the fast-path read but use the mutation’s returned id rather than
assuming a new row was inserted.

Comment on lines +30 to +46
handler: async (ctx, { roadmapId, nodeId, quizId, answers }) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Non authentifié");

const roadmap = await ctx.db.get(roadmapId);
if (!roadmap || roadmap.userId !== identity.subject) {
throw new Error("Introuvable");
}

const quiz = await ctx.db.get(quizId);
if (!quiz || quiz.roadmapId !== roadmapId || quiz.nodeId !== nodeId) {
throw new Error("Quiz introuvable");
}

if (answers.length !== quiz.questions.length) {
throw new Error("Réponses incomplètes");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Ordered unlocking is enforced only in the UI. Both server entry points verify roadmap ownership but never check that nodeId exists in the roadmap and is currently unlocked, so a direct API call can generate and pass quizzes for arbitrary future nodes.

  • convex/roadmap/mutation.ts#L30-L46: before scoring, derive the unlocked set (same logic backing getRoadmapProgress) and reject nodeIds that aren't unlocked.
  • convex/roadmap/action.ts#L107-L120: apply the same unlocked-node check before calling the model, so locked nodes can't trigger paid generation.
📍 Affects 2 files
  • convex/roadmap/mutation.ts#L30-L46 (this comment)
  • convex/roadmap/action.ts#L107-L120
🤖 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 `@convex/roadmap/mutation.ts` around lines 30 - 46, Enforce ordered unlocking
on both server entry points: in convex/roadmap/mutation.ts at lines 30-46,
derive the unlocked set using the same logic as getRoadmapProgress and reject
nodeId before scoring when it is not unlocked; in convex/roadmap/action.ts at
lines 107-120, apply the same check before invoking the model so locked nodes
cannot trigger generation. Preserve the existing ownership and quiz validation.

Comment on lines +11 to +16
// Quiz généré une seule fois par nœud (mis en cache, jamais régénéré)
export const roadmapNodeQuizzes = defineTable({
roadmapId: v.id("roadmaps"),
nodeId: v.string(),
questions: v.array(quizQuestionValidator),
}).index("by_roadmap_and_node", ["roadmapId", "nodeId"]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make quiz persistence idempotent.

The index does not enforce uniqueness, while the supplied saveNodeQuiz mutation always inserts. Concurrent generation can create multiple cached quizzes for one node, making retrieval ambiguous. Query by by_roadmap_and_node and return the existing record before inserting, within the same mutation.

🤖 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 `@convex/roadmap/progress.ts` around lines 11 - 16, Update saveNodeQuiz to
query the by_roadmap_and_node index for an existing quiz matching roadmapId and
nodeId, return that record without inserting when found, and insert only when no
record exists so concurrent calls do not create duplicate cached quizzes.

Comment on lines +59 to +78
const handleNodeClick = (node: Node) => {
const locked = Boolean(node.data.locked);
if (locked) return;

if (node.type === "topic" || node.type === "choice") {
const label = String(node.data.label ?? "");

if (completedSet.has(node.id)) {
// Déjà validé : plus besoin du menu, on va direct à l'explication.
onNodeClick(node);
return;
}

openNodeMenu(node.id, label);
return;
}

// "option" et "center" : jamais gatés, comportement normal
onNodeClick(node);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether tsc flags these assignments and inspect the Node data typing in use.
fd -t f 'tsconfig*.json' --exec cat {}
rg -nP --type=ts -C3 'RoadmapNodeSelection' src
rg -nP --type=ts -C5 'data\.label' src/modules/roadmap/lib

Repository: MiradoAndreas/learning-plateform-ai

Length of output: 5851


🏁 Script executed:

#!/bin/bash
set -u

echo "== roadmap-display-section outline =="
ast-grep outline src/modules/my-roadmap/ui/sections/roadmap-display-section.tsx --view expanded || true

echo "== roadmap-display-section relevant =="
sed -n '1,120p' src/modules/my-roadmap/ui/sections/roadmap-display-section.tsx | cat -n

echo "== roadmap-node-selection =="
cat -n src/modules/roadmap/lib/roadmap-node-selection.ts

echo "== onNodeClick usages in roadmap-learn-view =="
sed -n '1,80p' src/modules/my-roadmap/ui/views/roadmap-learn-view.tsx | cat -n

echo "== node labels and data in roadmap =="
rg -nP --type=ts -C4 'label|data:' src/modules my-roadmap 2>/dev/null || true

echo "== `@xyflow/react` deps =="
for f in package.json **/package.json; do [ -f "$f" ] && { echo "--- $f"; jq -c '.dependencies, .devDependencies' "$f" | rg -n '`@xyflow/react`|xyflow' || true; }; done
fd -t f 'package.json' -x sh -c 'echo "--- $1"; jq -c "\(.dependencies // {}) + \(.devDependencies // {})" "$1" | rg -n "`@xyflow/react`|xyflow" || true' sh {}

Repository: MiradoAndreas/learning-plateform-ai

Length of output: 50392


🏁 Script executed:

#!/bin/bash
set -u

echo "== roadmap-display-section outline =="
ast-grep outline src/modules/my-roadmap/ui/sections/roadmap-display-section.tsx --view expanded || true

echo "== roadmap-display-section relevant =="
sed -n '1,120p' src/modules/my-roadmap/ui/sections/roadmap-display-section.tsx | cat -n

echo "== roadmap-node-selection =="
cat -n src/modules/roadmap/lib/roadmap-node-selection.ts

echo "== learn-view node click relevant =="
sed -n '1,80p' src/modules/my-roadmap/ui/views/roadmap-learn-view.tsx | cat -n

echo "== onNodeClick/callgraph-ish usages in roadmap sections and views =="
rg -n --type=ts 'onNodeClick|handleNodeClick|RoadmapNodeSelection' src/modules/my-roadmap src/modules/roadmap 2>/dev/null | sed -n '1,160p'

echo "== package versions =="
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('.').glob('**/package.json'):
    if 'node_modules' in p.parts:
        continue
    try:
        data=json.loads(p.read_text())
        deps={**data.get('dependencies',{}), **data.get('devDependencies',{})}
        if any(k.startswith('`@xyflow/`') for k in deps):
            print(f'--- {p}')
            for k,v in deps.items():
                if k.startswith('`@xyflow/`'):
                    print(f'{k}: {v}')
    except Exception as e:
        ...
PY

echo "== check local lock/installed xyflow typing if present =="
fd -t f 'package-lock.json|pnpm-lock.yaml|yarn.lock|bun.lock' -x sh -c 'echo "--- $1"; rg -n "`@xyflow/react`|`@xyflow/system`" "$1" | sed -n "1,80p"' sh {}
find . -path '*/node_modules/@xyflow/*' -maxdepth 4 2>/dev/null | head -80

Repository: MiradoAndreas/learning-plateform-ai

Length of output: 10758


🏁 Script executed:

#!/bin/bash
set -u

echo "== roadmap-flow relevant =="
sed -n '1,100p' src/modules/roadmap/ui/components/roadmap-flow.tsx | cat -n

echo "== package manager files =="
ls -la package.json pnpm-lock.yaml 2>/dev/null | sed -n '1,40p'

echo "== xyflow typing in lock entries =="
python3 - <<'PY'
from pathlib import Path
p=Path('pnpm-lock.yaml')
text=p.read_text().splitlines()
for i,line in enumerate(text):
    if line.startswith('  `@xyflow/react`@12.11.2'):
        print('\n-- `@xyflow/react`@12.11.2 block --')
        for j in range(i, min(i+40, len(text))):
            print(f'{j+1}: {text[j]}')
        break
for i,line in enumerate(text):
    if line.startswith('  `@xyflow/system`@0.0.79'):
        print('\n-- `@xyflow/system`@0.0.79 block --')
        for j in range(i, min(i+40, len(text))):
            print(f'{j+1}: {text[j]}')
        break
PY

echo "== Node data declaration in installed xyflow if present =="
python3 - <<'PY'
from pathlib import Path
roots=[Path('node_modules')]
for root in roots:
    if not root.exists():
        continue
    for p in Path(root).glob('`@xyflow/react`*/dist/**/*.d.*'):
        if 'xy' in str(p).lower():
            continue
    for p in list(root.rglob('types.d.ts')) + list(root.rglob('index.d.ts')):
        if '`@xyflow`' not in str(p):
            continue
        txt=p.read_text(errors='ignore')
        if 'interface Node' in txt or 'type Node' in txt:
            print(f'-- {p} --')
            for i,line in enumerate(txt.splitlines(), 1):
                if 'interface Node' in line or 'type Node' in line:
                    for j in range(max(1,i-20), min(i+80, len(txt.splitlines())+1)):
                        print(f'{j}: {txt.splitlines()[j-1]}')
                    break
PY

Repository: MiradoAndreas/learning-plateform-ai

Length of output: 2566


🌐 Web query:

@xyflow/react v12.11.2 Node type data Record<string, unknown>

💡 Result:

In @xyflow/react v12.11.2, the Node type is defined with a generic constraint on its data property: NodeData extends Record<string, unknown> = Record<string, unknown> [1][2]. This means the data object must be an object-like structure that satisfies the Record<string, unknown> constraint [3]. Key points regarding this requirement: - Use type aliases over interfaces: Because of the Record<string, unknown> constraint, TypeScript may throw an error if you attempt to use an interface for your node data directly (as interfaces are open and may not strictly satisfy the index signature requirements in all contexts) [3]. Using a type alias is the recommended approach for defining custom node data [4][3]. - Constraint behavior: If you define a type alias and try to pass it to the Node generic, it must be compatible with Record<string, unknown>. If you encounter errors, ensure your data type does not contain properties that violate this constraint [3]. - Standard Usage: You define your custom node data by passing it as the first generic argument to the Node type [4][5]: type MyNodeData = { label: string; value: number }; type MyNode = Node<MyNodeData, 'my-custom-type'>; If you have complex requirements that make it difficult to satisfy Record<string, unknown>, you may need to ensure your type explicitly extends it or is structurally compatible [3].

Citations:


🌐 Web query:

TS2741 required property not present in assignment @xyflow/reactv12.11.2@xyflow/system v0.0.79

💡 Result:

The TypeScript error TS2741 indicates that you are attempting to assign an object to a variable or pass it as a prop that requires a specific property which is currently missing from your object [1]. In the context of @xyflow/react, this is a standard TypeScript error and not necessarily a bug specific to versions 12.11.2 or @xyflow/system 0.0.79. It occurs because the TypeScript definitions for React Flow components or hooks enforce that all mandatory properties (those without a? modifier) must be present [1]. To resolve this, check the following common causes: 1. Missing Required Property: Review the interface of the component or function you are using [1]. For example, if you are passing a node or edge, ensure you have provided all mandatory fields (such as id, position, or data, depending on the specific component's requirements). 2. Typo in Property Name: A common cause of TS2741 is a simple naming mismatch (e.g., using dataset instead of data) [2]. Ensure your property keys match the expected type definition exactly. 3. Partial Data Initialization: If you are building objects incrementally, the object may lack required properties during intermediate steps [3][1]. You can resolve this by: - Providing all required properties upfront [1]. - Using Partial while building the object and casting it to the final type only when it is complete [1]. - Marking the properties as optional in your own custom interfaces if they are intended to be optional [1]. If you are using specific hooks or components that were recently updated in the @xyflow/react v12.x series, verify that your implementation aligns with the latest exported types [4][5]. You can inspect the source types in your node_modules/@xyflow/react/dist directory to confirm exactly which property TypeScript expects [6].

Citations:


🌐 Web query:

"@xyflow/system@0.0.79" 0.0.79

💡 Result:

@xyflow/system version 0.0.79 is a core shared helper library for the xyflow ecosystem, which powers React Flow and Svelte Flow [1][2]. It was published on July 6, 2026 [3][1]. This release included several patch changes and improvements [4][5]: - Zoom pane extent is now cached to prevent panning and pinching from forcing synchronous layouts [4]. - The background pattern fill color was corrected for dark mode [4][5]. - An issue where fitView ignored hidden nodes with declared intrinsic sizes was resolved [4]. - A fix was implemented to prevent errors when clamping positions if a parent node is not in the node lookup [4]. - The onPanZoomEnd event is now consistently fired when triggered by pan-on-scroll wheel ticks [4]. - Improved safety checks were added for element existence and type in node/edge typeguards [4][5].

Citations:


Fix the assignment shape in the direct node-click callbacks.

RoadmapNodeSelection requires { id: string; data: { label: string; unknown keys } }, while @xyflow/react’s default Node uses data: Record<string, unknown>. Pass a narrowed selection object at lines 68 and 77 to avoid requiring a manual // ts-ignore around onNodeClick(node).

Suggested change
 -        onNodeClick(node);
 +        onNodeClick({ id: node.id, data: { ...node.data, label } });
         return;
       }
 
       openNodeMenu(node.id, label);
       return;
     }
 
     // "option" et "center" : jamais gatés, comportement normal
 -    onNodeClick(node);
 +    onNodeClick({
 +      id: node.id,
 +      data: { ...node.data, label: String(node.data.label ?? "") },
 +    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleNodeClick = (node: Node) => {
const locked = Boolean(node.data.locked);
if (locked) return;
if (node.type === "topic" || node.type === "choice") {
const label = String(node.data.label ?? "");
if (completedSet.has(node.id)) {
// Déjà validé : plus besoin du menu, on va direct à l'explication.
onNodeClick(node);
return;
}
openNodeMenu(node.id, label);
return;
}
// "option" et "center" : jamais gatés, comportement normal
onNodeClick(node);
};
const handleNodeClick = (node: Node) => {
const locked = Boolean(node.data.locked);
if (locked) return;
if (node.type === "topic" || node.type === "choice") {
const label = String(node.data.label ?? "");
if (completedSet.has(node.id)) {
// Déjà validé : plus besoin du menu, on va direct à l'explication.
onNodeClick({ id: node.id, data: { ...node.data, label } });
return;
}
openNodeMenu(node.id, label);
return;
}
// "option" et "center" : jamais gatés, comportement normal
onNodeClick({
id: node.id,
data: { ...node.data, label: String(node.data.label ?? "") },
});
};
🤖 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/modules/my-roadmap/ui/sections/roadmap-display-section.tsx` around lines
59 - 78, Update both direct onNodeClick calls in handleNodeClick to pass a
narrowed RoadmapNodeSelection-shaped object with node.id and a data.label
string, rather than the full `@xyflow/react` Node. Apply this in the completedSet
branch and the fallback branch while preserving the existing menu behavior and
click flow.

@@ -0,0 +1,16 @@
// src/modules/my-roadmap/lib/roadmap-node-selection.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Path comment doesn't match the file location — it says src/modules/my-roadmap/lib/... but the file lives at src/modules/roadmap/lib/.

✏️ Fix
-// src/modules/my-roadmap/lib/roadmap-node-selection.ts
+// src/modules/roadmap/lib/roadmap-node-selection.ts
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// src/modules/my-roadmap/lib/roadmap-node-selection.ts
// src/modules/roadmap/lib/roadmap-node-selection.ts
🤖 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/modules/roadmap/lib/roadmap-node-selection.ts` at line 1, Update the
stale source path reference associated with roadmap-node-selection.ts so it uses
src/modules/roadmap/lib/ rather than src/modules/my-roadmap/lib/. Preserve the
file name and remaining path unchanged.

Comment on lines +53 to +58
useEffect(() => {
if (mode !== "quiz" || !activeNodeId || !activeNodeLabel) return;
if (quizId) return; // déjà chargé pour ce passage

setResult(null);
setIsLoadingQuiz(true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stale result screen when re-entering the quiz.

result is only cleared inside this effect, but the if (quizId) return; guard on Line 55 short-circuits before setResult(null). So after finishing a quiz → "Retour" → "Passer le quiz", mode === "quiz" with a truthy result renders the old score screen instead of the questions.

Clear result when leaving quiz mode (or move the reset above the quizId guard).

🐛 Proposed fix
   useEffect(() => {
     if (mode !== "quiz" || !activeNodeId || !activeNodeLabel) return;
+    setResult(null);
     if (quizId) return; // déjà chargé pour ce passage
 
-    setResult(null);
     setIsLoadingQuiz(true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (mode !== "quiz" || !activeNodeId || !activeNodeLabel) return;
if (quizId) return; // déjà chargé pour ce passage
setResult(null);
setIsLoadingQuiz(true);
useEffect(() => {
if (mode !== "quiz" || !activeNodeId || !activeNodeLabel) return;
setResult(null);
if (quizId) return; // déjà chargé pour ce passage
setIsLoadingQuiz(true);
🤖 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/modules/roadmap/ui/components/node-action-dialog.tsx` around lines 53 -
58, Update the quiz state reset in the useEffect so result is cleared whenever
entering quiz mode, before the quizId early-return guard can skip it. Preserve
the existing quizId loading guard and ensure re-entering the quiz renders
questions instead of the previous score screen.

Comment on lines +60 to +69
getOrCreateNodeQuiz({
roadmapId,
nodeId: activeNodeId,
nodeLabel: activeNodeLabel,
})
.then((res) => {
setQuizId(res.quizId);
setQuestions(res.questions);
})
.finally(() => setIsLoadingQuiz(false));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No error handling on either async path.

getOrCreateNodeQuiz(...) uses .then(...).finally(...) with no .catch, and handleSubmit has try/finally with no catch. A backend failure (very plausible: the model call is unguarded server-side) produces an unhandled rejection and leaves the dialog showing an empty question list or silently doing nothing on submit.

Add error state and surface a message.

🛠️ Sketch
       .then((res) => {
         setQuizId(res.quizId);
         setQuestions(res.questions);
       })
+      .catch(() => setLoadError("Impossible de charger le quiz. Réessayez."))
       .finally(() => setIsLoadingQuiz(false));
       setResult(res);
+    } catch {
+      setSubmitError("Échec de l'envoi du quiz. Réessayez.");
     } finally {
       setSubmitting(false);
     }

Also applies to: 94-112

🤖 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/modules/roadmap/ui/components/node-action-dialog.tsx` around lines 60 -
69, Add error state to the dialog component and handle failures in both the
quiz-loading promise after getOrCreateNodeQuiz and the handleSubmit try block.
Capture each error, update the error state with a user-visible message, and
retain the existing loading cleanup in finally so failures do not leave
unhandled rejections or silent empty states.

@MiradoAndreas
MiradoAndreas merged commit cbb59a6 into main Jul 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant