From a33e3e6d2b348beff90a27e2f92ac29c2e68e85f Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sat, 13 Dec 2025 06:48:39 -0600 Subject: [PATCH] fix(practice): add defensive checks in recordSlotResult MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add robust error handling to recordSlotResult to prevent cryptic "Cannot read properties of undefined" errors: - Check that plan.parts exists and is a valid array before indexing - Validate currentPartIndex is within valid bounds - Ensure the database update returned a result These checks provide clearer error messages when data is corrupted or in an invalid state, helping diagnose production issues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../web/src/lib/curriculum/session-planner.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/web/src/lib/curriculum/session-planner.ts b/apps/web/src/lib/curriculum/session-planner.ts index 3d234ccb..2367d22f 100644 --- a/apps/web/src/lib/curriculum/session-planner.ts +++ b/apps/web/src/lib/curriculum/session-planner.ts @@ -503,6 +503,19 @@ export async function recordSlotResult( const plan = await getSessionPlan(planId) if (!plan) throw new Error(`Plan not found: ${planId}`) + // Defensive check: ensure parts array exists and is valid + if (!plan.parts || !Array.isArray(plan.parts)) { + throw new Error( + `Plan ${planId} has invalid parts: ${typeof plan.parts} (status: ${plan.status}, partIndex: ${plan.currentPartIndex})` + ) + } + + if (plan.currentPartIndex < 0 || plan.currentPartIndex >= plan.parts.length) { + throw new Error( + `Plan ${planId} has invalid currentPartIndex: ${plan.currentPartIndex} (parts.length: ${plan.parts.length})` + ) + } + const currentPart = plan.parts[plan.currentPartIndex] if (!currentPart) throw new Error(`Invalid part index: ${plan.currentPartIndex}`) @@ -545,6 +558,13 @@ export async function recordSlotResult( .where(eq(schema.sessionPlans.id, planId)) .returning() + // Defensive check: ensure update succeeded + if (!updated) { + throw new Error( + `Failed to update plan ${planId}: no rows returned (may have been deleted during update)` + ) + } + // Update global skill mastery with response time data // This builds the per-kid stats for identifying strengths/weaknesses if (result.skillsExercised && result.skillsExercised.length > 0) {