@ct-player/embed 1.2.0 → 1.2.1

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.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import * as react from 'react';
2
- import react__default from 'react';
1
+ import * as React__default from 'react';
2
+ import React__default__default from 'react';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import Hls from 'hls.js';
5
5
 
@@ -11,7 +11,7 @@ import Hls from 'hls.js';
11
11
  /**
12
12
  * All possible event types in the .ct format
13
13
  */
14
- type CTEventType = 'init' | 'code' | 'code_edit' | 'language' | 'terminal' | 'terminal_input' | 'stroke_start' | 'whiteboard_points' | 'stroke_end' | 'whiteboard_stroke' | 'whiteboard_clear' | 'whiteboard_undo' | 'whiteboard' | 'whiteboard_point' | 'excalidraw_scene' | 'excalidraw_clear' | 'document_load' | 'document_page' | 'document_zoom' | 'document_scroll' | 'document_clear' | 'file_create' | 'file_update' | 'file_delete' | 'file_rename' | 'file_switch' | 'folder_create' | 'folder_delete' | 'file_select' | 'file_change' | 'file_creation_start' | 'file_creation_input' | 'file_creation_cancel' | 'file_rename_start' | 'file_rename_input' | 'file_rename_cancel' | 'folder_toggle' | 'cursor_move' | 'cursor_click' | 'cursor_enter' | 'cursor_leave' | 'file_hover' | 'text_select' | 'editor_cursor' | 'tool_switch' | 'tool' | 'marker';
14
+ type CTEventType = 'init' | 'code' | 'code_edit' | 'language' | 'terminal' | 'terminal_input' | 'stroke_start' | 'whiteboard_points' | 'stroke_end' | 'whiteboard_stroke' | 'whiteboard_clear' | 'whiteboard_undo' | 'whiteboard' | 'whiteboard_point' | 'excalidraw_scene' | 'excalidraw_clear' | 'excalidraw_pointer' | 'overlay_scene' | 'overlay_clear' | 'overlay_pointer' | 'document_load' | 'document_page' | 'document_zoom' | 'document_scroll' | 'document_clear' | 'file_create' | 'file_update' | 'file_delete' | 'file_rename' | 'file_switch' | 'folder_create' | 'folder_delete' | 'file_select' | 'file_change' | 'file_creation_start' | 'file_creation_input' | 'file_creation_cancel' | 'file_rename_start' | 'file_rename_input' | 'file_rename_cancel' | 'folder_toggle' | 'cursor_move' | 'cursor_click' | 'cursor_enter' | 'cursor_leave' | 'file_hover' | 'text_select' | 'editor_cursor' | 'tool_switch' | 'tool' | 'marker' | 'webcam_state' | 'content_dimensions';
15
15
  /**
16
16
  * Base event structure with generic type parameter
17
17
  */
