Overview
Code review (2026-04-01) found several algorithmic performance issues in the timeline scheduling route.
Findings
1. featureResourceHours() Called Up to 200,000 Times — Critical
File: server/src/routes/timeline.ts:774,814
featureResourceHours() iterates all stories/tasks of a feature to compute a resource hours map. It's called inside the resource-levelling simulation loop (up to 1,000 steps) × per manual feature × per resource type. For 10 manual features × 20 resource types × 1,000 steps = 200,000 calls, each re-iterating tasks.
Fix: Pre-compute featureResourceHours for all features before the simulation loop and cache in a Map.
2. Kahn's Sort O(n² log n) — High
File: server/src/routes/timeline.ts:651
queue.sort() called on every iteration of the while (queue.length > 0) loop. For 500 features: ~125,000 comparisons. A min-heap gives O(F log F) — ~27× faster at 500 features, ~110× at 2,000.
Fix: Replace with a min-heap / priority queue.
3. Feature Timeline Upserts Sequential — Critical
File: server/src/routes/timeline.ts:956-966
for (const fId of processed) { await prisma.timelineEntry.upsert(...) } — sequential DB round trips. Story upserts at line 953 correctly use Promise.all — features should too.
Fix: await Promise.all([...processed].map(fId => prisma.timelineEntry.upsert(...)))
4. computeParallelWarnings Re-queries Already-Loaded Data — High
File: server/src/routes/timeline.ts:338-342
After scheduling finishes (which already loaded the full hierarchy), computeParallelWarnings issues fresh findMany queries for features and resource types already in memory.
Fix: Pass the already-loaded data to computeParallelWarnings instead of re-querying.
5. Story Scheduling O(S²) Per Feature — Medium
File: server/src/routes/timeline.ts:909-940
For each story, the code iterates sibling list from the start to find position. Pre-computing all story positions in one pass per feature reduces to O(S).
Acceptance Criteria
Overview
Code review (2026-04-01) found several algorithmic performance issues in the timeline scheduling route.
Findings
1.
featureResourceHours()Called Up to 200,000 Times — CriticalFile:
server/src/routes/timeline.ts:774,814featureResourceHours()iterates all stories/tasks of a feature to compute a resource hours map. It's called inside the resource-levelling simulation loop (up to 1,000 steps) × per manual feature × per resource type. For 10 manual features × 20 resource types × 1,000 steps = 200,000 calls, each re-iterating tasks.Fix: Pre-compute
featureResourceHoursfor all features before the simulation loop and cache in a Map.2. Kahn's Sort O(n² log n) — High
File:
server/src/routes/timeline.ts:651queue.sort()called on every iteration of thewhile (queue.length > 0)loop. For 500 features: ~125,000 comparisons. A min-heap gives O(F log F) — ~27× faster at 500 features, ~110× at 2,000.Fix: Replace with a min-heap / priority queue.
3. Feature Timeline Upserts Sequential — Critical
File:
server/src/routes/timeline.ts:956-966for (const fId of processed) { await prisma.timelineEntry.upsert(...) }— sequential DB round trips. Story upserts at line 953 correctly usePromise.all— features should too.Fix:
await Promise.all([...processed].map(fId => prisma.timelineEntry.upsert(...)))4.
computeParallelWarningsRe-queries Already-Loaded Data — HighFile:
server/src/routes/timeline.ts:338-342After scheduling finishes (which already loaded the full hierarchy),
computeParallelWarningsissues freshfindManyqueries for features and resource types already in memory.Fix: Pass the already-loaded data to
computeParallelWarningsinstead of re-querying.5. Story Scheduling O(S²) Per Feature — Medium
File:
server/src/routes/timeline.ts:909-940For each story, the code iterates sibling list from the start to find position. Pre-computing all story positions in one pass per feature reduces to O(S).
Acceptance Criteria
featureResourceHourspre-computed and cached before simulation loopPromise.allcomputeParallelWarningsaccepts pre-loaded data