Concept: we have a progression system (per-user lesson completion, prerequisites, auto-awarded skills, tree-of-knowledge dashboard) but no visualization that makes it engaging for a younger audience. Duolingo's UI is the reference: simple, playful, sounds, immediate feedback. This must be purely optional — launched from the menu, never forced into the lesson flow; the existing playground experience is unchanged for students who don't opt in.
Existing data to build on (no new progression backend expected):
GET /api/Lessons/progress — completed lesson numbers per user (UserLessonProgress: attempts, best execution time, completion timestamps)
Audio prior art in the codebase: dungeon-crawler/audio-system.ts, github-snake, chess — there is no lesson-flow audio today
Questions the spike must answer:
Visualization form — path/map (Duolingo-style winding trail per language curriculum) vs constellation/tree extension of tree-of-knowledge; how prerequisites and the completion-ratio locks (#329) render on it
Feedback moments — lesson complete, skill earned, streak milestones: celebration animation + optional sound; where sounds live (small local assets, no CDN per platform policy) and a global mute/reduced-motion story (accessibility)
Streaks — do we add streak tracking (new backend: last-activity-day per user) or defer? Recommendation required
Entry point — menu placement (Learn menu item, e.g. "My Journey"), route (public with auth-gated data, like /tree-of-knowledge), and whether anonymous users see the visualization of the free/earned slice (ties into #329)
Audience fit — visual language appropriate for younger students without alienating adults; respects existing theme
Effort estimate — library needs (animation: CSS/Web Animations vs Lottie; audio: plain HTMLAudio vs WebAudio) with the platform's no-third-party-CDN constraint
Deliverable: design doc + clickable prototype (Storybook story or dev-tools route) + implementation sub-tickets. No production wiring in the spike.
Spike under epic #323.
**Concept:** we have a progression system (per-user lesson completion, prerequisites, auto-awarded skills, tree-of-knowledge dashboard) but no visualization that makes it *engaging* for a younger audience. Duolingo's UI is the reference: simple, playful, sounds, immediate feedback. This must be **purely optional** — launched from the menu, never forced into the lesson flow; the existing playground experience is unchanged for students who don't opt in.
**Existing data to build on (no new progression backend expected):**
- `GET /api/Lessons/progress` — completed lesson numbers per user (`UserLessonProgress`: attempts, best execution time, completion timestamps)
- Skills: `SkillDefinition` (bronze/silver/gold tiers) + `EarnedSkill`, auto-awards via `CheckAutoAwardsCommandHandler` (e.g. `lessons_completed_in_range`)
- Existing visuals: lesson sidebar checkmarks/locks/difficulty stars, skill-earned toast stack in `language-runner.html`, tree-of-knowledge dashboard (category cards, proficiency rings, progress bars)
- Audio prior art in the codebase: `dungeon-crawler/audio-system.ts`, github-snake, chess — there is **no** lesson-flow audio today
**Questions the spike must answer:**
- [ ] **Visualization form** — path/map (Duolingo-style winding trail per language curriculum) vs constellation/tree extension of tree-of-knowledge; how prerequisites and the completion-ratio locks (#329) render on it
- [ ] **Feedback moments** — lesson complete, skill earned, streak milestones: celebration animation + optional sound; where sounds live (small local assets, no CDN per platform policy) and a global mute/reduced-motion story (accessibility)
- [ ] **Streaks** — do we add streak tracking (new backend: last-activity-day per user) or defer? Recommendation required
- [ ] **Entry point** — menu placement (Learn menu item, e.g. "My Journey"), route (public with auth-gated data, like `/tree-of-knowledge`), and whether anonymous users see the visualization of the free/earned slice (ties into #329)
- [ ] **Audience fit** — visual language appropriate for younger students without alienating adults; respects existing theme
- [ ] **Effort estimate** — library needs (animation: CSS/Web Animations vs Lottie; audio: plain HTMLAudio vs WebAudio) with the platform's no-third-party-CDN constraint
**Deliverable:** design doc + clickable prototype (Storybook story or dev-tools route) + implementation sub-tickets. No production wiring in the spike.
Spike deliverable — "My Journey" progression visualization
Recon done against the live code. Recommendations below answer every checklist box; two decisions flagged for your sign-off at the end. No production wiring in this spike — deliverable is this doc + a dev-tools prototype route + the sub-tickets filed below.
What already exists (build-on, don't rebuild)
Progress data:GET /api/Lessons/progress → completed lesson numbers per user (UserLessonProgress: attempts, best exec time, completion timestamps). No new progression backend needed for v1.
Skills:SkillDefinition (bronze/silver/gold) + EarnedSkill, auto-award via CheckAutoAwardsCommandHandler (lessons_completed_in_range). Skill-earned toast stack already lives in language-runner.html.
Dashboard pattern:tree-of-knowledge.component.ts — signals-based, HttpClient to /activity/*, proficiency rings + category cards, parent/child compare. /tree-of-knowledge-style route (public shell, auth-gated data) is the template to copy.
Audio prior art:dungeon-crawler/audio-system.ts is a THREE.js AudioLoader/AudioListener system pulling assets/mysterious-maze/*.wav|mp3. Confirms the local-assets, no-CDN policy — but it's far too heavy to reuse for lesson feedback (drags in three). Lesson flow has zero audio today.
Menu home:Learn dropdown (menu-bar.component.html) already hosts auth-gated /reading-journey (auto_stories) and /geography (public). "My Journey" slots in here cleanly.
1. Visualization form → winding path/map, per-language
A Duolingo-style vertical winding trail of lesson "stops," one track per language curriculum (C, C++, C#, Python, JavaScript — matches the standardized playground set). Rationale over a constellation/tree:
Lessons are an ordered sequence with prerequisites — a linear-ish trail reads as "where am I / what's next" instantly for a young audience; a tree reads as a reference map (which is exactly what tree-of-knowledge already is, so we'd be duplicating it).
Keeps tree-of-knowledge as the analytical dashboard and My Journey as the playful view — no overlap.
Node states:completed (filled, checkmark, language color), current (pulsing ring, "START"), available (outline), locked-prereq (padlock, greyed), locked-horizon (the #329 completion-ratio lock — distinct "create an account to continue" styling, not a plain padlock). Prereq edges render as the connecting trail segment; a horizon lock renders as a "gate" band across the trail with a sign-in CTA. This is the single integration point with #329 and is guarded behind a feature flag so the two spikes ship independently.
Rendering: inline SVG trail + positioned DOM nodes (Angular @for over computed node models). No canvas/WebGL — keeps it accessible (real focusable buttons), themeable via existing CSS vars, and cheap.
Three moments: lesson complete, skill earned (reuse existing toast, add optional sound), streak milestone (only if we adopt streaks — see #3).
Animation: Web Animations API + CSS keyframes (node fill sweep, checkmark draw, confetti burst as CSS particles). No Lottie — avoids a runtime dep and JSON asset weight for effects this simple.
Audio: a small standalone JourneyAudioService using a single AudioContext to synthesize short blips (success arpeggio, lock thud, level-up) — zero audio assets to ship, zero CDN, ~1KB of code. If we later want richer sound, drop .wavs into assets/journey/ following the dungeon-crawler pattern. Do not reuse the THREE-based AudioSystem.
Global controls (accessibility, required): a JourneyPrefsService persisting sound: on/off and motion: full/reduced to localStorage, initialized from window.matchMedia('(prefers-reduced-motion: reduce)') — note: the repo has noprefers-reduced-motion handling anywhere today, so this establishes the pattern. Reduced motion → no confetti/pulse, instant state changes; muted → no AudioContext calls. Toggles live in the My Journey header.
3. Streaks → defer to a fast-follow, not v1
Streaks need a new backend field (last-activity-day per user + a daily rollover job) and a whole "don't lose your streak" pressure loop that deserves its own product decision (and arguably conflicts with a low-pressure kids' tone). Recommendation: ship the path + feedback without streaks. File it as a separate ticket (done below, #NEW-streaks) so the visualization can render a streak flame later without rework — the node model reserves the slot.
4. Entry point → Learn ▸ My Journey, route /my-journey
Menu: new mat-menu-item in the Learn dropdown, emoji_events/map icon, gated like siblings (@if (isLoggedIn) for the data; the route itself renders a public shell).
Route: /my-journey (public component, auth-gated data fetch) mirroring /tree-of-knowledge.
Anonymous: with #329, anonymous users see the trail of the free/earned slice with the horizon gate rendered inline ("you've unlocked 6 of N — make an account to keep going"). Without #329 merged, anonymous simply sees the sign-in CTA. Flag-gated so it degrades gracefully.
5. Audience fit
Playful but theme-respecting: reuse existing language brand colors + CSS custom props, rounded friendly nodes, micro-animations. No mascot, no cartoon restyle of the whole app — the playground stays untouched. Adults reading the same trail just see a clean progress map. Everything opt-in via the menu; nothing injected into the lesson flow.
6. Effort estimate
Frontend (bulk): ~M. New feature-my-journey lib (component + node-model computed from /Lessons/progress + curriculum order), JourneyAudioService (S), JourneyPrefsService + reduced-motion (S), menu + route wiring (XS), i18n keys en/es (S), Storybook/dev-tools prototype (S), unit tests (S).
Backend: ~XS for v1 — none required if curriculum order + progress already expose enough; a thin GET /api/Lessons/journey (curriculum order + completion + horizon in one call) is a nice-to-have to avoid client-side stitching. Streaks = separate M if adopted.
Deps added: none. WAAPI + AudioContext are platform built-ins.
Decisions I need from you
Streaks in or out of v1? My rec: out (fast-follow ticket filed).
Thin /api/Lessons/journey aggregation endpoint, or stitch client-side from the existing /progress + curriculum? My rec: thin endpoint — one round trip, keeps horizon logic (#329) server-authoritative.
Implementation sub-tickets filed below; will link once created.
## Spike deliverable — "My Journey" progression visualization
Recon done against the live code. Recommendations below answer every checklist box; two decisions flagged for your sign-off at the end. No production wiring in this spike — deliverable is this doc + a dev-tools prototype route + the sub-tickets filed below.
### What already exists (build-on, don't rebuild)
- **Progress data:** `GET /api/Lessons/progress` → completed lesson numbers per user (`UserLessonProgress`: attempts, best exec time, completion timestamps). No new progression backend needed for v1.
- **Skills:** `SkillDefinition` (bronze/silver/gold) + `EarnedSkill`, auto-award via `CheckAutoAwardsCommandHandler` (`lessons_completed_in_range`). Skill-earned toast stack already lives in `language-runner.html`.
- **Dashboard pattern:** `tree-of-knowledge.component.ts` — signals-based, `HttpClient` to `/activity/*`, proficiency rings + category cards, parent/child compare. `/tree-of-knowledge`-style route (public shell, auth-gated data) is the template to copy.
- **Audio prior art:** `dungeon-crawler/audio-system.ts` is a THREE.js `AudioLoader`/`AudioListener` system pulling `assets/mysterious-maze/*.wav|mp3`. Confirms the **local-assets, no-CDN** policy — but it's far too heavy to reuse for lesson feedback (drags in `three`). Lesson flow has **zero audio today.**
- **Menu home:** `Learn` dropdown (`menu-bar.component.html`) already hosts auth-gated `/reading-journey` (`auto_stories`) and `/geography` (`public`). "My Journey" slots in here cleanly.
---
### 1. Visualization form → **winding path/map, per-language**
A Duolingo-style vertical winding trail of lesson "stops," one track per language curriculum (C, C++, C#, Python, JavaScript — matches the standardized playground set). Rationale over a constellation/tree:
- Lessons are an **ordered sequence with prerequisites** — a linear-ish trail reads as "where am I / what's next" instantly for a young audience; a tree reads as a reference map (which is exactly what tree-of-knowledge already is, so we'd be duplicating it).
- Keeps tree-of-knowledge as the analytical dashboard and My Journey as the *playful* view — no overlap.
**Node states:** `completed` (filled, checkmark, language color), `current` (pulsing ring, "START"), `available` (outline), `locked-prereq` (padlock, greyed), `locked-horizon` (the #329 completion-ratio lock — distinct "create an account to continue" styling, not a plain padlock). Prereq edges render as the connecting trail segment; a horizon lock renders as a "gate" band across the trail with a sign-in CTA. This is the single integration point with #329 and is guarded behind a feature flag so the two spikes ship independently.
**Rendering:** inline SVG trail + positioned DOM nodes (Angular `@for` over computed node models). No canvas/WebGL — keeps it accessible (real focusable buttons), themeable via existing CSS vars, and cheap.
### 2. Feedback moments → **CSS/Web Animations + tiny WebAudio blips**
Three moments: **lesson complete**, **skill earned** (reuse existing toast, add optional sound), **streak milestone** (only if we adopt streaks — see #3).
- **Animation:** Web Animations API + CSS keyframes (node fill sweep, checkmark draw, confetti burst as CSS particles). **No Lottie** — avoids a runtime dep and JSON asset weight for effects this simple.
- **Audio:** a small standalone `JourneyAudioService` using a single `AudioContext` to synthesize short blips (success arpeggio, lock thud, level-up) — **zero audio assets to ship, zero CDN**, ~1KB of code. If we later want richer sound, drop `.wav`s into `assets/journey/` following the dungeon-crawler pattern. Do **not** reuse the THREE-based `AudioSystem`.
- **Global controls (accessibility, required):** a `JourneyPrefsService` persisting `sound: on/off` and `motion: full/reduced` to `localStorage`, initialized from `window.matchMedia('(prefers-reduced-motion: reduce)')` — note: the repo has **no** `prefers-reduced-motion` handling anywhere today, so this establishes the pattern. Reduced motion → no confetti/pulse, instant state changes; muted → no `AudioContext` calls. Toggles live in the My Journey header.
### 3. Streaks → **defer to a fast-follow, not v1**
Streaks need a new backend field (last-activity-day per user + a daily rollover job) and a whole "don't lose your streak" pressure loop that deserves its own product decision (and arguably conflicts with a low-pressure kids' tone). **Recommendation: ship the path + feedback without streaks.** File it as a separate ticket (done below, #NEW-streaks) so the visualization can render a streak flame later without rework — the node model reserves the slot.
### 4. Entry point → **`Learn ▸ My Journey`, route `/my-journey`**
- Menu: new `mat-menu-item` in the `Learn` dropdown, `emoji_events`/`map` icon, gated like siblings (`@if (isLoggedIn)` for the data; the route itself renders a public shell).
- Route: `/my-journey` (public component, auth-gated data fetch) mirroring `/tree-of-knowledge`.
- **Anonymous:** with #329, anonymous users see the trail of the free/earned slice with the horizon gate rendered inline ("you've unlocked 6 of N — make an account to keep going"). Without #329 merged, anonymous simply sees the sign-in CTA. Flag-gated so it degrades gracefully.
### 5. Audience fit
Playful but theme-respecting: reuse existing language brand colors + CSS custom props, rounded friendly nodes, micro-animations. No mascot, no cartoon restyle of the whole app — the playground stays untouched. Adults reading the same trail just see a clean progress map. Everything opt-in via the menu; nothing injected into the lesson flow.
### 6. Effort estimate
- **Frontend (bulk):** ~M. New `feature-my-journey` lib (component + node-model computed from `/Lessons/progress` + curriculum order), `JourneyAudioService` (S), `JourneyPrefsService` + reduced-motion (S), menu + route wiring (XS), i18n keys en/es (S), Storybook/dev-tools prototype (S), unit tests (S).
- **Backend:** ~XS for v1 — none required if curriculum order + progress already expose enough; a thin `GET /api/Lessons/journey` (curriculum order + completion + horizon in one call) is a nice-to-have to avoid client-side stitching. Streaks = separate M if adopted.
- **Deps added:** none. WAAPI + `AudioContext` are platform built-ins.
---
### Decisions I need from you
1. **Streaks in or out of v1?** My rec: **out** (fast-follow ticket filed).
2. **Thin `/api/Lessons/journey` aggregation endpoint, or stitch client-side** from the existing `/progress` + curriculum? My rec: **thin endpoint** — one round trip, keeps horizon logic (#329) server-authoritative.
Implementation sub-tickets filed below; will link once created.
Spike is complete pending your two decisions above (streaks in/out, aggregation endpoint vs client-side stitch). #333 can start immediately once you bless it; #334 and #335 gate on those two decisions.
Sub-tickets filed:
- **#333** — frontend: winding-path visualization, audio + prefs services, menu/route, prototype (the bulk of the work)
- **#334** — backend: optional thin `GET /api/Lessons/journey` aggregation (dropped if we stitch client-side)
- **#335** — fast-follow: daily streaks + streak-milestone feedback (deferred from v1)
Spike is complete pending your two decisions above (streaks in/out, aggregation endpoint vs client-side stitch). #333 can start immediately once you bless it; #334 and #335 gate on those two decisions.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Spike under epic #323.
Concept: we have a progression system (per-user lesson completion, prerequisites, auto-awarded skills, tree-of-knowledge dashboard) but no visualization that makes it engaging for a younger audience. Duolingo's UI is the reference: simple, playful, sounds, immediate feedback. This must be purely optional — launched from the menu, never forced into the lesson flow; the existing playground experience is unchanged for students who don't opt in.
Existing data to build on (no new progression backend expected):
GET /api/Lessons/progress— completed lesson numbers per user (UserLessonProgress: attempts, best execution time, completion timestamps)SkillDefinition(bronze/silver/gold tiers) +EarnedSkill, auto-awards viaCheckAutoAwardsCommandHandler(e.g.lessons_completed_in_range)language-runner.html, tree-of-knowledge dashboard (category cards, proficiency rings, progress bars)dungeon-crawler/audio-system.ts, github-snake, chess — there is no lesson-flow audio todayQuestions the spike must answer:
/tree-of-knowledge), and whether anonymous users see the visualization of the free/earned slice (ties into #329)Deliverable: design doc + clickable prototype (Storybook story or dev-tools route) + implementation sub-tickets. No production wiring in the spike.
Spike deliverable — "My Journey" progression visualization
Recon done against the live code. Recommendations below answer every checklist box; two decisions flagged for your sign-off at the end. No production wiring in this spike — deliverable is this doc + a dev-tools prototype route + the sub-tickets filed below.
What already exists (build-on, don't rebuild)
GET /api/Lessons/progress→ completed lesson numbers per user (UserLessonProgress: attempts, best exec time, completion timestamps). No new progression backend needed for v1.SkillDefinition(bronze/silver/gold) +EarnedSkill, auto-award viaCheckAutoAwardsCommandHandler(lessons_completed_in_range). Skill-earned toast stack already lives inlanguage-runner.html.tree-of-knowledge.component.ts— signals-based,HttpClientto/activity/*, proficiency rings + category cards, parent/child compare./tree-of-knowledge-style route (public shell, auth-gated data) is the template to copy.dungeon-crawler/audio-system.tsis a THREE.jsAudioLoader/AudioListenersystem pullingassets/mysterious-maze/*.wav|mp3. Confirms the local-assets, no-CDN policy — but it's far too heavy to reuse for lesson feedback (drags inthree). Lesson flow has zero audio today.Learndropdown (menu-bar.component.html) already hosts auth-gated/reading-journey(auto_stories) and/geography(public). "My Journey" slots in here cleanly.1. Visualization form → winding path/map, per-language
A Duolingo-style vertical winding trail of lesson "stops," one track per language curriculum (C, C++, C#, Python, JavaScript — matches the standardized playground set). Rationale over a constellation/tree:
Node states:
completed(filled, checkmark, language color),current(pulsing ring, "START"),available(outline),locked-prereq(padlock, greyed),locked-horizon(the #329 completion-ratio lock — distinct "create an account to continue" styling, not a plain padlock). Prereq edges render as the connecting trail segment; a horizon lock renders as a "gate" band across the trail with a sign-in CTA. This is the single integration point with #329 and is guarded behind a feature flag so the two spikes ship independently.Rendering: inline SVG trail + positioned DOM nodes (Angular
@forover computed node models). No canvas/WebGL — keeps it accessible (real focusable buttons), themeable via existing CSS vars, and cheap.2. Feedback moments → CSS/Web Animations + tiny WebAudio blips
Three moments: lesson complete, skill earned (reuse existing toast, add optional sound), streak milestone (only if we adopt streaks — see #3).
JourneyAudioServiceusing a singleAudioContextto synthesize short blips (success arpeggio, lock thud, level-up) — zero audio assets to ship, zero CDN, ~1KB of code. If we later want richer sound, drop.wavs intoassets/journey/following the dungeon-crawler pattern. Do not reuse the THREE-basedAudioSystem.JourneyPrefsServicepersistingsound: on/offandmotion: full/reducedtolocalStorage, initialized fromwindow.matchMedia('(prefers-reduced-motion: reduce)')— note: the repo has noprefers-reduced-motionhandling anywhere today, so this establishes the pattern. Reduced motion → no confetti/pulse, instant state changes; muted → noAudioContextcalls. Toggles live in the My Journey header.3. Streaks → defer to a fast-follow, not v1
Streaks need a new backend field (last-activity-day per user + a daily rollover job) and a whole "don't lose your streak" pressure loop that deserves its own product decision (and arguably conflicts with a low-pressure kids' tone). Recommendation: ship the path + feedback without streaks. File it as a separate ticket (done below, #NEW-streaks) so the visualization can render a streak flame later without rework — the node model reserves the slot.
4. Entry point →
Learn ▸ My Journey, route/my-journeymat-menu-itemin theLearndropdown,emoji_events/mapicon, gated like siblings (@if (isLoggedIn)for the data; the route itself renders a public shell)./my-journey(public component, auth-gated data fetch) mirroring/tree-of-knowledge.5. Audience fit
Playful but theme-respecting: reuse existing language brand colors + CSS custom props, rounded friendly nodes, micro-animations. No mascot, no cartoon restyle of the whole app — the playground stays untouched. Adults reading the same trail just see a clean progress map. Everything opt-in via the menu; nothing injected into the lesson flow.
6. Effort estimate
feature-my-journeylib (component + node-model computed from/Lessons/progress+ curriculum order),JourneyAudioService(S),JourneyPrefsService+ reduced-motion (S), menu + route wiring (XS), i18n keys en/es (S), Storybook/dev-tools prototype (S), unit tests (S).GET /api/Lessons/journey(curriculum order + completion + horizon in one call) is a nice-to-have to avoid client-side stitching. Streaks = separate M if adopted.AudioContextare platform built-ins.Decisions I need from you
/api/Lessons/journeyaggregation endpoint, or stitch client-side from the existing/progress+ curriculum? My rec: thin endpoint — one round trip, keeps horizon logic (#329) server-authoritative.Implementation sub-tickets filed below; will link once created.
Sub-tickets filed:
GET /api/Lessons/journeyaggregation (dropped if we stitch client-side)Spike is complete pending your two decisions above (streaks in/out, aggregation endpoint vs client-side stitch). #333 can start immediately once you bless it; #334 and #335 gate on those two decisions.
Decisions locked (@spikerj):
GET /api/Lessons/journeyendpoint (#334), keeping the #329 horizon server-authoritative.Starting implementation: #334 (backend) first, then #333 (frontend, streaks included).
Spike delivered and implemented. Duolingo-style optional progression visualization shipped via #333 (frontend
feature-my-journey, PR #98) and #334 (backend/journeyaggregation + streaks, PR #55), both merged tomaster. Closing the spike.