@@ -27,7 +27,7 @@ interface CTEventBase<T extends CTEventType = CTEventType> {
27
27
  * Discriminated union of all event types.
28
28
  * This enables proper type narrowing in switch statements.
29
29
  */
30
- type CTEvent = CTEventBase<'init'> | CTEventBase<'code'> | CTEventBase<'code_edit'> | CTEventBase<'language'> | CTEventBase<'terminal'> | CTEventBase<'terminal_input'> | CTEventBase<'stroke_start'> | CTEventBase<'whiteboard_points'> | CTEventBase<'stroke_end'> | CTEventBase<'whiteboard_stroke'> | CTEventBase<'whiteboard_clear'> | CTEventBase<'whiteboard_undo'> | CTEventBase<'whiteboard'> | CTEventBase<'whiteboard_point'> | CTEventBase<'excalidraw_scene'> | CTEventBase<'excalidraw_clear'> | CTEventBase<'document_load'> | CTEventBase<'document_page'> | CTEventBase<'document_zoom'> | CTEventBase<'document_scroll'> | CTEventBase<'document_clear'> | CTEventBase<'file_create'> | CTEventBase<'file_update'> | CTEventBase<'file_delete'> | CTEventBase<'file_rename'> | CTEventBase<'file_switch'> | CTEventBase<'folder_create'> | CTEventBase<'folder_delete'> | CTEventBase<'file_select'> | CTEventBase<'file_change'> | CTEventBase<'file_creation_start'> | CTEventBase<'file_creation_input'> | CTEventBase<'file_creation_cancel'> | CTEventBase<'file_rename_start'> | CTEventBase<'file_rename_input'> | CTEventBase<'file_rename_cancel'> | CTEventBase<'folder_toggle'> | CTEventBase<'cursor_move'> | CTEventBase<'cursor_click'> | CTEventBase<'cursor_enter'> | CTEventBase<'cursor_leave'> | CTEventBase<'file_hover'> | CTEventBase<'text_select'> | CTEventBase<'editor_cursor'> | CTEventBase<'tool_switch'> | CTEventBase<'tool'> | CTEventBase<'marker'>;
30
+ type CTEvent = CTEventBase<'init'> | CTEventBase<'code'> | CTEventBase<'code_edit'> | CTEventBase<'language'> | CTEventBase<'terminal'> | CTEventBase<'terminal_input'> | CTEventBase<'stroke_start'> | CTEventBase<'whiteboard_points'> | CTEventBase<'stroke_end'> | CTEventBase<'whiteboard_stroke'> | CTEventBase<'whiteboard_clear'> | CTEventBase<'whiteboard_undo'> | CTEventBase<'whiteboard'> | CTEventBase<'whiteboard_point'> | CTEventBase<'excalidraw_scene'> | CTEventBase<'excalidraw_clear'> | CTEventBase<'excalidraw_pointer'> | CTEventBase<'overlay_scene'> | CTEventBase<'overlay_clear'> | CTEventBase<'overlay_pointer'> | CTEventBase<'document_load'> | CTEventBase<'document_page'> | CTEventBase<'document_zoom'> | CTEventBase<'document_scroll'> | CTEventBase<'document_clear'> | CTEventBase<'file_create'> | CTEventBase<'file_update'> | CTEventBase<'file_delete'> | CTEventBase<'file_rename'> | CTEventBase<'file_switch'> | CTEventBase<'folder_create'> | CTEventBase<'folder_delete'> | CTEventBase<'file_select'> | CTEventBase<'file_change'> | CTEventBase<'file_creation_start'> | CTEventBase<'file_creation_input'> | CTEventBase<'file_creation_cancel'> | CTEventBase<'file_rename_start'> | CTEventBase<'file_rename_input'> | CTEventBase<'file_rename_cancel'> | CTEventBase<'folder_toggle'> | CTEventBase<'cursor_move'> | CTEventBase<'cursor_click'> | CTEventBase<'cursor_enter'> | CTEventBase<'cursor_leave'> | CTEventBase<'file_hover'> | CTEventBase<'text_select'> | CTEventBase<'editor_cursor'> | CTEventBase<'tool_switch'> | CTEventBase<'tool'> | CTEventBase<'marker'> | CTEventBase<'webcam_state'> | CTEventBase<'content_dimensions'>;
31
31
  /**
32
32
  * Mapping of event types to their data structures
33
33
  */
@@ -46,6 +46,10 @@ interface CTEventDataMap {
46
46
  whiteboard_undo: WhiteboardUndoEventData;
47
47
  excalidraw_scene: ExcalidrawSceneEventData;
48
48
  excalidraw_clear: ExcalidrawClearEventData;
49
+ excalidraw_pointer: ExcalidrawPointerEventData;
50
+ overlay_scene: ExcalidrawSceneEventData;
51
+ overlay_clear: ExcalidrawClearEventData;
52
+ overlay_pointer: ExcalidrawPointerEventData;
49
53
  document_load: DocumentLoadEventData;
50
54
  document_page: DocumentPageEventData;
51
55
  document_zoom: DocumentZoomEventData;
@@ -74,6 +78,8 @@ interface CTEventDataMap {
74
78
  editor_cursor: EditorCursorEventData;
75
79
  tool_switch: ToolSwitchEventData;
76
80
  marker: MarkerEventData;
81
+ webcam_state: WebcamStateEventData;
82
+ content_dimensions: ContentDimensionsEventData;
77
83
  tool: ToolEventData;
78
84
  whiteboard: WhiteboardEventData;
79
85
  whiteboard_point: WhiteboardPointEventData;
@@ -221,6 +227,10 @@ interface InitEventData {
221
227
  x: number;
222
228
  y: number;
223
229
  };
230
+ contentDimensions?: {
231
+ width: number;
232
+ height: number;
233
+ };
224
234
  }
225
235
  /**
226
236
  * Code content change
@@ -380,6 +390,15 @@ interface ExcalidrawSceneEventData {
380
390
  */
381
391
  interface ExcalidrawClearEventData {
382
392
  }
393
+ /**
394
+ * Excalidraw pointer update (laser pointer, tool changes)
395
+ */
396
+ interface ExcalidrawPointerEventData {
397
+ x: number;
398
+ y: number;
399
+ tool: 'pointer' | 'laser';
400
+ button: 'down' | 'up';
401
+ }
383
402
  /**
384
403
  * Load document
385
404
  */
@@ -587,7 +606,22 @@ interface ToolSwitchEventData {
587
606
  interface MarkerEventData {
588
607
  id: string;
589
608
  label: string;
590
- type: 'chapter' | 'highlight' | 'quiz' | 'note';
609
+ type: 'chapter' | 'bookmark' | 'quiz' | 'note';
610
+ }
611
+ /**
612
+ * Webcam state event for maximize/minimize during recording
613
+ */
614
+ interface WebcamStateEventData {
615
+ /** Whether the webcam is maximized (full overlay) */
616
+ maximized: boolean;
617
+ }
618
+ /**
619
+ * Content dimensions event — captures the recording container size
620
+ * for accurate cursor alignment during playback
621
+ */
622
+ interface ContentDimensionsEventData {
623
+ width: number;
624
+ height: number;
591
625
  }
592
626
  /**
593
627
  * Legacy tool event (same as tool_switch)
@@ -683,6 +717,32 @@ interface PlaybackState$1 {
683
717
  textSelection: TextSelectionState | null;
684
718
  /** Editor typing cursor (caret) position */
685
719
  editorCursor: EditorCursorState | null;
720
+ /** Whether webcam is in maximized (full overlay) mode */
721
+ webcamMaximized: boolean;
722
+ /** Content area dimensions from the recording session.
723
+ * Used to correctly position the cursor overlay during playback when
724
+ * the player container is a different size than the recording container.
725
+ * Without this, the cursor diverges from Excalidraw's absolute coordinates. */
726
+ contentDimensions: {
727
+ width: number;
728
+ height: number;
729
+ } | null;
730
+ /** Excalidraw pointer state (for laser pointer playback) */
731
+ excalidrawPointer: {
732
+ x: number;
733
+ y: number;
734
+ tool: 'pointer' | 'laser';
735
+ button: 'down' | 'up';
736
+ } | null;
737
+ /** Drawing overlay scene (floating overlay on non-whiteboard panels) */
738
+ overlayScene: ExcalidrawScene | null;
739
+ /** Drawing overlay pointer state (laser pointer on overlay) */
740
+ overlayPointer: {
741
+ x: number;
742
+ y: number;
743
+ tool: 'pointer' | 'laser';
744
+ button: 'down' | 'up';
745
+ } | null;
686
746
  }
687
747
  /**
688
748
  * State for file/folder creation UI display during playback
@@ -1071,7 +1131,7 @@ interface CTManifest {
1071
1131
  media: CTMediaInfo;
1072
1132
  /** Events reference (chunked in v1.1.0+, single file in v1.0.0) */
1073
1133
  events: CTEventsInfo;
1074
- /** Chapter/highlight markers */
1134
+ /** Chapter/bookmark markers */
1075
1135
  markers: CTMarker[];
1076
1136
  /** Optional embedded assets */
1077
1137
  assets?: CTAssets;
@@ -1144,7 +1204,7 @@ interface CTWaveformInfo {
1144
1204
  samplesPerSecond: number;
1145
1205
  }
1146
1206
  /**
1147
- * Chapter/highlight marker
1207
+ * Chapter/bookmark marker
1148
1208
  */
1149
1209
  interface CTMarker {
1150
1210
  /** Unique identifier */
@@ -1155,13 +1215,16 @@ interface CTMarker {
1155
1215
  label: string;
1156
1216
  /** Marker type */
1157
1217
  type: CTMarkerType;
1158
- /** Type-specific additional data */
1218
+ /**
1219
+ * Type-specific additional data.
1220
+ * When `type === 'quiz'`, this should conform to {@link MCQMarkerData}.
1221
+ */
1159
1222
  data?: Record<string, unknown>;
1160
1223
  }
1161
1224
  /**
1162
1225
  * Marker types
1163
1226
  */
1164
- type CTMarkerType = 'chapter' | 'highlight' | 'quiz' | 'note';
1227
+ type CTMarkerType = 'chapter' | 'bookmark' | 'quiz' | 'note';
1165
1228
  /**
1166
1229
  * Embedded assets manifest
1167
1230
  */
@@ -1551,6 +1614,21 @@ interface IDEPanelProps extends BasePanelProps {
1551
1614
  line: number;
1552
1615
  column: number;
1553
1616
  }) => void;
1617
+ /**
1618
+ * Called when user interacts with the IDE (clicks explorer, terminal, etc).
1619
+ * Use this to pause playback when user starts interacting.
1620
+ */
1621
+ onInteractionStart?: () => void;
1622
+ /** Whether to show AI inline suggestions (recording mode only). Default: false */
1623
+ aiSuggestionsEnabled?: boolean;
1624
+ /** API endpoint URL for AI completions. Default: '/api/ai/complete' */
1625
+ aiSuggestionsEndpoint?: string;
1626
+ /** Debounce delay in ms before requesting a completion. Default: 300 */
1627
+ aiSuggestionsDebounceMs?: number;
1628
+ /** Whether the AI debug feature is available. Default: false */
1629
+ aiDebugEnabled?: boolean;
1630
+ /** API endpoint for AI debug analysis. Default: '/api/ai/debug' */
1631
+ aiDebugEndpoint?: string;
1554
1632
  }
1555
1633
  /**
1556
1634
  * Excalidraw scene structure (simplified)
@@ -1570,6 +1648,22 @@ interface WhiteboardPanelProps extends BasePanelProps {
1570
1648
  onSceneChange?: (scene: WhiteboardScene) => void;
1571
1649
  /** Called when scene is cleared */
1572
1650
  onSceneClear?: () => void;
1651
+ /** Called when pointer updates (laser pointer, drawing pointer) */
1652
+ onPointerUpdate?: (payload: {
1653
+ pointer: {
1654
+ x: number;
1655
+ y: number;
1656
+ tool: 'pointer' | 'laser';
1657
+ };
1658
+ button: 'down' | 'up';
1659
+ }) => void;
1660
+ /** Excalidraw pointer state for playback (laser trail rendering) */
1661
+ pointerState?: {
1662
+ x: number;
1663
+ y: number;
1664
+ tool: 'pointer' | 'laser';
1665
+ button: 'down' | 'up';
1666
+ } | null;
1573
1667
  /** Show Excalidraw toolbar */
1574
1668
  showToolbar?: boolean;
1575
1669
  /** Background color */
@@ -1647,6 +1741,8 @@ interface DocumentPanelProps extends BasePanelProps {
1647
1741
  allowMultiple?: boolean;
1648
1742
  /** Background color */
1649
1743
  backgroundColor?: string;
1744
+ /** Base URL for asset upload endpoint (e.g. '/api/assets'). PPTX files are uploaded here to get a public URL for iframe embedding. */
1745
+ assetUploadUrl?: string;
1650
1746
  }
1651
1747
  /**
1652
1748
  * Terminal component props
@@ -1763,13 +1859,15 @@ interface ToolSidebarProps {
1763
1859
  tools?: ToolType[];
1764
1860
  /** Additional CSS classes */
1765
1861
  className?: string;
1862
+ /** Inline styles (e.g. background override) */
1863
+ style?: React.CSSProperties;
1766
1864
  }
1767
1865
 
1768
- declare function ToolSidebar({ activeTool, onToolChange, tools, className, }: ToolSidebarProps): react_jsx_runtime.JSX.Element;
1866
+ declare function ToolSidebar({ activeTool, onToolChange, tools, className, style, }: ToolSidebarProps): react_jsx_runtime.JSX.Element;
1769
1867
 
1770
- declare function WhiteboardPanel({ scene, onSceneChange, onSceneClear, mode, isPlaying, onInteract, showToolbar, backgroundColor, className, }: WhiteboardPanelProps): react_jsx_runtime.JSX.Element;
1868
+ declare function WhiteboardPanel({ scene, onSceneChange, onSceneClear, onPointerUpdate, pointerState, mode, isPlaying, onInteract, showToolbar, backgroundColor, className, }: WhiteboardPanelProps): react_jsx_runtime.JSX.Element;
1771
1869
 
1772
- declare function DocumentPanel({ documentData, documents: externalDocuments, activeDocumentId: externalActiveId, currentPage, zoom, scrollPosition, onDocumentLoad, onDocumentSelect, onDocumentRemove, onPageChange, onZoomChange, onScrollChange, onDocumentClear, mode, isPlaying, onInteract, showTabBar, showToolbar, showPageIndicator, allowUpload, allowMultiple, backgroundColor, className, }: DocumentPanelProps): react_jsx_runtime.JSX.Element;
1870
+ declare function DocumentPanel({ documentData, documents: externalDocuments, activeDocumentId: externalActiveId, currentPage, zoom, scrollPosition, onDocumentLoad, onDocumentSelect, onDocumentRemove, onPageChange, onZoomChange, onScrollChange, onDocumentClear, mode, isPlaying, onInteract, showTabBar, showToolbar, showPageIndicator, allowUpload, allowMultiple, backgroundColor, assetUploadUrl, className, }: DocumentPanelProps): react_jsx_runtime.JSX.Element;
1773
1871
 
