v1.5.1: Show completed plan in Top Plans

Once every elective set is pinned, the Top Plans panel now renders the
user's completed selection as a single "Your Plan" card showing the
achievement count and pinned courses. Previously the panel went blank
because the all-pinned branch in useAppState cleared topPlans, even
though the spec strip was already showing the achievement.

Synthesizes a single PlanOutcome from pinnedAssignments +
optimizationResult.achieved + the priority scorer; TopPlans detects the
all-pinned state via ELECTIVE_SETS.every(...) and bypasses the
length>0 filter so a 0-spec completed plan still renders honestly.
This commit is contained in:
2026-05-10 11:57:56 -04:00
parent 3a5ebaa17a
commit cb2024f857
10 changed files with 246 additions and 5 deletions
@@ -0,0 +1,30 @@
## Why
When a student pins a course in every elective set, the Top Plans panel goes blank and shows the placeholder "No plans yet achieve a specialization with the current pinned courses." This is misleading: the optimizer has already computed achievement (visible in the spec strip), but the Top Plans state is short-circuited to `[]` because no decision-tree search runs when there are no open sets. The student's completed plan IS the only plan — it should be the one rendered.
## What Changes
- When `openSetIds.length === 0`, synthesize a single `PlanOutcome` from the pinned assignments + `optimizationResult.achieved` + the priority scorer, and surface it through the existing `topPlans` state slice instead of clearing the slice.
- Render the synthesized plan unconditionally (no `achievedSpecs.length > 0` filter) so a 0-spec completed plan still shows the courses-and-zero-achievements summary rather than the search placeholder.
- Header copy switches to "Your Plan" (singular) when `openSetIds.length === 0`. Otherwise stays "Top Plans".
- Leaf cache, search progress strip, "ranked by specs achieved" subtitle, and the search-complete/incomplete static text remain unchanged.
## Capabilities
### New Capabilities
_None._
### Modified Capabilities
- `optimization-engine`: when all elective sets are pinned, `useAppState` SHALL emit a single synthesized `PlanOutcome` rather than clearing the top-K slice.
- `unified-specialization-panel`: the Top Plans panel SHALL render a synthesized completed plan with header copy "Your Plan" and SHALL NOT filter out a single completed plan with zero achievements.
## Impact
- `app/src/state/appState.ts` — replace the `setTopPlans([])` early return with a synthesized one-element top-K when `openSetIds.length === 0`. Use `makePriorityScorer(state.ranking)(achieved)` for the score.
- `app/src/components/TopPlans.tsx` — when `plans.length === 1` and the only plan's `courseAssignments` covers every elective set, render it as "Your Plan" (header copy). Drop the `length > 0` visibility filter for that single-plan case so a 0-spec completed plan still renders. Other rendering paths unchanged.
- No new tests strictly required, but a state-layer assertion for the synthesis would be nice. Existing 97 tests must continue to pass.
- `app/vite.config.ts` — patch version bump (e.g., `1.5.1`).
- `CHANGELOG.md` — release entry.
- No data-file changes.