@lessonkit/core 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,7 @@ declare const ID_PATTERN: RegExp;
21
21
  declare const ID_MAX_LENGTH = 64;
22
22
 
23
23
  /** H5P-aligned interaction kinds for assessment telemetry and xAPI. */
24
- type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots" | "summary" | "imagePairing" | "imageSequencing" | "essay" | "arithmeticQuiz" | "memoryGame";
24
+ type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots" | "summary" | "imagePairing" | "imageSequencing" | "essay" | "arithmeticQuiz" | "memoryGame" | "combinationLock" | "crossword" | "wordSearch" | "sortParagraphs" | "guessTheAnswer";
25
25
  /** Serializable resume blob for a single assessment block. */
26
26
  type AssessmentResumeState = Record<string, unknown>;
27
27
  /** Behaviour flags aligned with H5P question types. */
@@ -57,15 +57,37 @@ type AssessmentBaseProps = AssessmentBehaviour & {
57
57
  checkId: CheckId;
58
58
  passingScore?: number;
59
59
  };
60
- /** MCQ assessment props shared by React components and LMS packaging descriptors. */
60
+ /**
61
+ * MCQ assessment props shared by React components and LMS packaging descriptors.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * const props: McqAssessmentProps = {
66
+ * checkId: "verify-sender",
67
+ * question: "First step for a suspicious email?",
68
+ * choices: ["Open attachment", "Verify sender"],
69
+ * answer: "Verify sender",
70
+ * passingScore: 1,
71
+ * };
72
+ * ```
73
+ */
61
74
  type McqAssessmentProps = AssessmentBaseProps & {
62
75
  kind?: "mcq";
63
76
  question: string;
64
77
  choices: string[];
78
+ /** Single correct choice (required for backward compatibility). */
65
79
  answer: string;
80
+ /** When length > 1, enables multi-select checkbox mode. */
81
+ answers?: string[];
82
+ /** Randomize choice display order in the SPA (stable when `shuffleSeed` set). */
83
+ shuffleChoices?: boolean;
84
+ /** Seed for deterministic shuffle; defaults to `checkId`. */
85
+ shuffleSeed?: string | number;
86
+ /** Per-choice feedback announced on selection; keys match choice labels. */
87
+ choiceFeedback?: Record<string, string>;
66
88
  };
67
89
 