1774
1872
  interface VirtualFileSystem {
1775
1873
  getFileTree: () => FileSystemEntry;
@@ -1784,103 +1882,62 @@ interface VirtualFileSystem {
1784
1882
  notifyChanged?: () => void;
1785
1883
  mkdir?: (path: string) => void;
1786
1884
  }
1787
- declare function IDEPanel({ fileSystem, tree: externalTree, files: externalFiles, code: externalCode, language: externalLanguage, currentFile: externalCurrentFile, onCodeChange, onFileChange, onFileCreate, onFileDelete, onFileRename, onFileSelect, onFolderCreate, onFolderDelete, onTerminalCommand, onTerminalClear, onTerminalAddLines, fileCreationState, onFileCreationStart, onFileCreationInput, onFileCreationCancel, fileRenamingState, onFileRenameStart, onFileRenameInput, onFileRenameCancel, expandedFoldersState, onFolderToggle, terminalLines: externalTerminalLines, terminalInput: externalTerminalInput, onTerminalInputChange, onExecute, onTerminalExecute, mode, isPlaying, defaultShowExplorer, defaultShowTerminal, showRunButton, interactiveLabel, initialFiles, initialActiveFile, hoveredFile, onFileHover, textSelection, onTextSelect, editorCursor, onEditorCursorChange }: IDEPanelProps & {
1885
+ declare function IDEPanel({ fileSystem, tree: externalTree, files: externalFiles, code: externalCode, language: externalLanguage, currentFile: externalCurrentFile, onCodeChange, onFileChange, onFileCreate, onFileDelete, onFileRename, onFileSelect, onFolderCreate, onFolderDelete, onTerminalCommand, onTerminalClear, onTerminalAddLines, fileCreationState, onFileCreationStart, onFileCreationInput, onFileCreationCancel, fileRenamingState, onFileRenameStart, onFileRenameInput, onFileRenameCancel, expandedFoldersState, onFolderToggle, terminalLines: externalTerminalLines, terminalInput: externalTerminalInput, onTerminalInputChange, onExecute, onTerminalExecute, mode, isPlaying, defaultShowExplorer, defaultShowTerminal, showRunButton, interactiveLabel, initialFiles, initialActiveFile, hoveredFile, onFileHover, textSelection, onTextSelect, editorCursor, onEditorCursorChange, onInteractionStart, aiSuggestionsEnabled, aiSuggestionsEndpoint, aiSuggestionsDebounceMs, aiDebugEnabled, aiDebugEndpoint, }: IDEPanelProps & {
1788
1886
  fileSystem?: VirtualFileSystem;
1789
1887
  }): react_jsx_runtime.JSX.Element;
1790
1888
 
1791
1889
  declare function FileExplorer({ tree, selectedFile, expandedFolders, fileCreation, fileRenaming, mode, isPlaying, hoveredFile, onFileHover, onFileSelect, onFileCreate, onFolderCreate, onFileDelete, onFileRename, onFolderToggle, onFileCreationStart, onFileCreationInput, onFileCreationCancel, onFileRenameStart, onFileRenameInput, onFileRenameCancel, className, }: FileExplorerProps): react_jsx_runtime.JSX.Element;
1792
1890
 
1793
- declare const Terminal: react.ForwardRefExoticComponent<TerminalProps & react.RefAttributes<TerminalRef>>;
1891
+ declare const Terminal: React__default.ForwardRefExoticComponent<TerminalProps & React__default.RefAttributes<TerminalRef>>;
1794
1892
 
1795
1893
  /**
1796
- * Active tool type - matches ToolType from @ct-courses/ui
1894
+ * Learner Branch Types
1895
+ *
1896
+ * A branch is a saved snapshot of the learner's code edits at a specific
1897
+ * moment in the instructor's timeline. Branches are learner-side only —
1898
+ * they never modify the .ct file, events, or manifest.
1899
+ *
1900
+ * @packageDocumentation
1797
1901
  */
1798
- type ActiveTool$1 = ToolType;
1799
1902
  /**
1800
- * Props for the CoursePlayer component
1903
+ * A point-in-time capture of the IDE state.
1904
+ * Used for both the instructor's original state and the learner's edited state.
1801
1905
  */
1802
- interface CoursePlayerProps {
1803
- /** Pre-loaded recording data */
1804
- recording?: CTRecording;
1805
- /** URL to a .ct file */
1806
- recordingUrl?: string;
1807
- /** Recording ID to fetch from API */
1808
- recordingId?: string;
1809
- /** Base API URL for fetching recordings */
1810
- apiUrl?: string;
1811
- /** Authentication token */
1812
- authToken?: string;
1813
- /** Go-Judge server URL */
1814
- goJudgeUrl?: string;
1815
- /** Go-Judge API key */
1816
- goJudgeApiKey?: string;
1817
- /** Theme setting */
1818
- theme?: 'light' | 'dark' | 'system';
1819
- /** Aspect ratio of the player */
1820
- aspectRatio?: '16:9' | '4:3' | 'auto';
1821
- /** Show playback controls toolbar */
1822
- showToolbar?: boolean;
1823
- /** Show timeline below player */
1824
- showTimeline?: boolean;
1825
- /** Show chapter markers */
1826
- showChapters?: boolean;
1827
- /** Show tool sidebar */
1828
- showToolSidebar?: boolean;
1829
- /** Auto-play when loaded */
1830
- autoPlay?: boolean;
1831
- /** Default playback speed */
1832
- defaultSpeed?: number;
1833
- /** Allow fullscreen mode */
1834
- allowFullscreen?: boolean;
1835
- /** Default active tool */
1836
- defaultTool?: ActiveTool$1;
1837
- /** Called when player is ready */
1838
- onReady?: () => void;
1839
- /** Called when playback starts */
1840
- onPlay?: () => void;
1841
- /** Called when playback pauses */
1842
- onPause?: () => void;
1843
- /** Called on progress update */
1844
- onProgress?: (time: number, duration: number) => void;
1845
- /** Called when playback completes */
1846
- onComplete?: () => void;
1847
- /** Called on error */
1848
- onError?: (error: Error) => void;
1849
- /** Called when entering interactive mode */
1850
- onInteractionStart?: () => void;
1851
- /** Called when exiting interactive mode */
1852
- onInteractionEnd?: (code: string) => void;
1853
- /** Called when tool changes */
1854
- onToolChange?: (tool: string) => void;
1906
+ interface BranchSnapshot {
1907
+ /** Active file's code content */
1908
+ code: string;
1909
+ /** Programming language at snapshot time */
1910
+ language: string;
1911
+ /** Which file tab was active (null if single-file mode) */
1912
+ currentFile: string | null;
1913
+ /** Full file system snapshot path → content */
1914
+ fileSystem: Record<string, string>;
1915
+ /** Terminal output lines if any existed */
1916
+ terminalOutput?: string[];
1917
+ /** Which tool panel was open (code / terminal / whiteboard / document) */
1918
+ activeTool: string;
1919
+ /** Folder paths created by the learner during interactive mode */
1920
+ folders?: string[];
1855
1921
  }
1856
1922
  /**
1857
- * Imperative handle for CoursePlayer
1923
+ * A learner branch — a saved code snapshot at a point in the timeline.
1858
1924
  */
1859
- interface CoursePlayerRef {
1860
- play(): void;
1861
- pause(): void;
1862
- seek(timeMs: number): void;
1863
- setSpeed(speed: number): void;
1864
- getCurrentTime(): number;
1865
- getDuration(): number;
1866
- getState(): PlaybackState$1;
1867
- enterInteractiveMode(): void;
1868
- exitInteractiveMode(): void;
1869
- setActiveTool(tool: ActiveTool$1): void;
1925
+ interface LearnerBranch {
1926
+ /** Unique branch ID (crypto.randomUUID) */
1927
+ id: string;
1928
+ /** Lesson/recording this branch belongs to */
1929
+ lessonId: string;
1930
+ /** Playback time in milliseconds where the learner paused */
1931
+ timeMs: number;
1932
+ /** Instructor's state at that moment (for comparison / restoring) */
1933
+ originalState: BranchSnapshot;
1934
+ /** Learner's edited state */
1935
+ learnerState: BranchSnapshot;
1936
+ /** ISO timestamp of when the branch was created */
1937
+ createdAt: string;
1938
+ /** User-provided label, defaults to "Branch at MM:SS" */
1939
+ label?: string;
1870
1940
  }
1871
- /**
1872
- * CoursePlayer component
1873
- */
1874
- declare const CoursePlayer: react.ForwardRefExoticComponent<CoursePlayerProps & react.RefAttributes<CoursePlayerRef>>;
1875
-
1876
- /**
1877
- * CT-Courses Player - Remote Streaming Types
1878
- *
1879
- * Types for streaming playback from a remote CDN/R2 storage.
1880
- * These types match the streaming service manifest structure.
1881
- *
1882
- * @packageDocumentation
1883
- */
1884
1941
 
1885
1942
  /**
1886
1943
  * Audio quality level information
@@ -1981,6 +2038,32 @@ interface StreamingConfig {
1981
2038
  /** Maximum buffer to maintain (ms) */
1982
2039
  maxBufferMs: number;
1983
2040
  }
2041
+ /**
2042
+ * An interaction point from the streaming manifest (CDN-safe).
2043
+ * Options intentionally omit `isCorrect` — answers validated server-side only.
2044
+ */
2045
+ interface InteractionPointManifest {
2046
+ /** Marker ID from the .ct manifest */
2047
+ id: string;
2048
+ /** Interaction type (only 'mcq' for now) */
2049
+ type: 'mcq';
2050
+ /** Time position in milliseconds */
2051
+ timeMs: number;
2052
+ /** Whether to pause playback when this point is reached */
2053
+ pausePlayback: boolean;
2054
+ /** MCQ question data (without isCorrect on options) */
2055
+ mcq: {
2056
+ question: string;
2057
+ options: {
2058
+ id: string;
2059
+ text: string;
2060
+ }[];
2061
+ explanation?: string;
2062
+ allowRetry: boolean;
2063
+ shuffleOptions: boolean;
2064
+ timeoutSeconds: number | null;
2065
+ };
2066
+ }
1984
2067
  /**
1985
2068
  * Complete streaming manifest structure
1986
2069
  * This is the main entry point returned by the streaming service.
@@ -2000,6 +2083,12 @@ interface StreamingManifest {
2000
2083
  events: StreamingEventsConfig;
2001
2084
  /** Streaming playback hints */
2002
2085
  streaming: StreamingConfig;
2086
+ /**
2087
+ * Interaction points extracted from .ct manifest quiz markers.
2088
+ * Options omit `isCorrect` — answers are validated server-side only.
2089
+ * Absent or empty when the recording has no quiz markers.
2090
+ */
2091
+ interactionPoints?: InteractionPointManifest[];
2003
2092
  }
2004
2093
  /**
2005
2094
  * Options for useRemoteStreamingPlayback hook
@@ -2107,6 +2196,12 @@ interface RemoteStreamingPlaybackControls {
2107
2196
  getVideoElement(): HTMLVideoElement | null;
2108
2197
  /** Attach HLS video player to a video element (from WebcamOverlay onVideoRef) */
2109
2198
  initVideoHls(videoElement: HTMLVideoElement | null): void;
2199
+ /** Get available webcam video quality levels */
2200
+ getVideoQualityLevels(): HLSQualityLevel[];
2201
+ /** Set webcam video quality level (-1 for auto) */
2202
+ setVideoQualityLevel(index: number): void;
2203
+ /** Get current webcam video quality level (-1 = auto) */
2204
+ getVideoQualityLevel(): number;
2110
2205
  }
2111
2206
  /**
2112
2207
  * HLS player configuration
@@ -2131,81 +2226,12 @@ interface HLSQualityLevel {
2131
2226
  bitrate: number;
2132
2227
  /** Codec string */
2133
2228
  codecs: string;
2229
+ /** Video width (0 if audio-only) */
2230
+ width?: number;
2231
+ /** Video height (0 if audio-only) */
2232
+ height?: number;
2134
2233
  }
2135
2234
 
2136
- type ActiveTool = ToolType;
2137
- /**
2138
- * Props for StreamingCoursePlayer
2139
- */
2140
- interface StreamingCoursePlayerProps {
2141
- /** URL to the streaming manifest.json */
2142
- manifestUrl: string;
2143
- /** Theme setting */
2144
- theme?: 'light' | 'dark' | 'system';
2145
- /** Aspect ratio of the player */
2146
- aspectRatio?: '16:9' | '4:3' | 'auto';
2147
- /** Show playback controls toolbar */
2148
- showToolbar?: boolean;
2149
- /** Show tool sidebar */
2150
- showToolSidebar?: boolean;
2151
- /** Auto-play when loaded */
2152
- autoPlay?: boolean;
2153
- /** Default playback speed */
2154
- defaultSpeed?: number;
2155
- /** Allow fullscreen mode */
2156
- allowFullscreen?: boolean;
2157
- /** Default active tool */
2158
- defaultTool?: ActiveTool;
2159
- /** Maximum chunks to cache */
2160
- maxCacheSize?: number;
2161
- /** Additional CSS class */
2162
- className?: string;
2163
- /** Called when player is ready */
2164
- onReady?: () => void;
2165
- /** Called when playback starts */
2166
- onPlay?: () => void;
2167
- /** Called when playback pauses */
2168
- onPause?: () => void;
2169
- /** Called on progress update */
2170
- onProgress?: (time: number, duration: number) => void;
2171
- /** Called when playback completes */
2172
- onComplete?: () => void;
2173
- /** Called on error */
2174
- onError?: (error: Error) => void;
2175
- /** Called when buffering state changes */
2176
- onBuffering?: (isBuffering: boolean) => void;
2177
- /** Called when tool changes */
2178
- onToolChange?: (tool: string) => void;
2179
- /** Go-Judge server URL */
2180
- goJudgeUrl?: string;
2181
- /** Go-Judge API key */
2182
- goJudgeApiKey?: string;
2183
- /** Show chapter markers */
2184
- showChapters?: boolean;
2185
- /** Called when entering interactive mode */
2186
- onInteractionStart?: () => void;
2187
- /** Called when exiting interactive mode */
2188
- onInteractionEnd?: (code: string) => void;
2189
- }
2190
- /**
2191
- * Imperative handle for StreamingCoursePlayer
2192
- */
2193
- interface StreamingCoursePlayerRef {
2194
- play(): void;
2195
- pause(): void;
2196
- seek(timeMs: number): Promise<void>;
2197
- setSpeed(speed: number): void;
2198
- getCurrentTime(): number;
2199
- getDuration(): number;
2200
- getState(): PlaybackState$1;
2201
- enterInteractiveMode(): void;
2202
- exitInteractiveMode(): void;
2203
- setActiveTool(tool: ActiveTool): void;
2204
- getManifest(): StreamingManifest | null;
2205
- getBufferHealth(): RemoteBufferHealth;
2206
- }
2207
- declare const StreamingCoursePlayer: react.ForwardRefExoticComponent<StreamingCoursePlayerProps & react.RefAttributes<StreamingCoursePlayerRef>>;
2208
-
2209
2235
  /**
2210
2236
  * @ct-courses/player - Type Definitions
2211
2237
  */
@@ -2448,6 +2474,248 @@ interface VolumeControlProps {
2448
2474
  onMuteToggle: () => void;
2449
2475
  className?: string;
2450
2476
  }
2477
+ /**
2478
+ * Client-side interaction point (no isCorrect — answers validated server-side).
2479
+ * Mirrors InteractionPointManifest from streaming.ts but usable without a manifest.
2480
+ */
2481
+ interface InteractionPoint {
2482
+ /** Unique identifier */
2483
+ id: string;
2484
+ /** Interaction type */
2485
+ type: 'mcq';
2486
+ /** Time position in milliseconds */
2487
+ timeMs: number;
2488
+ /** Whether to pause playback when this point is reached */
2489
+ pausePlayback: boolean;
2490
+ /** MCQ question data */
2491
+ mcq: {
2492
+ question: string;
2493
+ options: {
2494
+ id: string;
2495
+ text: string;
2496
+ }[];
2497
+ explanation?: string;
2498
+ allowRetry: boolean;
2499
+ shuffleOptions: boolean;
2500
+ timeoutSeconds: number | null;
2501
+ };
2502
+ }
2503
+ /**
2504
+ * MCQ answer submission from the player
2505
+ */
2506
+ interface MCQSubmission {
2507
+ /** The interaction point ID */
2508
+ pointId: string;
2509
+ /** Selected option IDs */
2510
+ selectedOptionIds: string[];
2511
+ }
2512
+ /**
2513
+ * MCQ answer result returned from the host app's callback
2514
+ */
2515
+ interface MCQResult {
2516
+ /** Whether the answer was correct */
2517
+ correct: boolean;
2518
+ /** Optional explanation text */
2519
+ explanation?: string;
2520
+ /** Correct option IDs (revealed after answering) */
2521
+ correctOptionIds?: string[];
2522
+ }
2523
+
2524
+ /**
2525
+ * Active tool type - matches ToolType from @ct-courses/ui
2526
+ */
2527
+ type ActiveTool$1 = ToolType;
2528
+ /**
2529
+ * Props for the CoursePlayer component
2530
+ */
2531
+ interface CoursePlayerProps {
2532
+ /** Pre-loaded recording data */
2533
+ recording?: CTRecording;
2534
+ /** URL to a .ct file */
2535
+ recordingUrl?: string;
2536
+ /** Recording ID to fetch from API */
2537
+ recordingId?: string;
2538
+ /** Base API URL for fetching recordings */
2539
+ apiUrl?: string;
2540
+ /** Authentication token */
2541
+ authToken?: string;
2542
+ /** Go-Judge server URL */
2543
+ goJudgeUrl?: string;
2544
+ /** Go-Judge API key */
2545
+ goJudgeApiKey?: string;
2546
+ /** Theme setting */
2547
+ theme?: "light" | "dark" | "system";
2548
+ /** Aspect ratio of the player */
2549
+ aspectRatio?: "16:9" | "4:3" | "auto";
2550
+ /** Show playback controls toolbar */
2551
+ showToolbar?: boolean;
2552
+ /** Show timeline below player */
2553
+ showTimeline?: boolean;
2554
+ /** Show chapter markers */
2555
+ showChapters?: boolean;
2556
+ /** Show tool sidebar */
2557
+ showToolSidebar?: boolean;
2558
+ /** Auto-play when loaded */
2559
+ autoPlay?: boolean;
2560
+ /** Default playback speed */
2561
+ defaultSpeed?: number;
2562
+ /** Allow fullscreen mode */
2563
+ allowFullscreen?: boolean;
2564
+ /** Default active tool */
2565
+ defaultTool?: ActiveTool$1;
2566
+ /** Called when player is ready */
2567
+ onReady?: () => void;
2568
+ /** Called when playback starts */
2569
+ onPlay?: () => void;
2570
+ /** Called when playback pauses */
2571
+ onPause?: () => void;
2572
+ /** Called on progress update */
2573
+ onProgress?: (time: number, duration: number) => void;
2574
+ /** Called when playback completes */
2575
+ onComplete?: () => void;
2576
+ /** Called on error */
2577
+ onError?: (error: Error) => void;
2578
+ /** Called when entering interactive mode */
2579
+ onInteractionStart?: () => void;
2580
+ /** Called when exiting interactive mode */
2581
+ onInteractionEnd?: (code: string) => void;
2582
+ /** Called when tool changes */
2583
+ onToolChange?: (tool: string) => void;
2584
+ /** Interaction points (MCQs) to display during playback */
2585
+ interactionPoints?: InteractionPoint[];
2586
+ /** Pre-answered point IDs (skip these MCQs) */
2587
+ answeredPointIds?: Set<string>;
2588
+ /** Called when a learner submits an MCQ answer. Host app validates server-side. */
2589
+ onSubmitAnswer?: (pointId: string, selectedOptionIds: string[]) => Promise<MCQResult>;
2590
+ /** Info about the next lesson for auto-advance overlay */
2591
+ nextLesson?: {
2592
+ id: string;
2593
+ title: string;
2594
+ } | null;
2595
+ /** Called when the player wants to navigate to the next lesson */
2596
+ onNavigateToLesson?: (lessonId: string) => void;
2597
+ /** Called when the current lesson completes (before transition) */
2598
+ onLessonComplete?: () => void;
2599
+ /** Lesson ID used to scope saved branches in localStorage */
2600
+ lessonId?: string;
2601
+ /** Enable the branch save feature (default: true) */
2602
+ enableBranches?: boolean;
2603
+ /** Called after a branch is saved */
2604
+ onBranchSave?: (branch: LearnerBranch) => void;
2605
+ /** Called when a learner restores a saved branch */
2606
+ onBranchRestore?: (branch: LearnerBranch) => void;
2607
+ /** Called after a branch is deleted */
2608
+ onBranchDelete?: (branchId: string) => void;
2609
+ /** Maximum branches per lesson (default: 50) */
2610
+ maxBranches?: number;
2611
+ }
2612
+ /**
2613
+ * Imperative handle for CoursePlayer
2614
+ */
2615
+ interface CoursePlayerRef {
2616
+ play(): void;
2617
+ pause(): void;
2618
+ seek(timeMs: number): void;
2619
+ setSpeed(speed: number): void;
2620
+ getCurrentTime(): number;
2621
+ getDuration(): number;
2622
+ getState(): PlaybackState$1;
2623
+ enterInteractiveMode(): void;
2624
+ exitInteractiveMode(): void;
2625
+ setActiveTool(tool: ActiveTool$1): void;
2626
+ }
2627
+ /**
2628
+ * CoursePlayer component
2629
+ */
2630
+ declare const CoursePlayer: React__default.ForwardRefExoticComponent<CoursePlayerProps & React__default.RefAttributes<CoursePlayerRef>>;
2631
+
2632
+ type ActiveTool = ToolType;
2633
+ /**
2634
+ * Props for StreamingCoursePlayer
2635
+ */
2636
+ interface StreamingCoursePlayerProps {
2637
+ /** URL to the streaming manifest.json */
2638
+ manifestUrl: string;
2639
+ /** Theme setting */
2640
+ theme?: "light" | "dark" | "system";
2641
+ /** Aspect ratio of the player */
2642
+ aspectRatio?: "16:9" | "4:3" | "auto";
2643
+ /** Show playback controls toolbar */
2644
+ showToolbar?: boolean;
2645
+ /** Show tool sidebar */
2646
+ showToolSidebar?: boolean;
2647
+ /** Auto-play when loaded */
2648
+ autoPlay?: boolean;
2649
+ /** Default playback speed */
2650
+ defaultSpeed?: number;
2651
+ /** Allow fullscreen mode */
2652
+ allowFullscreen?: boolean;
2653
+ /** Default active tool */
2654
+ defaultTool?: ActiveTool;
2655
+ /** Maximum chunks to cache */
2656
+ maxCacheSize?: number;
2657
+ /** Additional CSS class */
2658
+ className?: string;
2659
+ /** Called when player is ready */
2660
+ onReady?: () => void;
2661
+ /** Called when playback starts */
2662
+ onPlay?: () => void;
2663
+ /** Called when playback pauses */
2664
+ onPause?: () => void;
2665
+ /** Called on progress update */
2666
+ onProgress?: (time: number, duration: number) => void;
2667
+ /** Called when playback completes */
2668
+ onComplete?: () => void;
2669
+ /** Called on error */
2670
+ onError?: (error: Error) => void;
2671
+ /** Called when buffering state changes */
2672
+ onBuffering?: (isBuffering: boolean) => void;
2673
+ /** Called when tool changes */
2674
+ onToolChange?: (tool: string) => void;
2675
+ /** Go-Judge server URL */
2676
+ goJudgeUrl?: string;
2677
+ /** Go-Judge API key */
2678
+ goJudgeApiKey?: string;
2679
+ /** Show chapter markers */
2680
+ showChapters?: boolean;
2681
+ /** Called when entering interactive mode */
2682
+ onInteractionStart?: () => void;
2683
+ /** Called when exiting interactive mode */
2684
+ onInteractionEnd?: (code: string) => void;
2685
+ /** Interaction points (MCQs) to display during playback */
2686
+ interactionPoints?: InteractionPoint[];
2687
+ /** Pre-answered point IDs (skip these MCQs) */
2688
+ answeredPointIds?: Set<string>;
2689
+ /** Called when a learner submits an MCQ answer. Host app validates server-side. */
2690
+ onSubmitAnswer?: (pointId: string, selectedOptionIds: string[]) => Promise<MCQResult>;
2691
+ /** Info about the next lesson for auto-advance overlay */
2692
+ nextLesson?: {
2693
+ id: string;
2694
+ title: string;
2695
+ } | null;
2696
+ /** Called when the player wants to navigate to the next lesson */
2697
+ onNavigateToLesson?: (lessonId: string) => void;
2698
+ /** Called when the current lesson completes (before transition) */
2699
+ onLessonComplete?: () => void;
2700
+ }
2701
+ /**
2702
+ * Imperative handle for StreamingCoursePlayer
2703
+ */
2704
+ interface StreamingCoursePlayerRef {
2705
+ play(): void;
2706
+ pause(): void;
2707
+ seek(timeMs: number): Promise<void>;
2708
+ setSpeed(speed: number): void;
2709
+ getCurrentTime(): number;
2710
+ getDuration(): number;
2711
+ getState(): PlaybackState$1;
2712
+ enterInteractiveMode(): void;
2713
+ exitInteractiveMode(): void;
2714
+ setActiveTool(tool: ActiveTool): void;
2715
+ getManifest(): StreamingManifest | null;
2716
+ getBufferHealth(): RemoteBufferHealth;
2717
+ }
2718
+ declare const StreamingCoursePlayer: React__default.ForwardRefExoticComponent<StreamingCoursePlayerProps & React__default.RefAttributes<StreamingCoursePlayerRef>>;
2451
2719
 
2452
2720
  /**
2453
2721
  * usePlayback Hook
@@ -2509,6 +2777,10 @@ interface PlaybackControls {
2509
2777
  x: number;
2510
2778
  y: number;
2511
2779
  }): void;
2780
+ /** Inject a full branch snapshot into the interactive state. Only works when isInteracting is true. */
2781
+ injectInteractiveState(snapshot: BranchSnapshot): void;
2782
+ /** Get the engine's (instructor's) state at a given time, without affecting playback position. */
2783
+ getEngineStateAtTime(timeMs: number): PlaybackState$1 | null;
2512
2784
  }
