diff --git a/.jules/bolt.md b/.jules/bolt.md index ba67a71..2cd600f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -3,3 +3,7 @@ ## 2026-07-16 - [O(N*M) Nested Loop Lookups in Grading Engines] **Learning:** In interactive scenarios (such as Bid Elevator and STR Triage), grading engines frequently iterate over user decisions and match them against scenario properties (like keywords or search terms). Performing `array.find()` inside loop bodies or filter predicates results in costly O(N*M) lookups. **Action:** Convert arrays to `Map` lookups before entering loops/nested scans. Mapping keys once in O(M) time enables O(1) lookups during execution, transforming the time complexity of the grading logic to O(N + M). + +## 2026-07-17 - [Scoping User-Specific Lesson Progress Queries] +**Learning:** Querying a user's entire history of lesson progress on dashboard and course catalog pages creates large database payloads and high memory consumption, which grows quadratically as more courses/lessons are added. +**Action:** Always scope database queries for lesson progress to the relevant courses or specific lesson IDs (e.g., using `lessonId: { in: lessonIds }`) to load only what is being displayed. diff --git a/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx b/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx index 3640169..82b7e0a 100644 --- a/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx +++ b/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx @@ -194,13 +194,21 @@ async function PendingLessons({ take: 50, }); + const courseLessonIds = allLessons.map((l) => l.id); const completedSet = new Set( - ( - await db.lessonProgress.findMany({ - where: { userId, status: 'COMPLETED', deletedAt: null }, - select: { lessonId: true }, - }) - ).map((p) => p.lessonId), + courseLessonIds.length > 0 + ? ( + await db.lessonProgress.findMany({ + where: { + userId, + lessonId: { in: courseLessonIds }, + status: 'COMPLETED', + deletedAt: null, + }, + select: { lessonId: true }, + }) + ).map((p) => p.lessonId) + : [] ); const pending = allLessons.filter((l) => !completedSet.has(l.id)).slice(0, 8); diff --git a/src/app/(dashboard)/courses/page.tsx b/src/app/(dashboard)/courses/page.tsx index 1d0c6c3..ee9e641 100644 --- a/src/app/(dashboard)/courses/page.tsx +++ b/src/app/(dashboard)/courses/page.tsx @@ -37,11 +37,14 @@ export default async function CoursesIndexPage() { }, }); - // Get user's lesson progress - const lessonProgress = await db.lessonProgress.findMany({ - where: { userId: user.id, deletedAt: null }, - select: { lessonId: true, status: true }, - }); + // Get user's lesson progress scoped to published courses (Bolt performance optimization) + const allLessonIds = courses.flatMap((c) => c.modules.flatMap((m) => m.lessons.map((l) => l.id))); + const lessonProgress = allLessonIds.length > 0 + ? await db.lessonProgress.findMany({ + where: { userId: user.id, lessonId: { in: allLessonIds }, deletedAt: null }, + select: { lessonId: true, status: true }, + }) + : []; const progressMap = new Map(lessonProgress.map((p) => [p.lessonId, p.status])); return ( diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 546298d..1844ec1 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -30,11 +30,14 @@ export default async function DashboardPage() { }, }); - // Get user's lesson progress - const lessonProgress = await db.lessonProgress.findMany({ - where: { userId: user.id, deletedAt: null }, - select: { lessonId: true, status: true }, - }); + // Get user's lesson progress scoped to published courses (Bolt performance optimization) + const allLessonIds = courses.flatMap((c) => c.modules.flatMap((m) => m.lessons.map((l) => l.id))); + const lessonProgress = allLessonIds.length > 0 + ? await db.lessonProgress.findMany({ + where: { userId: user.id, lessonId: { in: allLessonIds }, deletedAt: null }, + select: { lessonId: true, status: true }, + }) + : []; const progressMap = new Map(lessonProgress.map((p) => [p.lessonId, p.status])); // Compute aggregate stats