68
- type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "slide_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed" | "video_cue_reached" | "video_segment_completed" | "memory_card_flipped" | "information_wall_search" | "parallax_slide_viewed" | "questionnaire_submitted" | "branch_node_viewed" | "branch_selected";
90
+ type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "slide_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed" | "video_cue_reached" | "video_segment_completed" | "memory_card_flipped" | "information_wall_search" | "parallax_slide_viewed" | "questionnaire_submitted" | "branch_node_viewed" | "branch_selected" | "image_juxtaposition_changed" | "timeline_event_viewed" | "image_sequence_changed" | "audio_recording_started" | "audio_recording_completed" | "qr_content_revealed" | "advent_door_opened" | "map_stage_viewed" | "map_exit_selected";
69
91
  type TelemetryUser = {
70
92
  id?: string;
71
93
  email?: string;
@@ -196,6 +218,42 @@ type BranchSelectedData = {
196
218
  label: string;
197
219
  scoreWeight?: number;
198
220
  };
221
+ type ImageJuxtapositionChangedData = {
222
+ blockId: BlockId;
223
+ position: number;
224
+ };
225
+ type TimelineEventViewedData = {
226
+ blockId: BlockId;
227
+ eventId: string;
228
+ };
229
+ type ImageSequenceChangedData = {
230
+ blockId: BlockId;
231
+ frameIndex: number;
232
+ };
233
+ type AudioRecordingData = {
234
+ blockId: BlockId;
235
+ };
236
+ type QrContentRevealedData = {
237
+ blockId: BlockId;
238
+ };
239
+ type AdventDoorOpenedData = {
240
+ blockId: BlockId;
241
+ doorId: string;
242
+ day: number;
243
+ };
244
+ type MapStageViewedData = {
245
+ blockId: BlockId;
246
+ stageId: string;
247
+ stageIndex: number;
248
+ stageLabel?: string;
249
+ };
250
+ type MapExitSelectedData = {
251
+ blockId: BlockId;
252
+ fromStageId: string;
253
+ toStageId: string;
254
+ label: string;
255
+ scoreWeight?: number;
256
+ };
199
257
  type TelemetryEvent = (TelemetryEventBase & {
200
258
  name: "course_started";
201
259
  lessonId?: LessonId;
@@ -296,6 +354,42 @@ type TelemetryEvent = (TelemetryEventBase & {
296
354
  name: "branch_selected";
297
355
  lessonId: LessonId;
298
356
  data: BranchSelectedData;
357
+ }) | (TelemetryEventBase & {
358
+ name: "image_juxtaposition_changed";
359
+ lessonId?: LessonId;
360
+ data: ImageJuxtapositionChangedData;
361
+ }) | (TelemetryEventBase & {
362
+ name: "timeline_event_viewed";
363
+ lessonId?: LessonId;
364
+ data: TimelineEventViewedData;
365
+ }) | (TelemetryEventBase & {
366
+ name: "image_sequence_changed";
367
+ lessonId?: LessonId;
368
+ data: ImageSequenceChangedData;
369
+ }) | (TelemetryEventBase & {
370
+ name: "audio_recording_started";
371
+ lessonId?: LessonId;
372
+ data: AudioRecordingData;
373
+ }) | (TelemetryEventBase & {
374
+ name: "audio_recording_completed";
375
+ lessonId?: LessonId;
376
+ data: AudioRecordingData;
377
+ }) | (TelemetryEventBase & {
378
+ name: "qr_content_revealed";
379
+ lessonId?: LessonId;
380
+ data: QrContentRevealedData;
381
+ }) | (TelemetryEventBase & {
382
+ name: "advent_door_opened";
383
+ lessonId?: LessonId;
384
+ data: AdventDoorOpenedData;
385
+ }) | (TelemetryEventBase & {
386
+ name: "map_stage_viewed";
387
+ lessonId: LessonId;
388
+ data: MapStageViewedData;
389
+ }) | (TelemetryEventBase & {
390
+ name: "map_exit_selected";
391
+ lessonId: LessonId;
392
+ data: MapExitSelectedData;
299
393
  });
300
394
  /** Payload shape for a telemetry event name. */
301
395
  type TelemetryDataFor<N extends TelemetryEventName> = Extract<TelemetryEvent, {
@@ -446,6 +540,42 @@ type BuildTelemetryEventInput = (BuildTelemetryEventContext & {
446
540
  name: "branch_selected";
447
541
  lessonId?: LessonId;
448
542
  data: BranchSelectedData;
543
+ }) | (BuildTelemetryEventContext & {
544
+ name: "image_juxtaposition_changed";
545
+ lessonId?: LessonId;
546
+ data: ImageJuxtapositionChangedData;
547
+ }) | (BuildTelemetryEventContext & {
548
+ name: "timeline_event_viewed";
549
+ lessonId?: LessonId;
550
+ data: TimelineEventViewedData;
551
+ }) | (BuildTelemetryEventContext & {
552
+ name: "image_sequence_changed";
553
+ lessonId?: LessonId;
554
+ data: ImageSequenceChangedData;
555
+ }) | (BuildTelemetryEventContext & {
556
+ name: "audio_recording_started";
557
+ lessonId?: LessonId;
558
+ data: AudioRecordingData;
559
+ }) | (BuildTelemetryEventContext & {
560
+ name: "audio_recording_completed";
561
+ lessonId?: LessonId;
562
+ data: AudioRecordingData;
563
+ }) | (BuildTelemetryEventContext & {
564
+ name: "qr_content_revealed";
565
+ lessonId?: LessonId;
566
+ data: QrContentRevealedData;
567
+ }) | (BuildTelemetryEventContext & {
568
+ name: "advent_door_opened";
569
+ lessonId?: LessonId;
570
+ data: AdventDoorOpenedData;
571
+ }) | (BuildTelemetryEventContext & {
572
+ name: "map_stage_viewed";
573
+ lessonId?: LessonId;
574
+ data: MapStageViewedData;
575
+ }) | (BuildTelemetryEventContext & {
576
+ name: "map_exit_selected";
577
+ lessonId?: LessonId;
578
+ data: MapExitSelectedData;
449
579
  });
450
580
 
451
581
  /** Reset dev-warning state (tests only). */
@@ -453,6 +583,18 @@ declare function resetTelemetryBuilderWarningsForTests(): void;
453
583
  /**
454
584
  * Build a typed telemetry event from a catalog event name and context.
455
585
  * Validates lesson-scoped events require `lessonId`.
586
+ *
587
+ * @example
588
+ * ```ts
589
+ * import { buildTelemetryEvent } from "@lessonkit/core";
590
+ *
591
+ * const event = buildTelemetryEvent({
592
+ * name: "lesson_completed",
593
+ * courseId: "sec-101",
594
+ * lessonId: "phishing-101",
595
+ * sessionId: "tab-abc",
596
+ * });
597
+ * ```
456
598
  */
457
599
  declare function buildTelemetryEvent(opts: BuildTelemetryEventInput): TelemetryEvent;
458
600
  /**
@@ -482,8 +624,20 @@ type ProgressController = {
482
624
  declare function createProgressController(): ProgressController;
483
625
 
484
626
  declare const SESSION_STORAGE_KEY = "lessonkit:sessionId";
627
+ type InvalidSessionIdContext = {
628
+ /** The invalid id that was rejected. */
629
+ invalidId: string;
630
+ /** Id actually used after fallback. */
631
+ fallbackId: string;
632
+ /** Whether the invalid id came from config or from stored tab state. */
633
+ source: "provided" | "stored";
634
+ };
635
+ type ResolveSessionIdOptions = {
636
+ /** Invoked when an invalid session id is replaced by a tab or generated id. */
637
+ onInvalidSessionId?: (ctx: InvalidSessionIdContext) => void;
638
+ };
485
639
  declare function getTabSessionId(storage: StoragePort): string | null;
486
- declare function resolveSessionId(storage: StoragePort, provided?: string): string;
640
+ declare function resolveSessionId(storage: StoragePort, provided?: string, options?: ResolveSessionIdOptions): string;
487
641
  declare function hasCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
488
642
  declare function markCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
489
643
  declare function hasCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
@@ -592,12 +746,38 @@ declare function tryEmitCourseStarted(ctx: CourseLifecycleContext, deps: CourseL
592
746
  }>;
593
747
  declare function buildCourseStartedTelemetryEvent(ctx: CourseLifecycleContext): TelemetryEvent;
594
748
  type LessonCompletionEmitter = (lessonId: LessonId, durationMs?: number) => void;
749
+ /**
750
+ * Mark a lesson complete in progress state and emit `lesson_completed` when newly completed.
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * completeLessonWithTelemetry({
755
+ * progress,
756
+ * lessonId: "lesson-1",
757
+ * nowMs: Date.now(),
758
+ * emitLessonCompleted: (id, durationMs) => track("lesson_completed", { lessonId: id, durationMs }),
759
+ * });
760
+ * ```
761
+ */
595
762
  declare function completeLessonWithTelemetry(opts: {
596
763
  progress: ProgressController;
597
764
  lessonId: LessonId;
598
765
  nowMs: number;
599
766
  emitLessonCompleted: LessonCompletionEmitter;
600
767
  }): boolean;
768
+ /**
769
+ * Complete the active lesson (if any), then mark the course complete and emit `course_completed`.
770
+ *
771
+ * @example
772
+ * ```ts
773
+ * completeCourseWithTelemetry({
774
+ * progress,
775
+ * nowMs: Date.now(),
776
+ * emitLessonCompleted: (id) => track("lesson_completed", { lessonId: id }),
777
+ * emitCourseCompleted: () => track("course_completed", {}),
778
+ * });
779
+ * ```
780
+ */
601
781
  declare function completeCourseWithTelemetry(opts: {
602
782
  progress: ProgressController;
603
783
  nowMs: number;
@@ -605,4 +785,4 @@ declare function completeCourseWithTelemetry(opts: {
605
785
  emitCourseCompleted: () => void;
606
786
  }): boolean;
607
787
 
608
- export { type LessonCompletionEmitter as $, type AssessmentResumeState as A, type BlockId as B, type CourseId as C, type AssessmentInteractionType as D, type AssessmentXAPIData as E, type BookPageViewedData as F, type BranchNodeViewedData as G, type BranchSelectedData as H, type IdentityIdPath as I, type BuildTelemetryEventInput as J, type CompoundPageViewedData as K, type LessonId as L, type CourseLifecycleContext as M, type CourseLifecycleDeps as N, type FlashcardFlippedData as O, type PluginRegistry as P, type HotspotOpenedData as Q, ID_MAX_LENGTH as R, type StoragePort as S, type TelemetryEventName as T, ID_PATTERN as U, type IdentityValidationIssue as V, type ImageSliderChangedData as W, type InformationWallSearchData as X, type InteractionBlockRegistration as Y, type InteractionData as Z, type InteractionPlugin as _, type CheckId as a, type LessonLifecycleData as a0, type LessonkitPluginKind as a1, type McqAssessmentProps as a2, type MemoryCardFlippedData as a3, type ParallaxSlideViewedData as a4, type PluginIdentity as a5, type QuestionnaireSubmittedData as a6, type QuizAnsweredData as a7, type QuizCompletedData as a8, SESSION_STORAGE_KEY as a9, resetTelemetryBuilderWarningsForTests as aA, resolveSessionId as aB, tryBuildTelemetryEvent as aC, tryEmitCourseStarted as aD, resetCourseStartedEmitFlightForTests as aE, type SlideViewedData as aa, type TelemetryEventBase as ab, type TimerPort as ac, type VideoCueReachedData as ad, type VideoSegmentCompletedData as ae, buildCourseStartedTelemetryEvent as af, buildTelemetryEvent as ag, completeCourseWithTelemetry as ah, completeLessonWithTelemetry as ai, createDefaultClock as aj, createGlobalTimer as ak, createNoopStorage as al, createProgressController as am, createSessionStoragePort as an, getTabSessionId as ao, hasCourseStarted as ap, hasCourseStartedEmittedToTracking as aq, hasCourseStartedPipelineDelivered as ar, hasCourseStartedXapiSent as as, markCourseStarted as at, markCourseStartedEmittedToTracking as au, markCourseStartedPipelineDelivered as av, markCourseStartedXapiSent as aw, migrateCourseStartedMark as ax, resetSharedVolatileSessionIdForTests as ay, resetStoragePortForTests as az, type IdentityValidationResult as b, type LessonkitUrn as c, type TelemetrySink as d, type TelemetryBatchSink as e, type TrackingClient as f, type TelemetryEvent as g, type TelemetryUser as h, type LessonkitPlugin as i, type ProgressController as j, type PluginHost as k, type ProgressState as l, type TelemetryDataFor as m, type AssessmentScoreInput as n, type AssessmentScoreResult as o, type ClockPort as p, type AssessmentPlugin as q, type LifecyclePlugin as r, type TelemetryPlugin as s, type LessonkitPluginContext as t, type AccordionSectionToggledData as u, type AssessmentAnsweredData as v, type AssessmentBaseProps as w, type AssessmentBehaviour as x, type AssessmentCompletedData as y, type AssessmentHandle as z };
788
+ export { type InteractionPlugin as $, type AssessmentResumeState as A, type BlockId as B, type CourseId as C, type AssessmentInteractionType as D, type AssessmentXAPIData as E, type BookPageViewedData as F, type BranchNodeViewedData as G, type BranchSelectedData as H, type IdentityIdPath as I, type BuildTelemetryEventInput as J, type CompoundPageViewedData as K, type LessonId as L, type McqAssessmentProps as M, type CourseLifecycleContext as N, type CourseLifecycleDeps as O, type PluginRegistry as P, type FlashcardFlippedData as Q, type HotspotOpenedData as R, type StoragePort as S, type TelemetryEventName as T, ID_MAX_LENGTH as U, ID_PATTERN as V, type IdentityValidationIssue as W, type ImageSliderChangedData as X, type InformationWallSearchData as Y, type InteractionBlockRegistration as Z, type InteractionData as _, type CheckId as a, type InvalidSessionIdContext as a0, type LessonCompletionEmitter as a1, type LessonLifecycleData as a2, type LessonkitPluginKind as a3, type MemoryCardFlippedData as a4, type ParallaxSlideViewedData as a5, type PluginIdentity as a6, type QuestionnaireSubmittedData as a7, type QuizAnsweredData as a8, type QuizCompletedData as a9, resetSharedVolatileSessionIdForTests as aA, resetStoragePortForTests as aB, resetTelemetryBuilderWarningsForTests as aC, resolveSessionId as aD, tryBuildTelemetryEvent as aE, tryEmitCourseStarted as aF, resetCourseStartedEmitFlightForTests as aG, type ResolveSessionIdOptions as aa, SESSION_STORAGE_KEY as ab, type SlideViewedData as ac, type TelemetryEventBase as ad, type TimerPort as ae, type VideoCueReachedData as af, type VideoSegmentCompletedData as ag, buildCourseStartedTelemetryEvent as ah, buildTelemetryEvent as ai, completeCourseWithTelemetry as aj, completeLessonWithTelemetry as ak, createDefaultClock as al, createGlobalTimer as am, createNoopStorage as an, createProgressController as ao, createSessionStoragePort as ap, getTabSessionId as aq, hasCourseStarted as ar, hasCourseStartedEmittedToTracking as as, hasCourseStartedPipelineDelivered as at, hasCourseStartedXapiSent as au, markCourseStarted as av, markCourseStartedEmittedToTracking as aw, markCourseStartedPipelineDelivered as ax, markCourseStartedXapiSent as ay, migrateCourseStartedMark as az, type IdentityValidationResult as b, type LessonkitUrn as c, type TelemetrySink as d, type TelemetryBatchSink as e, type TrackingClient as f, type TelemetryEvent as g, type TelemetryUser as h, type LessonkitPlugin as i, type ProgressController as j, type PluginHost as k, type ProgressState as l, type TelemetryDataFor as m, type AssessmentScoreInput as n, type AssessmentScoreResult as o, type ClockPort as p, type AssessmentPlugin as q, type LifecyclePlugin as r, type TelemetryPlugin as s, type LessonkitPluginContext as t, type AccordionSectionToggledData as u, type AssessmentAnsweredData as v, type AssessmentBaseProps as w, type AssessmentBehaviour as x, type AssessmentCompletedData as y, type AssessmentHandle as z };
@@ -21,7 +21,7 @@ declare const ID_PATTERN: RegExp;
21
21
  declare const ID_MAX_LENGTH = 64;
22
22
 
23
23
  /** H5P-aligned interaction kinds for assessment telemetry and xAPI. */
24
- type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots" | "summary" | "imagePairing" | "imageSequencing" | "essay" | "arithmeticQuiz" | "memoryGame";
24
+ type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots" | "summary" | "imagePairing" | "imageSequencing" | "essay" | "arithmeticQuiz" | "memoryGame" | "combinationLock" | "crossword" | "wordSearch" | "sortParagraphs" | "guessTheAnswer";
25
25
  /** Serializable resume blob for a single assessment block. */
26
26
  type AssessmentResumeState = Record<string, unknown>;
27
27
  /** Behaviour flags aligned with H5P question types. */
@@ -57,15 +57,37 @@ type AssessmentBaseProps = AssessmentBehaviour & {
57
57
  checkId: CheckId;
58
58
  passingScore?: number;
59
59
  };
60
- /** MCQ assessment props shared by React components and LMS packaging descriptors. */
60
+ /**
61
+ * MCQ assessment props shared by React components and LMS packaging descriptors.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * const props: McqAssessmentProps = {
66
+ * checkId: "verify-sender",
67
+ * question: "First step for a suspicious email?",
68
+ * choices: ["Open attachment", "Verify sender"],
69
+ * answer: "Verify sender",
70
+ * passingScore: 1,
71
+ * };
72
+ * ```
73
+ */
61
74
  type McqAssessmentProps = AssessmentBaseProps & {
62
75
  kind?: "mcq";
63
76
  question: string;
64
77
  choices: string[];
78
+ /** Single correct choice (required for backward compatibility). */
65
79
  answer: string;
80
+ /** When length > 1, enables multi-select checkbox mode. */
81
+ answers?: string[];
82
+ /** Randomize choice display order in the SPA (stable when `shuffleSeed` set). */
83
+ shuffleChoices?: boolean;
84
+ /** Seed for deterministic shuffle; defaults to `checkId`. */
85
+ shuffleSeed?: string | number;
86
+ /** Per-choice feedback announced on selection; keys match choice labels. */
87
+ choiceFeedback?: Record<string, string>;
66
88
  };
67
89
 
68
- type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "slide_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed" | "video_cue_reached" | "video_segment_completed" | "memory_card_flipped" | "information_wall_search" | "parallax_slide_viewed" | "questionnaire_submitted" | "branch_node_viewed" | "branch_selected";
90
+ type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "slide_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed" | "video_cue_reached" | "video_segment_completed" | "memory_card_flipped" | "information_wall_search" | "parallax_slide_viewed" | "questionnaire_submitted" | "branch_node_viewed" | "branch_selected" | "image_juxtaposition_changed" | "timeline_event_viewed" | "image_sequence_changed" | "audio_recording_started" | "audio_recording_completed" | "qr_content_revealed" | "advent_door_opened" | "map_stage_viewed" | "map_exit_selected";
69
91
  type TelemetryUser = {
70
92
  id?: string;
71
93
  email?: string;
@@ -196,6 +218,42 @@ type BranchSelectedData = {
196
218
  label: string;
197
219
  scoreWeight?: number;
198
220
  };
221
+ type ImageJuxtapositionChangedData = {
222
+ blockId: BlockId;
223
+ position: number;
224
+ };
225
+ type TimelineEventViewedData = {
226
+ blockId: BlockId;
227
+ eventId: string;
228
+ };
229
+ type ImageSequenceChangedData = {
230
+ blockId: BlockId;
231
+ frameIndex: number;
232
+ };
233
+ type AudioRecordingData = {
234
+ blockId: BlockId;
235
+ };
236
+ type QrContentRevealedData = {
237
+ blockId: BlockId;
238
+ };
239
+ type AdventDoorOpenedData = {
240
+ blockId: BlockId;
241
+ doorId: string;
242
+ day: number;
243
+ };
244
+ type MapStageViewedData = {
245
+ blockId: BlockId;
246
+ stageId: string;
247
+ stageIndex: number;
248
+ stageLabel?: string;
249
+ };
250
+ type MapExitSelectedData = {
251
+ blockId: BlockId;
252
+ fromStageId: string;
253
+ toStageId: string;
254
+ label: string;
255
+ scoreWeight?: number;
256
+ };
199
257
  type TelemetryEvent = (TelemetryEventBase & {
200
258
  name: "course_started";
201
259
  lessonId?: LessonId;
@@ -296,6 +354,42 @@ type TelemetryEvent = (TelemetryEventBase & {
296
354
  name: "branch_selected";
297
355
  lessonId: LessonId;
298
356
  data: BranchSelectedData;
357
+ }) | (TelemetryEventBase & {
358
+ name: "image_juxtaposition_changed";
359
+ lessonId?: LessonId;
360
+ data: ImageJuxtapositionChangedData;
361
+ }) | (TelemetryEventBase & {
362
+ name: "timeline_event_viewed";
363
+ lessonId?: LessonId;
364
+ data: TimelineEventViewedData;
365
+ }) | (TelemetryEventBase & {
366
+ name: "image_sequence_changed";
367
+ lessonId?: LessonId;
368
+ data: ImageSequenceChangedData;
369
+ }) | (TelemetryEventBase & {
370
+ name: "audio_recording_started";
371
+ lessonId?: LessonId;
372
+ data: AudioRecordingData;
373
+ }) | (TelemetryEventBase & {
374
+ name: "audio_recording_completed";
375
+ lessonId?: LessonId;
376
+ data: AudioRecordingData;
377
+ }) | (TelemetryEventBase & {
378
+ name: "qr_content_revealed";
379
+ lessonId?: LessonId;
380
+ data: QrContentRevealedData;
381
+ }) | (TelemetryEventBase & {
382
+ name: "advent_door_opened";
383
+ lessonId?: LessonId;
384
+ data: AdventDoorOpenedData;
385
+ }) | (TelemetryEventBase & {
386
+ name: "map_stage_viewed";
387
+ lessonId: LessonId;
388
+ data: MapStageViewedData;
389
+ }) | (TelemetryEventBase & {
390
+ name: "map_exit_selected";
391
+ lessonId: LessonId;
392
+ data: MapExitSelectedData;
299
393
  });
300
394
  /** Payload shape for a telemetry event name. */
301
395
  type TelemetryDataFor<N extends TelemetryEventName> = Extract<TelemetryEvent, {
@@ -446,6 +540,42 @@ type BuildTelemetryEventInput = (BuildTelemetryEventContext & {
446
540
  name: "branch_selected";
447
541
  lessonId?: LessonId;
448
542
  data: BranchSelectedData;
543
+ }) | (BuildTelemetryEventContext & {
544
+ name: "image_juxtaposition_changed";
545
+ lessonId?: LessonId;
546
+ data: ImageJuxtapositionChangedData;
547
+ }) | (BuildTelemetryEventContext & {
548
+ name: "timeline_event_viewed";
549
+ lessonId?: LessonId;
550
+ data: TimelineEventViewedData;
551
+ }) | (BuildTelemetryEventContext & {
552
+ name: "image_sequence_changed";
553
+ lessonId?: LessonId;
554
+ data: ImageSequenceChangedData;
555
+ }) | (BuildTelemetryEventContext & {
556
+ name: "audio_recording_started";
557
+ lessonId?: LessonId;
558
+ data: AudioRecordingData;
559
+ }) | (BuildTelemetryEventContext & {
560
+ name: "audio_recording_completed";
561
+ lessonId?: LessonId;
562
+ data: AudioRecordingData;
563
+ }) | (BuildTelemetryEventContext & {
564
+ name: "qr_content_revealed";
565
+ lessonId?: LessonId;
566
+ data: QrContentRevealedData;
567
+ }) | (BuildTelemetryEventContext & {
568
+ name: "advent_door_opened";
569
+ lessonId?: LessonId;
570
+ data: AdventDoorOpenedData;
571
+ }) | (BuildTelemetryEventContext & {
572
+ name: "map_stage_viewed";
573
+ lessonId?: LessonId;
574
+ data: MapStageViewedData;
575
+ }) | (BuildTelemetryEventContext & {
576
+ name: "map_exit_selected";
577
+ lessonId?: LessonId;
578
+ data: MapExitSelectedData;
449
579
  });
450
580
 
451
581
  /** Reset dev-warning state (tests only). */
@@ -453,6 +583,18 @@ declare function resetTelemetryBuilderWarningsForTests(): void;
453
583
  /**
454
584
  * Build a typed telemetry event from a catalog event name and context.
455
585
  * Validates lesson-scoped events require `lessonId`.
586
+ *
587
+ * @example
588
+ * ```ts
589
+ * import { buildTelemetryEvent } from "@lessonkit/core";
590
+ *
591
+ * const event = buildTelemetryEvent({
592
+ * name: "lesson_completed",
593
+ * courseId: "sec-101",
594
+ * lessonId: "phishing-101",
595
+ * sessionId: "tab-abc",
596
+ * });
597
+ * ```
456
598
  */
457
599
  declare function buildTelemetryEvent(opts: BuildTelemetryEventInput): TelemetryEvent;
458
600
  /**
@@ -482,8 +624,20 @@ type ProgressController = {
482
624
  declare function createProgressController(): ProgressController;
483
625
 
484
626
  declare const SESSION_STORAGE_KEY = "lessonkit:sessionId";
627
+ type InvalidSessionIdContext = {
628
+ /** The invalid id that was rejected. */
629
+ invalidId: string;
630
+ /** Id actually used after fallback. */
631
+ fallbackId: string;
632
+ /** Whether the invalid id came from config or from stored tab state. */
633
+ source: "provided" | "stored";
634
+ };
635
+ type ResolveSessionIdOptions = {
636
+ /** Invoked when an invalid session id is replaced by a tab or generated id. */
637
+ onInvalidSessionId?: (ctx: InvalidSessionIdContext) => void;
638
+ };
485
639
  declare function getTabSessionId(storage: StoragePort): string | null;
486
- declare function resolveSessionId(storage: StoragePort, provided?: string): string;
640
+ declare function resolveSessionId(storage: StoragePort, provided?: string, options?: ResolveSessionIdOptions): string;
487
641
  declare function hasCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
488
642
  declare function markCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
489
643
  declare function hasCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
@@ -592,12 +746,38 @@ declare function tryEmitCourseStarted(ctx: CourseLifecycleContext, deps: CourseL
592
746
  }>;
593
747
  declare function buildCourseStartedTelemetryEvent(ctx: CourseLifecycleContext): TelemetryEvent;
594
748
  type LessonCompletionEmitter = (lessonId: LessonId, durationMs?: number) => void;
749
+ /**
750
+ * Mark a lesson complete in progress state and emit `lesson_completed` when newly completed.
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * completeLessonWithTelemetry({
755
+ * progress,
756
+ * lessonId: "lesson-1",
757
+ * nowMs: Date.now(),
758
+ * emitLessonCompleted: (id, durationMs) => track("lesson_completed", { lessonId: id, durationMs }),
759
+ * });
760
+ * ```
761
+ */
595
762
  declare function completeLessonWithTelemetry(opts: {
596
763
  progress: ProgressController;
597
764
  lessonId: LessonId;
598
765
  nowMs: number;
599
766
  emitLessonCompleted: LessonCompletionEmitter;
600
767
  }): boolean;
768
+ /**
769
+ * Complete the active lesson (if any), then mark the course complete and emit `course_completed`.
770
+ *
771
+ * @example
772
+ * ```ts
773
+ * completeCourseWithTelemetry({
774
+ * progress,
775
+ * nowMs: Date.now(),
776
+ * emitLessonCompleted: (id) => track("lesson_completed", { lessonId: id }),
777
+ * emitCourseCompleted: () => track("course_completed", {}),
778
+ * });
779
+ * ```
780
+ */
601
781
  declare function completeCourseWithTelemetry(opts: {
602
782
  progress: ProgressController;
603
783
  nowMs: number;
@@ -605,4 +785,4 @@ declare function completeCourseWithTelemetry(opts: {
605
785
  emitCourseCompleted: () => void;
606
786
  }): boolean;
607
787
 
608
- export { type LessonCompletionEmitter as $, type AssessmentResumeState as A, type BlockId as B, type CourseId as C, type AssessmentInteractionType as D, type AssessmentXAPIData as E, type BookPageViewedData as F, type BranchNodeViewedData as G, type BranchSelectedData as H, type IdentityIdPath as I, type BuildTelemetryEventInput as J, type CompoundPageViewedData as K, type LessonId as L, type CourseLifecycleContext as M, type CourseLifecycleDeps as N, type FlashcardFlippedData as O, type PluginRegistry as P, type HotspotOpenedData as Q, ID_MAX_LENGTH as R, type StoragePort as S, type TelemetryEventName as T, ID_PATTERN as U, type IdentityValidationIssue as V, type ImageSliderChangedData as W, type InformationWallSearchData as X, type InteractionBlockRegistration as Y, type InteractionData as Z, type InteractionPlugin as _, type CheckId as a, type LessonLifecycleData as a0, type LessonkitPluginKind as a1, type McqAssessmentProps as a2, type MemoryCardFlippedData as a3, type ParallaxSlideViewedData as a4, type PluginIdentity as a5, type QuestionnaireSubmittedData as a6, type QuizAnsweredData as a7, type QuizCompletedData as a8, SESSION_STORAGE_KEY as a9, resetTelemetryBuilderWarningsForTests as aA, resolveSessionId as aB, tryBuildTelemetryEvent as aC, tryEmitCourseStarted as aD, resetCourseStartedEmitFlightForTests as aE, type SlideViewedData as aa, type TelemetryEventBase as ab, type TimerPort as ac, type VideoCueReachedData as ad, type VideoSegmentCompletedData as ae, buildCourseStartedTelemetryEvent as af, buildTelemetryEvent as ag, completeCourseWithTelemetry as ah, completeLessonWithTelemetry as ai, createDefaultClock as aj, createGlobalTimer as ak, createNoopStorage as al, createProgressController as am, createSessionStoragePort as an, getTabSessionId as ao, hasCourseStarted as ap, hasCourseStartedEmittedToTracking as aq, hasCourseStartedPipelineDelivered as ar, hasCourseStartedXapiSent as as, markCourseStarted as at, markCourseStartedEmittedToTracking as au, markCourseStartedPipelineDelivered as av, markCourseStartedXapiSent as aw, migrateCourseStartedMark as ax, resetSharedVolatileSessionIdForTests as ay, resetStoragePortForTests as az, type IdentityValidationResult as b, type LessonkitUrn as c, type TelemetrySink as d, type TelemetryBatchSink as e, type TrackingClient as f, type TelemetryEvent as g, type TelemetryUser as h, type LessonkitPlugin as i, type ProgressController as j, type PluginHost as k, type ProgressState as l, type TelemetryDataFor as m, type AssessmentScoreInput as n, type AssessmentScoreResult as o, type ClockPort as p, type AssessmentPlugin as q, type LifecyclePlugin as r, type TelemetryPlugin as s, type LessonkitPluginContext as t, type AccordionSectionToggledData as u, type AssessmentAnsweredData as v, type AssessmentBaseProps as w, type AssessmentBehaviour as x, type AssessmentCompletedData as y, type AssessmentHandle as z };
788
+ export { type InteractionPlugin as $, type AssessmentResumeState as A, type BlockId as B, type CourseId as C, type AssessmentInteractionType as D, type AssessmentXAPIData as E, type BookPageViewedData as F, type BranchNodeViewedData as G, type BranchSelectedData as H, type IdentityIdPath as I, type BuildTelemetryEventInput as J, type CompoundPageViewedData as K, type LessonId as L, type McqAssessmentProps as M, type CourseLifecycleContext as N, type CourseLifecycleDeps as O, type PluginRegistry as P, type FlashcardFlippedData as Q, type HotspotOpenedData as R, type StoragePort as S, type TelemetryEventName as T, ID_MAX_LENGTH as U, ID_PATTERN as V, type IdentityValidationIssue as W, type ImageSliderChangedData as X, type InformationWallSearchData as Y, type InteractionBlockRegistration as Z, type InteractionData as _, type CheckId as a, type InvalidSessionIdContext as a0, type LessonCompletionEmitter as a1, type LessonLifecycleData as a2, type LessonkitPluginKind as a3, type MemoryCardFlippedData as a4, type ParallaxSlideViewedData as a5, type PluginIdentity as a6, type QuestionnaireSubmittedData as a7, type QuizAnsweredData as a8, type QuizCompletedData as a9, resetSharedVolatileSessionIdForTests as aA, resetStoragePortForTests as aB, resetTelemetryBuilderWarningsForTests as aC, resolveSessionId as aD, tryBuildTelemetryEvent as aE, tryEmitCourseStarted as aF, resetCourseStartedEmitFlightForTests as aG, type ResolveSessionIdOptions as aa, SESSION_STORAGE_KEY as ab, type SlideViewedData as ac, type TelemetryEventBase as ad, type TimerPort as ae, type VideoCueReachedData as af, type VideoSegmentCompletedData as ag, buildCourseStartedTelemetryEvent as ah, buildTelemetryEvent as ai, completeCourseWithTelemetry as aj, completeLessonWithTelemetry as ak, createDefaultClock as al, createGlobalTimer as am, createNoopStorage as an, createProgressController as ao, createSessionStoragePort as ap, getTabSessionId as aq, hasCourseStarted as ar, hasCourseStartedEmittedToTracking as as, hasCourseStartedPipelineDelivered as at, hasCourseStartedXapiSent as au, markCourseStarted as av, markCourseStartedEmittedToTracking as aw, markCourseStartedPipelineDelivered as ax, markCourseStartedXapiSent as ay, migrateCourseStartedMark as az, type IdentityValidationResult as b, type LessonkitUrn as c, type TelemetrySink as d, type TelemetryBatchSink as e, type TrackingClient as f, type TelemetryEvent as g, type TelemetryUser as h, type LessonkitPlugin as i, type ProgressController as j, type PluginHost as k, type ProgressState as l, type TelemetryDataFor as m, type AssessmentScoreInput as n, type AssessmentScoreResult as o, type ClockPort as p, type AssessmentPlugin as q, type LifecyclePlugin as r, type TelemetryPlugin as s, type LessonkitPluginContext as t, type AccordionSectionToggledData as u, type AssessmentAnsweredData as v, type AssessmentBaseProps as w, type AssessmentBehaviour as x, type AssessmentCompletedData as y, type AssessmentHandle as z };
package/dist/testing.cjs CHANGED
@@ -46,8 +46,10 @@ function resetSharedVolatileSessionIdForTests() {
46
46
 
47
47
  // src/runtime/courseLifecycle.ts
48
48
  var courseStartedEmitFlights = /* @__PURE__ */ new Map();
49
+ var courseStartedEmittedInTab = /* @__PURE__ */ new Set();
49
50
  function resetCourseStartedEmitFlightForTests() {
50
51
  courseStartedEmitFlights.clear();
52
+ courseStartedEmittedInTab.clear();
51
53
  }
52
54
  // Annotate the CommonJS export names for ESM import in node:
53
55
  0 && (module.exports = {
@@ -1 +1 @@
1
- export { aE as resetCourseStartedEmitFlightForTests, ay as resetSharedVolatileSessionIdForTests, az as resetStoragePortForTests, aA as resetTelemetryBuilderWarningsForTests } from './testing-BFr8oEfw.cjs';
1
+ export { aG as resetCourseStartedEmitFlightForTests, aA as resetSharedVolatileSessionIdForTests, aB as resetStoragePortForTests, aC as resetTelemetryBuilderWarningsForTests } from './testing-CQ-ZsT7D.cjs';
package/dist/testing.d.ts CHANGED
@@ -1 +1 @@
1
- export { aE as resetCourseStartedEmitFlightForTests, ay as resetSharedVolatileSessionIdForTests, az as resetStoragePortForTests, aA as resetTelemetryBuilderWarningsForTests } from './testing-BFr8oEfw.js';
1
+ export { aG as resetCourseStartedEmitFlightForTests, aA as resetSharedVolatileSessionIdForTests, aB as resetStoragePortForTests, aC as resetTelemetryBuilderWarningsForTests } from './testing-CQ-ZsT7D.js';
package/dist/testing.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  resetSharedVolatileSessionIdForTests,
4
4
  resetStoragePortForTests,
5
5
  resetTelemetryBuilderWarningsForTests
6
- } from "./chunk-KFXFQ6B2.js";
6
+ } from "./chunk-DTJGIMUU.js";
7
7
  export {
8
8
  resetCourseStartedEmitFlightForTests,
9
9
  resetSharedVolatileSessionIdForTests,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/core",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "private": false,
5
5
  "description": "Shared types and telemetry primitives for LessonKit.",
6
6
  "license": "Apache-2.0",
@@ -48,8 +48,8 @@
48
48
  "identity-contract.v1.json"
49
49
  ],
50
50
  "scripts": {
51
- "build": "tsup src/index.ts src/testing.ts --format esm,cjs --dts",
52
- "dev": "tsup src/index.ts src/testing.ts --format esm,cjs --dts --watch",
51
+ "build": "tsup src/index.ts src/testing.ts --format esm,cjs --dts --tsconfig tsconfig.build.json",
52
+ "dev": "tsup src/index.ts src/testing.ts --format esm,cjs --dts --watch --tsconfig tsconfig.build.json",
53
53
  "prepublishOnly": "npm run build",
54
54
  "typecheck": "tsc -p tsconfig.json",
55
55
  "test": "vitest run --passWithNoTests",