2513
2785
  /**
2514
2786
  * Options for usePlayback hook
@@ -2755,7 +3027,7 @@ declare function useAudioSync(options: UseAudioSyncOptions): AudioSyncState & {
2755
3027
  * Hook for managing audio element lifecycle
2756
3028
  */
2757
3029
  declare function useAudioElement(src: string | null): {
2758
- audioRef: react.MutableRefObject<HTMLAudioElement | null>;
3030
+ audioRef: React__default.MutableRefObject<HTMLAudioElement | null>;
2759
3031
  isLoaded: boolean;
2760
3032
  error: Error | null;
2761
3033
  duration: number;
@@ -2826,12 +3098,163 @@ declare function useVideoSync(options: UseVideoSyncOptions): VideoSyncState & {
2826
3098
  * Hook for managing video element lifecycle
2827
3099
  */
2828
3100
  declare function useVideoElement(src: string | null): {
2829
- videoRef: react.MutableRefObject<HTMLVideoElement | null>;
3101
+ videoRef: React__default.MutableRefObject<HTMLVideoElement | null>;
2830
3102
  isLoaded: boolean;
2831
3103
  error: Error | null;
2832
3104
  duration: number;
2833
3105
  };
2834
3106
 
3107
+ /**
3108
+ * useInteractionPoints — monitors playback time and triggers MCQ pauses.
3109
+ *
3110
+ * The hook is player-mode agnostic: it only needs the current time (ms),
3111
+ * a pause callback, and a play callback. Both CoursePlayer and
3112
+ * StreamingCoursePlayer can use it.
3113
+ *
3114
+ * Edge cases handled:
3115
+ * - Seek past unanswered MCQ → still triggers (first unanswered point ≤ currentTime)
3116
+ * - Seek backwards past a triggered-but-unanswered point → re-evaluates
3117
+ * - Multiple MCQs at exact same time → triggers in array order
3118
+ * - Race: activePoint takes priority — no duplicate triggers while overlay is open
3119
+ *
3120
+ * @packageDocumentation
3121
+ */
3122
+
3123
+ interface UseInteractionPointsOptions {
3124
+ /** Sorted interaction points (ascending by timeMs) */
3125
+ interactionPoints: InteractionPoint[];
3126
+ /** Current playback time in milliseconds */
3127
+ currentTimeMs: number;
3128
+ /** Callback to pause playback */
3129
+ pause: () => void;
3130
+ /** Callback to resume playback */
3131
+ play: () => void;
3132
+ /** Pre-loaded set of already-answered point IDs */
3133
+ initialAnsweredPointIds?: Set<string>;
3134
+ }
3135
+ interface UseInteractionPointsResult {
3136
+ /** The interaction point currently requiring attention, or null */
3137
+ activePoint: InteractionPoint | null;
3138
+ /** Dismiss the active point (adds to answered, resumes playback) */
3139
+ dismissPoint: () => void;
3140
+ /** Mark a point as answered without dismissing (for pre-loading state) */
3141
+ markAnswered: (pointId: string) => void;
3142
+ /** Set of all answered point IDs */
3143
+ answeredPointIds: Set<string>;
3144
+ }
3145
+ declare function useInteractionPoints({ interactionPoints, currentTimeMs, pause, play, initialAnsweredPointIds, }: UseInteractionPointsOptions): UseInteractionPointsResult;
3146
+
3147
+ /**
3148
+ * useBranching — manages lesson-to-lesson transition with auto-advance countdown.
3149
+ *
3150
+ * @packageDocumentation
3151
+ */
3152
+ interface UseBranchingOptions {
3153
+ /** Next lesson info (null = course complete) */
3154
+ nextLesson: {
3155
+ id: string;
3156
+ title: string;
3157
+ } | null;
3158
+ /** Seconds before auto-navigating (default 5) */
3159
+ autoAdvanceSeconds?: number;
3160
+ /** Called when auto-advance or manual skip triggers navigation */
3161
+ onNavigate: (lessonId: string) => void;
3162
+ }
3163
+ interface UseBranchingResult {
3164
+ /** Whether the transition overlay is active */
3165
+ isTransitioning: boolean;
3166
+ /** Current countdown value (seconds remaining, 0 = done) */
3167
+ countdown: number;
3168
+ /** Start the transition overlay + countdown */
3169
+ startTransition: () => void;
3170
+ /** Skip the countdown and navigate immediately */
3171
+ skipToNext: () => void;
3172
+ /** Pause the auto-advance but keep the overlay visible */
3173
+ cancelAutoAdvance: () => void;
3174
+ }
3175
+ declare function useBranching({ nextLesson, autoAdvanceSeconds, onNavigate, }: UseBranchingOptions): UseBranchingResult;
3176
+
3177
+ /**
3178
+ * useBranches — manages learner branch state for a single lesson.
3179
+ *
3180
+ * Handles save / delete / rename / restore of learner code snapshots,
3181
+ * persisted to localStorage via branchStorage utilities.
3182
+ *
3183
+ * @packageDocumentation
3184
+ */
3185
+
3186
+ interface UseBranchesOptions {
3187
+ /** Lesson/recording ID — branches are scoped to this. Undefined = feature disabled. */
3188
+ lessonId: string | undefined;
3189
+ /** Returns current playback time in milliseconds */
3190
+ getCurrentTimeMs: () => number;
3191
+ /** Captures the learner's current IDE state (interactive mode state) */
3192
+ getCurrentState: () => BranchSnapshot;
3193
+ /** Captures the instructor's state at the current time from the engine */
3194
+ getOriginalState: () => BranchSnapshot;
3195
+ /** Called when the learner wants to restore a branch (parent orchestrates seek + inject) */
3196
+ onRestoreBranch?: (branch: LearnerBranch) => void;
3197
+ /** Called after a branch is successfully saved (for analytics / callbacks) */
3198
+ onBranchSaved?: (branch: LearnerBranch) => void;
3199
+ /** Called after a branch is deleted */
3200
+ onBranchDeleted?: (branchId: string) => void;
3201
+ /** Maximum branches per lesson (default 50) */
3202
+ maxBranches?: number;
3203
+ }
3204
+ interface UseBranchesResult {
3205
+ /** All branches for the current lesson, sorted oldest-first */
3206
+ branches: LearnerBranch[];
3207
+ /** Number of saved branches */
3208
+ branchCount: number;
3209
+ /** Whether the feature is enabled (lessonId is defined) */
3210
+ enabled: boolean;
3211
+ /** Save a new branch from the current interactive state. Returns the created branch, or null if disabled / duplicate. */
3212
+ saveBranch: (label?: string) => LearnerBranch | null;
3213
+ /** Delete a branch by ID */
3214
+ deleteBranch: (branchId: string) => void;
3215
+ /** Rename a branch */
3216
+ renameBranch: (branchId: string, label: string) => void;
3217
+ /** Restore a branch — delegates to parent via onRestoreBranch callback */
3218
+ restoreBranch: (branchId: string) => void;
3219
+ /** Check if a branch at this timeMs with this code already exists (duplicate detection) */
3220
+ isDuplicate: (timeMs: number, code: string) => boolean;
3221
+ }
3222
+ declare function useBranches(options: UseBranchesOptions): UseBranchesResult;
3223
+
3224
+ /**
3225
+ * Branch Storage — localStorage CRUD helpers
3226
+ *
3227
+ * Persists learner branches as JSON arrays keyed by lesson ID.
3228
+ * Key format: `ct-branches:<lessonId>`
3229
+ *
3230
+ * Pure functions — no React, no hooks.
3231
+ *
3232
+ * @packageDocumentation
3233
+ */
3234
+
3235
+ /**
3236
+ * Load all branches for a lesson from localStorage.
3237
+ * Returns empty array if nothing stored or on parse error.
3238
+ * Sorted by `createdAt` ascending (oldest first).
3239
+ */
3240
+ declare function loadBranches(lessonId: string): LearnerBranch[];
3241
+ /**
3242
+ * Persist a new branch. Caps at `maxBranches` per lesson (drops oldest).
3243
+ */
3244
+ declare function saveBranch(lessonId: string, branch: LearnerBranch, maxBranches?: number): void;
3245
+ /**
3246
+ * Delete a single branch by ID.
3247
+ */
3248
+ declare function deleteBranch(lessonId: string, branchId: string): void;
3249
+ /**
3250
+ * Update the label of an existing branch.
3251
+ */
3252
+ declare function updateBranchLabel(lessonId: string, branchId: string, label: string): void;
3253
+ /**
3254
+ * Format milliseconds as "MM:SS" for default branch labels.
3255
+ */
3256
+ declare function formatBranchTime(ms: number): string;
3257
+
2835
3258
  /**
2836
3259
  * HLS Loader Utility
2837
3260
  *
@@ -2927,7 +3350,14 @@ declare function destroyHlsPlayer(player: HLSPlayerInstance | HLSVideoPlayerInst
2927
3350
  interface Marker$1 {
2928
3351
  time: number;
2929
3352
  label?: string;
2930
- type?: 'chapter' | 'highlight';
3353
+ type?: "chapter" | "bookmark" | "quiz";
3354
+ }
3355
+ interface VideoQualityLevel {
3356
+ index: number;
3357
+ bitrate: number;
3358
+ codecs: string;
3359
+ width?: number;
3360
+ height?: number;
2931
3361
  }
2932
3362
  interface PlayerControlsProps {
2933
3363
  isPlaying: boolean;
@@ -2938,6 +3368,9 @@ interface PlayerControlsProps {
2938
3368
  playbackSpeed: number;
2939
3369
  markers?: Marker$1[];
2940
3370
  isFullscreen?: boolean;
3371
+ videoQualityLevels?: VideoQualityLevel[];
3372
+ videoQualityLevel?: number;
3373
+ onVideoQualityChange?: (index: number) => void;
2941
3374
  onPlayPause: () => void;
2942
3375
  onSeek: (timeMs: number) => void;
2943
3376
  onSeekStart?: () => void;
@@ -2948,11 +3381,28 @@ interface PlayerControlsProps {
2948
3381
  onMuteToggle: () => void;
2949
3382
  onSpeedChange: (speed: number) => void;
2950
3383
  onFullscreenToggle?: () => void;
3384
+ /** Called when the user interacts with the code editor area while playing.
3385
+ * The parent should pause playback when this fires. */
3386
+ onEditorInteraction?: () => void;
3387
+ /** Interaction points — purple dots rendered on the seek bar */
3388
+ interactionPoints?: InteractionPoint[];
3389
+ /** Saved learner branches — green diamond markers on the progress bar */
3390
+ branches?: LearnerBranch[];
3391
+ /** Callback when a branch marker is clicked */
3392
+ onBranchClick?: (branch: LearnerBranch) => void;
3393
+ /** Number of saved branches (for badge) */
3394
+ branchCount?: number;
3395
+ /** Whether the branch panel is currently open */
3396
+ isBranchPanelOpen?: boolean;
3397
+ /** Toggle the branch panel */
3398
+ onBranchPanelToggle?: () => void;
3399
+ /** Whether the learner is in interactive/editing mode */
3400
+ isInteractive?: boolean;
2951
3401
  showMarkers?: boolean;
2952
3402
  compact?: boolean;
2953
- containerRef?: react__default.RefObject<HTMLElement>;
3403
+ containerRef?: React__default__default.RefObject<HTMLElement>;
2954
3404
  }
2955
- declare function PlayerControls({ isPlaying, currentTime, duration, volume, muted, playbackSpeed, markers, isFullscreen, onPlayPause, onSeek, onSeekStart, onSeekEnd, onSkip, onRestart, onVolumeChange, onMuteToggle, onSpeedChange, onFullscreenToggle, showMarkers, compact, }: PlayerControlsProps): react_jsx_runtime.JSX.Element;
3405
+ declare function PlayerControls({ isPlaying, currentTime, duration, volume, muted, playbackSpeed, markers, isFullscreen, videoQualityLevels, videoQualityLevel, onVideoQualityChange, onPlayPause, onSeek, onSeekStart, onSeekEnd, onSkip, onRestart, onVolumeChange, onMuteToggle, onSpeedChange, onFullscreenToggle, onEditorInteraction, interactionPoints, branches, onBranchClick, branchCount, isBranchPanelOpen, onBranchPanelToggle, isInteractive, showMarkers, compact, }: PlayerControlsProps): react_jsx_runtime.JSX.Element;
2956
3406
 
2957
3407
  /**
2958
3408
  * Timeline Component
@@ -2969,7 +3419,7 @@ interface Marker {
2969
3419
  id: string;
2970
3420
  time: number;
2971
3421
  label: string;
2972
- type: 'chapter' | 'highlight' | 'quiz' | 'note' | 'bookmark' | 'error';
3422
+ type: 'chapter' | 'bookmark' | 'quiz' | 'note' | 'error';
2973
3423
  description?: string;
2974
3424
  }
2975
3425
  /**
@@ -3024,6 +3474,128 @@ interface TimelineProps {
3024
3474
  */
3025
3475
  declare function Timeline({ currentTime, duration, markers, chunks, buffered, onSeek, onMarkerClick, theme, showChapterLabels, showBufferStatus, showChunkIndicators, compact, }: TimelineProps): JSX.Element;
3026
3476
 
3477
+ /**
3478
+ * MCQOverlay — full-area overlay that renders a multiple-choice question
3479
+ * on top of the player content.
3480
+ *
3481
+ * Phases: Question → Loading → Feedback (with optional retry).
3482
+ * Handles timeout countdown, focus trapping, and keyboard navigation.
3483
+ *
3484
+ * @packageDocumentation
3485
+ */
3486
+
3487
+ interface MCQOverlayProps {
3488
+ /** The interaction point to display */
3489
+ point: InteractionPoint;
3490
+ /** Called when the learner submits an answer. Returns result from host app. */
3491
+ onSubmit: (selectedOptionIds: string[]) => Promise<MCQResult>;
3492
+ /** Called when the learner is done (correct answer, or gave up) */
3493
+ onDismiss: () => void;
3494
+ /** Optional extra class name for the outer container */
3495
+ className?: string;
3496
+ }
3497
+ declare const MCQOverlay: React__default__default.FC<MCQOverlayProps>;
3498
+
3499
+ /**
3500
+ * BranchTransition — end-of-lesson overlay with auto-advance countdown.
3501
+ *
3502
+ * Shows "Next: [title]" with countdown + skip/cancel, or a "Course Complete"
3503
+ * message when there's no next lesson.
3504
+ *
3505
+ * @packageDocumentation
3506
+ */
3507
+
3508
+ interface BranchTransitionProps {
3509
+ /** Title of the next lesson */
3510
+ nextLessonTitle: string;
3511
+ /** Countdown seconds remaining */
3512
+ countdown: number;
3513
+ /** Skip countdown and navigate immediately */
3514
+ onSkip: () => void;
3515
+ /** Stop auto-advance (stay on current lesson) */
3516
+ onCancel: () => void;
3517
+ /** If true, show "Course Complete" instead of next lesson */
3518
+ isComplete?: boolean;
3519
+ }
3520
+ declare const BranchTransition: React__default__default.FC<BranchTransitionProps>;
3521
+
3522
+ /**
3523
+ * SaveBranchButton — floating pill button shown in the top-right of the IDE
3524
+ * panel when the learner is in interactive mode and has made code changes.
3525
+ *
3526
+ * @packageDocumentation
3527
+ */
3528
+
3529
+ interface SaveBranchButtonProps {
3530
+ /** Whether the button should be visible */
3531
+ visible: boolean;
3532
+ /** Called when the learner clicks "Save Branch" */
3533
+ onSave: () => void;
3534
+ /** Whether the max branch limit has been reached */
3535
+ limitReached?: boolean;
3536
+ }
3537
+ declare const SaveBranchButton: React__default__default.FC<SaveBranchButtonProps>;
3538
+
3539
+ /**
3540
+ * BranchSavedToast — brief confirmation toast shown after saving a branch.
3541
+ *
3542
+ * Slides up, holds, then fades out automatically.
3543
+ *
3544
+ * @packageDocumentation
3545
+ */
3546
+
3547
+ interface BranchSavedToastProps {
3548
+ /** Whether to show the toast */
3549
+ visible: boolean;
3550
+ /** Called when the toast fully disappears */
3551
+ onDismiss: () => void;
3552
+ /** Optional message override (defaults to "Branch saved") */
3553
+ message?: string;
3554
+ /** Show as a duplicate/error toast instead of success */
3555
+ variant?: "success" | "duplicate";
3556
+ }
3557
+ declare const BranchSavedToast: React__default__default.FC<BranchSavedToastProps>;
3558
+
3559
+ /**
3560
+ * UnsavedBranchPrompt — tooltip-style popup that appears near the play button
3561
+ * when the learner exits interactive mode with unsaved code changes.
3562
+ *
3563
+ * Offers "Save & Resume" or "Discard & Resume". Auto-dismisses after 5 seconds.
3564
+ *
3565
+ * @packageDocumentation
3566
+ */
3567
+
3568
+ interface UnsavedBranchPromptProps {
3569
+ /** Whether to show the prompt */
3570
+ visible: boolean;
3571
+ /** Called when the user chooses "Save & Resume" */
3572
+ onSaveAndResume: () => void;
3573
+ /** Called when the user chooses "Discard & Resume" or prompt auto-dismisses */
3574
+ onDiscardAndResume: () => void;
3575
+ }
3576
+ declare const UnsavedBranchPrompt: React__default__default.FC<UnsavedBranchPromptProps>;
3577
+
3578
+ interface BranchPanelProps {
3579
+ /** Whether the panel is visible */
3580
+ visible: boolean;
3581
+ /** All branches for the current lesson */
3582
+ branches: LearnerBranch[];
3583
+ /** Close the panel */
3584
+ onClose: () => void;
3585
+ /** Restore a branch (seek + interactive + inject) */
3586
+ onRestore: (branch: LearnerBranch) => void;
3587
+ /** Delete a branch by id */
3588
+ onDelete: (branchId: string) => void;
3589
+ /** Rename a branch */
3590
+ onRename: (branchId: string, newLabel: string) => void;
3591
+ /** Maximum branches per lesson (for limit warning) */
3592
+ maxBranches?: number;
3593
+ /** Ref to the anchor element for fixed positioning */
3594
+ anchorRef?: React__default__default.RefObject<HTMLDivElement>;
3595
+ }
3596
+ declare function BranchPanelInner({ visible, branches, onClose, onRestore, onDelete, onRename, maxBranches, anchorRef, }: BranchPanelProps): react_jsx_runtime.JSX.Element | null;
3597
+ declare const BranchPanel: React__default__default.MemoExoticComponent<typeof BranchPanelInner>;
3598
+
3027
3599
  /**
3028
3600
  * AudioOverlay Component
3029
3601
  *
@@ -3088,89 +3660,54 @@ interface AudioOverlayProps {
3088
3660
  declare function AudioOverlay({ src, currentTime: _currentTime, isPlaying: _isPlaying, playbackRate: _playbackRate, position, customPosition, visible, draggable, showControls, opacity, onPositionChange, onAudioRef, muted: _muted, volume: _volume, syncTolerance: _syncTolerance, }: AudioOverlayProps): JSX.Element | null;
3089
3661
 
3090
3662
  /**
3091
- * WebcamOverlay Component
3663
+ * WebcamOverlay Component v3
3092
3664
  *
3093
- * Circular webcam video overlay with draggable positioning,
3094
- * resize handles, and video sync. Matches frontend styling.
3665
+ * Clean webcam video overlay with:
3666
+ * - Draggable circular bubble
3667
+ * - Maximize on hover click
3668
+ * - Smooth animations
3095
3669
  *
3096
3670
  * @packageDocumentation
3097
3671
  */
3098
- /**
3099
- * Overlay position presets
3100
- */
3101
- type OverlayPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'custom';
3102
- /**
3103
- * Overlay size presets
3104
- */
3105
- type OverlaySize = 'small' | 'medium' | 'large' | 'custom';
3106
- /**
3107
- * Custom position
3108
- */
3672
+ type OverlayPosition = "top-right" | "top-left" | "bottom-right" | "bottom-left" | "custom";
3673
+ type OverlaySize = "small" | "medium" | "large" | "custom";
3109
3674
  interface CustomPosition {
3110
3675
  x: number;
3111
3676
  y: number;
3112
3677
  }
3113
- /**
3114
- * Custom size
3115
- */
3116
3678
  interface CustomSize {
3117
3679
  width: number;
3118
3680
  height: number;
3119
3681
  }
3120
- /**
3121
- * Props for WebcamOverlay component
3122
- */
3123
3682
  interface WebcamOverlayProps {
3124
- /** Video source URL */
3125
3683
  src: string | null;
3126
- /** Current time to sync to (ms) */
3127
3684
  currentTime: number;
3128
- /** Whether video should be playing */
3129
3685
  isPlaying: boolean;
3130
- /** Playback rate */
3131
3686
  playbackRate?: number;
3132
- /** Overlay position preset */
3133
3687
  position?: OverlayPosition;
3134
- /** Custom position (when position='custom') */
3135
3688
  customPosition?: CustomPosition;
3136
- /** Size preset */
3137
3689
  size?: OverlaySize;
3138
- /** Custom size (when size='custom') */
3139
3690
  customSize?: CustomSize;
3140
- /** Whether to show the overlay */
3141
3691
  visible?: boolean;
3142
- /** Enable dragging */
3143
3692
  draggable?: boolean;
3144
- /** Enable resize */
3145
3693
  resizable?: boolean;
3146
- /** Show controls on hover */
3147
3694
  showControls?: boolean;
3148
- /** Border radius (default is circular) */
3149
- borderRadius?: number | 'circle';
3150
- /** Opacity (0-1) */
3695
+ borderRadius?: number | "circle";
3151
3696
  opacity?: number;
3152
- /** On position change callback */
3153
3697
  onPositionChange?: (position: CustomPosition) => void;
3154
- /** On size change callback */
3155
3698
  onSizeChange?: (size: CustomSize) => void;
3156
- /** On visibility toggle callback */
3157
3699
  onVisibilityToggle?: () => void;
3158
- /** Callback to pass video element reference for sync */
3159
3700
  onVideoRef?: (video: HTMLVideoElement | null) => void;
3160
- /** Whether audio is muted */
3161
3701
  muted?: boolean;
3162
- /** Volume level (0-1) */
3163
3702
  volume?: number;
3164
- /** Sync tolerance in ms */
3165
3703
  syncTolerance?: number;
3166
- }
3167
- /**
3168
- * Webcam overlay for instructor video
3169
- *
3170
- * NOTE: Video playback is now controlled entirely by parent (usePlayback).
3171
- * Props like currentTime, isPlaying, playbackRate, muted, volume, syncTolerance
3172
- * are kept for backward compatibility but are no longer used.
3173
- */
3174
- declare function WebcamOverlay({ src, currentTime: _currentTime, isPlaying: _isPlaying, playbackRate: _playbackRate, position, customPosition, size, customSize, visible, draggable, resizable, showControls, borderRadius, opacity, onPositionChange, onSizeChange: _onSizeChange, onVisibilityToggle: _onVisibilityToggle, onVideoRef, muted: _muted, volume: _volume, syncTolerance: _syncTolerance, }: WebcamOverlayProps): JSX.Element | null;
3704
+ /** Whether webcam is in maximized (full overlay) mode - from playback events */
3705
+ maximized?: boolean;
3706
+ /** Callback when user clicks minimize button */
3707
+ onMinimize?: () => void;
3708
+ /** Callback when user clicks maximize button */
3709
+ onMaximize?: () => void;
3710
+ }
3711
+ declare function WebcamOverlay({ src, currentTime: _currentTime, isPlaying: _isPlaying, playbackRate: _playbackRate, position, customPosition, size, customSize, visible, draggable, resizable: _resizable, showControls: _showControls, borderRadius, opacity, onPositionChange, onSizeChange: _onSizeChange, onVisibilityToggle: _onVisibilityToggle, onVideoRef, muted: _muted, volume: _volume, syncTolerance: _syncTolerance, maximized, onMinimize, onMaximize, }: WebcamOverlayProps): JSX.Element | null;
3175
3712
 
