name: add-training description: Map and implement all Musix Studio touchpoints required to add a complete training module. Use when adding a new training, training page, practice module, metric service, Supabase training data, leaderboard, gamification, daily goals, daily challenges, achievements, feature card, route, menu, or metrics/ranking integration.
Add Training
Use this skill before creating or wiring a new Musix Studio training. Goal: avoid a half-added module that appears in UI but misses Supabase, metrics, ranking, goals, challenges, achievements, XP, or round completions.
Hard rule: do not call a training "complete" until the local validator passes for its module id and metric table. If Supabase persistence is involved, also verify the live database or apply a migration before reporting done.
First Pass
- Run Nx workspace discovery first:
npx nx show projectsnpx nx show project musix-studio --json
- Search existing modules before editing:
rg -n "TrainingRoundModule|RoundCompleteEvent|AnswerEvent|recordRound|recordAnswer|onRoundComplete|recordProgress|fetchLeaderboard|getSummary|hydrateFromRemote" src/app/domain/musix-studiorg -n "training_round_completions|metric_entries|leaderboard|daily_challenges|achievements|daily_goals|xp|module" supabase/migrations
- Pick a stable module id and use it everywhere. Existing ids:
major_scalecipher_notationfretboardharmonic_fieldtriadesintervalosperceptionkey_training
Ask before implementation if the user did not provide: training name, route slug, module id, question/answer shape, scoring rule, modes, whether it needs Supabase persistence, and which rankings/achievements should count it.
Required Guardrail
Before final response, run:
.\.agents\skills\add-training\scripts\validate-training-module.ps1 -ModuleId <module_id> -MetricTable <metric_table>
Examples:
.\.agents\skills\add-training\scripts\validate-training-module.ps1 -ModuleId key_training -MetricTable key_training_answers
.\.agents\skills\add-training\scripts\validate-training-module.ps1 -ModuleId intervalos -MetricTable interval_metric_entries
If the validator fails, fix the missing touchpoints instead of explaining the checklist. Use -SkipMetricTable only when a training intentionally has no persisted answer metrics; still validate round completions and gamification.
UI Entry Points
Update these when the new training should be discoverable:
src/app/domain/musix-studio/feature-cards.constant.ts- Add
FEATURE_CARDSentry withcategory: 'training'. - Confirm
TRAINING_CARDSshould include it automatically.
- Add
src/app/domain/musix-studio/musix-studio.routes.ts- Add authenticated route under
MUSIX_STUDIO_ROUTESchildren.
- Add authenticated route under
src/app/shared/constants/menus.ts- Usually no per-training menu item; trainings live under
/trainings. - Only add menu entry when product explicitly wants direct sidebar access.
- Usually no per-training menu item; trainings live under
src/app/domain/musix-studio/pages/training-hub/*- Verify hub renders
TRAINING_CARDS; adjust only if filtering/sorting/category rules hide the new card.
- Verify hub renders
src/app/domain/musix-studio/pages/landing-page/*- Verify public landing shows desired cards and auth redirect behavior still holds.
Training Page Contract
Use existing training pages as templates. Prefer closest behavior:
pages/major-scale-training/*: mode/scale-heavy flow, leaderboard snapshot, full metrics.pages/cipher-notation/*: compact answer training with mode filters.pages/fretboard-training/*: fast answer loop, per-string metrics.pages/harmonic-field-training/*: scale/mode details and richer summaries.pages/triades-training/*: multi-mode music theory training.pages/interval-training/*: recent simple full integration.pages/perception-training/*: audio/perception flow and daily challenge direct call.pages/key-training/*: key-by-ear flow.
Wire shared components when useful:
components/training-flow-header/*components/training-answer-feedback/*components/training-round-summary/*components/leaderboard-row/*training-flow.types.tstraining-practice-focus.tstraining-round-share.tsshare-card.ts
At answer time, call the module metric service recordAnswer(...). For gamification, call GamificationService.onAnswer(...) unless the page has a deliberate special path. At round end, call both TrainingRoundCompletionsService.recordRound(...) and GamificationService.onRoundComplete(...).
Supabase Checklist
Create or update migrations under supabase/migrations.
For a complete persisted training, add:
<module>_metric_entriestable or equivalent:id,user_id default auth.uid(),answered_at default now()mode_id- prompt/answer columns needed for metrics
correctresponse_time_mswhen speed/ranking/XP should use speed- indexes on
(user_id, answered_at desc)and key filters - RLS enabled
- select/insert policies scoped to
auth.uid() - grants for
authenticated
- leaderboard RPC:
- returns rank, user id, display name, avatar url when UI needs it
- uses
SECURITY DEFINERonly when cross-user aggregation requires it - revokes public and grants execute to
authenticated
- speed leaderboard RPC if the metrics page has speed rankings.
training_round_completionsconstraint:- drop and recreate
training_round_completions_module_check - include the new module id and every existing module id
- drop and recreate
training_rounds_leaderboardRPC:- include new module id in accepted
p_modulelist - keep return contract aligned with
TrainingRoundsLeaderboardRow
- include new module id in accepted
Important drift class to prevent: TypeScript may know a module while Supabase still rejects it. Always verify:
- answer table inserts can infer
user_idthroughdefault auth.uid() - answer table has
answered_at default now() - answer table RLS has select/insert policies scoped to
auth.uid() = user_id training_round_completions_module_checkincludes the module idtraining_rounds_leaderboardaccepts the module id instead of falling back to another module- the live DB has the migration applied when working against hosted Supabase
Metrics Service
Create src/app/domain/musix-studio/services/<module>-metrics.service.ts or extend an existing service if the training shares data.
Match existing service surface:
recordAnswer(input): void | Promise<void>getSummary()hydrateFromRemote()fetchLeaderboard(...)- optional
fetchLeaderboardSpeed(...) - local signal cache plus Supabase persistence
- graceful behavior when Supabase client/session is unavailable
Add imports to gamification only when summary data is needed for achievements/cross-module checks.
Gamification
Update all module id unions:
src/app/domain/musix-studio/services/training-round-completions.service.tsTrainingRoundModule
src/app/domain/musix-studio/services/gamification/gamification.service.tsAnswerEvent.moduleRoundCompleteEvent.module
Update XP and activity:
services/gamification/xp-levels.service.tsresolveXpWeight(module, modeId)if weight differs from default- labels/activity metadata only if needed by UI
pages/gamification/gamification.page.tsmoduleTrainingPath(...)moduleLabel(...)- any module-specific challenge labels/icons
Update daily goals and challenges:
services/gamification/daily-goal.service.ts- Usually answer count is module-agnostic; verify no module filter blocks the new training.
services/gamification/daily-challenges.service.ts- Add module id to
DailyChallengeDefinition.moduleunion. - Add challenge definitions for the new training if product wants it in daily rotation.
- Update cross-module lists (
CROSS_MODULES) if the new training should count toward cross-module challenges.
- Add module id to
Update achievements:
services/gamification/achievements.service.ts- Add module-specific
ACHIEVEMENTSentries when needed. - Extend
ExtendedMasteryContextwith summary/round fields. - Add checks in
checkExtendedMastery(...). - Update cross-module achievements if new training changes "all modules" semantics.
- Add module-specific
services/gamification/gamification.service.ts- Inject new metrics service if achievements need summaries.
- Hydrate it in
hydrateAll(). - Include round counts in daily rounds, totals, and cross-module booleans.
Metrics And Ranking Page
Update src/app/domain/musix-studio/pages/major-scale-metrics/* even though the name says major-scale; it is the app-wide metrics/ranking page.
Check:
- services injected in
.ts - leaderboard fetch methods
- round-completion leaderboards:
- add
this.roundCompletions.fetchLeaderboard({ module: '<module_id>', ... })
- add
- template sections for accuracy, speed, rounds, and skeleton states
- labels and units
- SCSS only if layout needs another section/column
If adding a dedicated per-training leaderboard snapshot, mirror existing training page pattern with TrainingRoundSummaryComponent and LeaderboardRowComponent.
Validation
Prefer Nx commands:
npx nx build musix-studionpx nx lint musix-studioonly if requested or needed; repo has known pre-existing lint errors.npx nx test musix-studioonly if requested or after fixing known Jest setup issue; repo has known pre-existing Jest setup path problem.
For Supabase migrations, inspect SQL carefully:
- no unsafe RLS
- no raw user rows exposed by leaderboard RPC
- all module ids preserved in constraints and RPC allowlists
- frontend type unions match DB allowlists
Run the bundled validator:
.\.agents\skills\add-training\scripts\validate-training-module.ps1 -ModuleId <module_id> -MetricTable <metric_table>
For hosted Supabase, also inspect or apply the live migration. Minimal SQL checks:
select column_default
from information_schema.columns
where table_schema = 'public'
and table_name = '<metric_table>'
and column_name in ('user_id', 'answered_at');
select pg_get_constraintdef(oid)
from pg_constraint
where conrelid = 'public.training_round_completions'::regclass
and conname = 'training_round_completions_module_check';
select public.training_rounds_leaderboard('<module_id>', 30, 1, 1);
Manual smoke path:
- Public landing shows card if expected.
- Card click redirects/login-gates correctly.
- Authenticated route loads.
- Answer records metric.
- Round complete records
training_round_completions. - XP/daily goal/challenge update.
- Achievements unlock when thresholds are met.
- Metrics/ranking page shows accuracy/speed/round data or intentionally omits unsupported sections.
Commit Message
After code changes, suggest:
feat(<module>): add <training name> training
Use another type only when the work is not a feature.