feat(learning): roadmap player side panel#61
Merged
Conversation
…ge) roadmap selection Two API fixes ahead of the roadmap player (its first real consumer): - POST /api/learning/validate without a JSON Content-Type left req.body undefined, so the destructuring threw and the endpoint answered 500; it now falls through the existing per-field checks and returns 400. - Translations share a roadmap id and differ by language, so getRoadmap(id) returned whichever file readdir listed first and the selection was nondeterministic. The key is now (id, language): GET /roadmaps/:id accepts ?language= (exact match or 404), the language-less form picks deterministically (sorted), and /validate stays language-neutral since steps and validators are shared across translations. Contract documented in docs/learning-api.md. Also fixes the example roadmap's first hint: the node library sits on the right side of the canvas, not the left.
New frontend/src/features/learning: a sidebar that turns the empty canvas into a guided session. It lists the roadmap catalogue (GET /api/learning/roadmaps), opens a roadmap by (id, language), shows every step with the current one highlighted, and a Validate button that runs POST /api/learning/validate against the real containers and renders the per-validator report (raw rendering for now — P-2 owns the rich pass/fail/error presentation). - useLearningPlayer holds the play-through state: clamped navigation, per-step results kept in memory when navigating (persistence is P-4), a ref-based guard so double-clicking Validate sends one request. - Network failures render inline with a retry action; per the learning API contract, non-2xx is a request/server problem and is never shown as a learner failure. - Minimal CanvasPage surface: one showLearning state, one topbar button, one conditional sibling in the body row. Closed panel means nothing is mounted and no learning endpoint is touched (tested). - Labels in en/fr via i18next; styling on the shared CSS variables.
- Render RoadmapCatalog at a single JSX position so a failed roadmap load no longer remounts it (which re-fired its catalogue fetch). - Lift readErrorMessage to shared/utils and reuse it from useContainers and useLearningPlayer instead of duplicating it. - Guard openRoadmap against out-of-order resolutions (latest request wins) and against a malformed 200 body (steps must be a non-empty array) so a bad payload surfaces as a load error, not a render crash. - estimatedMinutes uses a null check: a value of 0 rendered a stray "0" text node in the catalogue card. - Pluralize the step-count label (steps_one/steps_other) so a single-step roadmap doesn't read "1 steps". - Name the requested language in the 404 when a roadmap id exists but the translation doesn't, instead of claiming the id is unknown. - Derive Previous/Next bounds once (atFirstStep/atLastStep).
Derssa
approved these changes
Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary of Changes
Adds the roadmap player: a sidebar next to the canvas that opens a roadmap, lists its steps with the current one highlighted, shows the instruction, and validates the current step against the real containers via
POST /api/learning/validate. This closes the full loop: frontend → learning API → Docker → frontend, and gives new users a guided path instead of an empty canvas.Deliberately deferred to follow-ups: rich ✓/✗ result presentation (this PR renders the raw validation report), a hints UI, and progression persistence (player state is in-memory).
How it's built:
frontend/src/features/learning/—useRoadmaps(catalogue) +useLearningPlayer(open/navigate/validate; per-step results kept when navigating; ref-based guard so double-clicking Validate sends one request; latest-open-wins guard) and 4 components (LearningPanel,RoadmapCatalog,RoadmapPlayer,StepValidationResults).showLearningstate, one topbar button, one conditional sibling in the body row. Closed panel = nothing mounted, no learning endpoint touched (tested).docs/learning-api.md): non-2xx is a request/server problem — shown inline with retry, never as a learner failure.POST /validatewithout a JSON body now returns 400 instead of 500.GET /roadmaps/:id?language=(exact match or 404), deterministic sorted pick without the param,/validatestays language-neutral. Documented indocs/learning-api.md.Also fixes the example roadmap's first hint (the node library is on the right side of the canvas, not the left).
Types of Changes
Verification & Testing
Automated Checks
npm run lintsuccessfully with no errorsnpm run buildsuccessfully with no compilation errorsnpm testsuccessfully (all tests pass)Backend: 211 passing (new: 400-not-500 on non-JSON body, language forwarding, deterministic/exact/missing-translation selection, fr fixture). Frontend: 195 passing (new: 22 — both hooks, LearningPanel behavior incl. network-down + retry, CanvasPage mount/unmount with zero learning fetches while closed).
Manual Verification
Verified against real Docker with curl: step 1 of the example roadmap fails on an empty project, passes after creating and starting the
webnode;?language=de→ 404; garbage body → 400; the disposable test project was deleted with no container residue.Checklist