Aller au contenu principal

Lesson Authoring Guide

How to add, edit, and ship training lessons + quizzes in Kaltiv's LMS without breaking parity, locales, or the KONA tool-unlock pipeline.

This guide is for content authors and developers extending the LMS. End-users do not need to read it.

1. Architecture at a glance

Kaltiv's LMS v2 (FOF-917) uses two storage layers, each addressing a different concern:

ConcernStorageFile
Educational content (titles, descriptions, quiz Q&A)TypeScript sourcesrc/data/learningPaths.ts, src/data/quizzes.ts
User-facing stringsi18n JSON ×4 localespublic/locales/{fr,en,pt,sw}/training.json
Lesson completion progress (v1)localStoragekey terraflow-learning-progress
Formal HR training catalog (separate concern)DB (training_programs)Used by /dashboard/trainings route

Why TS, not DB? See F-LMS-PHANTOM-DB-TARGET-1. FOF-917 was originally scoped as "DB seed migration" — but the consumer route (TrainingCenter dialog) reads TS data. Forcing a DB schema would have created a phantom table that nothing queried. Cross-cutting constraint C11 (ai_memory/cross_cutting_constraints.md) now mandates storage-layer verification at /piv NODE-1.

The DB-backed completion log is deferred to EPIC 2 (FOF-920) — see §9.

2. Lesson schema

// src/data/learningPaths.ts
export type LessonType = 'guide' | 'video' | 'quiz' | 'task';

export interface Lesson {
id: string; // prefix-by-path: rh-1, pdg-2, sup-3, emp-4 (see §4)
titleKey: string; // i18n key — e.g. 'training.lessons.rh.missions.title'
descriptionKey: string; // i18n key — e.g. 'training.lessons.rh.missions.description'
type: LessonType;
durationMin: number; // estimated reading/viewing time
resourceUrl?: string; // optional deep-link or external doc
}

export interface LearningPath {
id: 'path-pdg' | 'path-rh' | 'path-superviseur' | 'path-employe';
titleKey: string; // 'onboarding:training.paths.pdg.label'
descriptionKey: string; // 'onboarding:training.paths.pdg.description'
audience: 'pdg' | 'rh' | 'superviseur' | 'employe';
lessons: Lesson[];
}

What changed in v2 (FOF-917)

Pre-v2 lessons hardcoded title: 'Missions et déplacements' (FR-only). v2 replaces every literal with an i18n key. The render layer (LearningPathView.tsx, LearningPathCard.tsx) calls t(lesson.titleKey) instead of reading the literal. No t() call inside the data file — the data file stays pure TS, the rendering does the locale resolution.

3. Quiz schema (multi-quiz per path)

Big 5 SAP SuccessFactors Learning ships multiple short quizzes per role-path (one per topic) instead of one long end-of-path exam. v2 adopts this pattern.

// src/data/quizzes.ts
export interface QuizQuestion {
questionKey: string; // 'training.quizzes.rh.missions.q1.question'
optionKeys: string[]; // ['training.quizzes.rh.missions.q1.opt1', ...] — typically 4
correctIndex: number; // 0-based, must be < optionKeys.length
rationaleKey?: string; // optional 'why this is the right answer' explanation
}

export interface Quiz {
id: string; // 'quiz-rh-missions', 'quiz-pdg-branding', ...
pathId: 'path-pdg' | 'path-rh' | 'path-superviseur' | 'path-employe';
topic: string; // logical topic — UI groups quizzes by topic
titleKey: string;
questions: QuizQuestion[];
passingScore: number; // 50-100, score % required to "pass"
}

export const quizzes: Quiz[] = [/* one row per quiz */];

A path can hold N quizzes. The selector in QuizView.tsx groups them by topic.

4. Naming conventions

Lesson IDs — prefix-by-path

PathLesson ID prefixExample
path-pdgpdg-pdg-1, pdg-2, …
path-rhrh-rh-1, rh-2, …
path-superviseursup-sup-1, sup-2, …
path-employeemp-emp-1, emp-2, …

The prefix is load-bearing: useLearningProgress computes path completion by counting localStorage entries whose lesson ID starts with the prefix. Breaking this convention silently breaks the KONA tool-unlock trigger.

The parity ratchet test learningPaths-parity.test.ts enforces both the prefix and uniqueness across paths.

i18n key convention

training.lessons.<audience>.<topic>.{title,description}
training.quizzes.<audience>.<topic>.<qN>.{question,opt1..optN,rationale}
training.quizzes.<audience>.<topic>.title
onboarding:training.paths.<audience>.{label,description}

Path labels live in the onboarding namespace (already loaded when the dialog mounts). Lesson + quiz content lives in the new training namespace registered in src/i18n.ts and lazy-loaded on first dialog open.

5. Locale discipline (4 locales — non-negotiable)

Every key MUST exist in fr, en, pt, sw. The parity ratchet test asserts:

