## 1. State synthesis - [x] 1.1 In `app/src/state/appState.ts`, import `makePriorityScorer` from `../solver/priority` - [x] 1.2 Replace the `setTopPlans([])` line in the `openSetIds.length === 0` early-return branch with a synthesized single-element top-K: `{ courseAssignments: pinnedAssignments, achievedSpecs: optimizationResult.achieved, priorityScore: scorer(optimizationResult.achieved) }`. Keep `setTreeResults([])`, `setTopPlansPartial(false)`, `setSearchProgress(null)`, `setTreeLoading(false)` as-is - [x] 1.3 Confirm the synthesized state respects external credits implicitly via `optimizationResult.achieved` (no extra plumbing needed) ## 2. UI: detect all-pinned and render - [x] 2.1 In `app/src/components/TopPlans.tsx`, compute `const allPinned = ELECTIVE_SETS.every((s) => pinnedCourses[s.id])` - [x] 2.2 Switch the visibility filter: `const visible = allPinned ? plans : plans.filter((p) => p.achievedSpecs.length > 0)` - [x] 2.3 Switch the header text: render `"Your Plan"` when `allPinned`, otherwise `"Top Plans"` - [x] 2.4 Hide the "ranked by specs achieved" subtitle when `allPinned` ## 3. Tests + verification - [x] 3.1 Run the existing 97-test suite; confirm all still pass - [ ] 3.2 Browser verify: pin a single course in every set and observe that Top Plans now renders the completed plan with the same achievement count as the spec strip; header reads "Your Plan" - [ ] 3.3 Browser verify the 0-spec edge case: pin courses that yield no achievement; the panel still shows the plan with `0` achievements (no "No plans yet…" placeholder) - [ ] 3.4 Browser verify unpin: unpin one set, header reverts to "Top Plans", search runs normally ## 4. Version + changelog - [x] 4.1 Bump `__APP_VERSION__` and `__APP_VERSION_DATE__` in `app/vite.config.ts` (e.g., `1.5.1`) - [x] 4.2 Add a `CHANGELOG.md` entry: completed plan now surfaces in the Top Plans panel as "Your Plan" once every set is pinned; was previously empty