3176
- export { AudioOverlay, type AudioOverlayProps, type AudioQuality, type AudioSyncControls, type AudioSyncState, type BackendAdapter, type BufferHealth, type CTEvent, type CTManifest, type CTMarker, type CTReadOptions, type CTRecording, CoursePlayer, type CoursePlayerProps, type CoursePlayerRef, type CursorPosition, type CustomPosition$1 as CustomPosition, DocumentPanel, type DocumentPanelProps, FileExplorer, type FileExplorerProps, type FileSystemSnapshot, type HLSPlayerInstance, HLS_EVENTS, IDEPanel, type IDEPanelProps, type InternalEvent, type InternalRecording, type OverlayPosition$1 as OverlayPosition, type PanelMode, type PlaybackControls$1 as PlaybackControls, type PlaybackEngineConfig, type PlaybackHookState, type PlaybackState, PlayerControls, type PlayerControlsProps$1 as PlayerControlsProps, type PlayerProps, type PlayerRef, type RecordingMarker, type RemoteBufferHealth, type RemoteStreamingMetrics, type RemoteStreamingPlaybackControls, type RemoteStreamingPlaybackState, type SpeedSelectorProps, type StreamingAudioConfig, type StreamingConfig, StreamingCoursePlayer, type StreamingCoursePlayerProps, type StreamingCoursePlayerRef, type StreamingEventsConfig, type StreamingManifest, type StreamingMetrics, type StreamingPlaybackControls, type StreamingPlaybackState, type StreamingRecordingInfo, Terminal, type TerminalLineData, type TerminalProps, Timeline, type TimelineProps$1 as TimelineProps, ToolSidebar, type ToolSidebarProps, type ToolType, type UseAudioSyncOptions, type UsePlaybackEngineResult, type UsePlaybackOptions, type UseRemoteStreamingPlaybackOptions, type UseVideoSyncOptions, type ValidationResult$1 as ValidationResult, type VideoSyncControls, type VideoSyncState, type VolumeControlProps, WebcamOverlay, type WebcamOverlayProps, WhiteboardPanel, type WhiteboardPanelProps, type WriteCTOptions, canPlayHls, createEmptyRecording, createHlsPlayer, destroyHlsPlayer, hasNativeHlsSupport, isHlsSupported, readCTFile, readCTManifest, useAudioElement, useAudioSync, useInteractiveMode, usePlayback, usePlayback as usePlaybackEngine, useRemoteStreamingPlayback, useStreamingPlayback, useVideoElement, useVideoSync, validateCTFile, writeCTFile };
3713
+ export { AudioOverlay, type AudioOverlayProps, type AudioQuality, type AudioSyncControls, type AudioSyncState, type BackendAdapter, BranchPanel, type BranchPanelProps, BranchSavedToast, type BranchSavedToastProps, type BranchSnapshot, BranchTransition, type BranchTransitionProps, type BufferHealth, type CTEvent, type CTManifest, type CTMarker, type CTReadOptions, type CTRecording, CoursePlayer, type CoursePlayerProps, type CoursePlayerRef, type CursorPosition, type CustomPosition$1 as CustomPosition, DocumentPanel, type DocumentPanelProps, FileExplorer, type FileExplorerProps, type FileSystemSnapshot, type HLSPlayerInstance, HLS_EVENTS, IDEPanel, type IDEPanelProps, type InteractionPoint, type InternalEvent, type InternalRecording, type LearnerBranch, MCQOverlay, type MCQOverlayProps, type MCQResult, type MCQSubmission, type OverlayPosition$1 as OverlayPosition, type PanelMode, type PlaybackControls$1 as PlaybackControls, type PlaybackEngineConfig, type PlaybackHookState, type PlaybackState, PlayerControls, type PlayerControlsProps$1 as PlayerControlsProps, type PlayerProps, type PlayerRef, type RecordingMarker, type RemoteBufferHealth, type RemoteStreamingMetrics, type RemoteStreamingPlaybackControls, type RemoteStreamingPlaybackState, SaveBranchButton, type SaveBranchButtonProps, type SpeedSelectorProps, type StreamingAudioConfig, type StreamingConfig, StreamingCoursePlayer, type StreamingCoursePlayerProps, type StreamingCoursePlayerRef, type StreamingEventsConfig, type StreamingManifest, type StreamingMetrics, type StreamingPlaybackControls, type StreamingPlaybackState, type StreamingRecordingInfo, Terminal, type TerminalLineData, type TerminalProps, Timeline, type TimelineProps$1 as TimelineProps, ToolSidebar, type ToolSidebarProps, type ToolType, UnsavedBranchPrompt, type UnsavedBranchPromptProps, type UseAudioSyncOptions, type UseBranchesOptions, type UseBranchesResult, type UseBranchingOptions, type UseBranchingResult, type UseInteractionPointsOptions, type UseInteractionPointsResult, type UsePlaybackEngineResult, type UsePlaybackOptions, type UseRemoteStreamingPlaybackOptions, type UseVideoSyncOptions, type ValidationResult$1 as ValidationResult, type VideoSyncControls, type VideoSyncState, type VolumeControlProps, WebcamOverlay, type WebcamOverlayProps, WhiteboardPanel, type WhiteboardPanelProps, type WriteCTOptions, canPlayHls, createEmptyRecording, createHlsPlayer, deleteBranch as deleteBranchFromStorage, destroyHlsPlayer, formatBranchTime, hasNativeHlsSupport, isHlsSupported, loadBranches, readCTFile, readCTManifest, saveBranch as saveBranchToStorage, updateBranchLabel, useAudioElement, useAudioSync, useBranches, useBranching, useInteractionPoints, useInteractiveMode, usePlayback, usePlayback as usePlaybackEngine, useRemoteStreamingPlayback, useStreamingPlayback, useVideoElement, useVideoSync, validateCTFile, writeCTFile };