Object.keys(fr.training) — Object.keys(en.training) ===
// repeat for pt, sw

CI fails if any locale is missing a key.

PT and SW are NEVER skipped. v1 acceptable: tenant-admin override path is planned for EPIC 2. If translation is uncertain, copy the FR string verbatim + open a Linear follow-up — never leave a key absent. See cross-cutting constraint C1 in ai_memory/cross_cutting_constraints.md.

6. Deep-linking from tours and Help

The post-tour toast and HelpButton route to:

/help?trainingPath=path-rh&lesson=rh-6

TrainingCenter.tsx reads the query params on mount, opens the dialog, switches to the right path tab, and scrolls the lesson into view. HelpButton.tsx listens at the second mount point (regression-tested).

The bridge map trainingKonaBridge.ts → TOUR_TRAINING_MAP drives the toast: every entry MUST resolve to a real (pathId, lessonId) pair. The parity test asserts this.

7. Big 5 LMS reference patterns

Researched 2026-05-07. Adoption decisions:

PatternSourcev2 status
Microlearning (5-15 min role-based modules)LinkedIn Learning, SC TrainingAdopted — every path's lessons stay ≤ 15 min
Multi-quiz per role-path (one per topic)SAP SuccessFactors LearningAdopted (T5 multi-quiz refactor)
AI content recommendationsDoceboDeferred to EPIC 3 (FOF-921) — KONA suggest_lesson based on page context
Native HCM analytics integrationWorkday LearningDeferred to EPIC 2 (FOF-920) — DB-backed completion + analytics dashboards
Compliance audit trail / attestationSAP SuccessFactorsDeferred to EPIC 2 (FOF-920) — completion log in lesson_completions table

Sources:

8. Adding a new lesson — workflow

  1. Pick the path (path-pdg, path-rh, path-superviseur, path-employe).
  2. Pick the next lesson ID — increment per the path's prefix (e.g. rh-7 if rh-1..rh-6 exist).
  3. Add the data row in src/data/learningPaths.ts:
    {
    id: 'rh-7',
    titleKey: 'training.lessons.rh.<topic>.title',
    descriptionKey: 'training.lessons.rh.<topic>.description',
    type: 'guide',
    durationMin: 8,
    },
  4. Add 4 i18n entries — one per locale — in public/locales/{fr,en,pt,sw}/training.json. Use the same key path; translate the value. PT/SW v1 may copy FR if uncertain.
  5. Run the ratchetbash scripts/test-changed.sh. The locale parity ratchet, prefix convention test, and MIN_LESSONS derivation test all run. Fix failures before commit.
  6. Update the bridge if a tour should land on this lesson — add a TOUR_TRAINING_MAP entry in trainingKonaBridge.ts pointing to (pathId, lessonId).
  7. Live-verify — load the app, complete the relevant tour, click the toast, confirm the lesson opens in all 4 locales (per live-verify-before-done.md §6 mandatory checklist).
  8. Commit — conventional commit feat(lms): FOF-XXX — add <topic> lesson referencing the parent EPIC.

9. Adding a new quiz — workflow

Same shape as lessons, with quiz-specific guards:

  • correctIndex < optionKeys.length — ratchet asserts.
  • passingScore ∈ [50, 100].
  • A path may have multiple quizzes; the UI selector groups them by topic.
  • Quiz IDs follow quiz-<audience>-<topic> (e.g. quiz-rh-missions).

KONA tool-unlock for a path requires all lessons completed (count ≥ MIN_LESSONS[pathId], computed dynamically from the data file via useLearningProgress.ts) and at least one quiz passed at the path's passing-score threshold. Adding a quiz alone does not unlock tools.

10. Future evolution

PatternEPICTrigger
DB-backed completion (lesson_completions table + RLS + audit trail)FOF-920Cross-device progress, compliance attestation
Tenant-admin translation override (replace LLM PT/SW v1 with curated copy)FOF-920Tenant onboarding feedback
KONA suggest_lesson(currentRoute) tool — AI recommendationsFOF-921Docebo-style contextual nudges
"?" contextual help button per page (replaces some tours)FOF-921Big 5 SaaS onboarding pattern (40% faster TTV)
Cross-module lesson parity (28 modules with tour but no lesson)FOF-920Help-ecosystem H4 closure across modules

Each EPIC has its own plan in .agents/plans/.

11. Test ratchets that catch drift

Three parity ratchets run on every bash scripts/test-changed.sh:

RatchetWhat it asserts
learningPaths-parity.test.tsTOUR_TRAINING_MAP → real lesson, prefix convention, i18n parity ×4 locales
quizzes-parity.test.tsMulti-quiz architecture, correctIndex bounds, passingScore range, locale parity
TrainingCenter-deeplink.test.tsQuery-param routing, fallback on invalid path, post-tour toast click

Drift can only decrease over time (cross-cutting constraint C7). Authors who break a ratchet fix the data, never the test.

12. References