@pie-players/pie-assessment-player 0.3.74 → 1.0.0-beta.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.
@@ -0,0 +1,521 @@
1
+ {
2
+ "schemaVersion": "2.1.0",
3
+ "modules": [
4
+ {
5
+ "kind": "javascript-module",
6
+ "path": "dist/pie-assessment-player.js",
7
+ "declarations": [
8
+ {
9
+ "kind": "class",
10
+ "description": "Reference multi-section assessment player. A host gives it an `assessment` definition and `assessment-id`, optionally with `attempt-id`, `session` and `hooks`; it creates an `AssessmentController` for the attempt, mounts the current section in a split-pane or vertical section player, and renders the section position with Back and Next controls.",
11
+ "name": "AssessmentPlayerDefaultElement",
12
+ "tagName": "pie-assessment-player-default",
13
+ "customElement": true,
14
+ "superclass": {
15
+ "name": "HTMLElement"
16
+ },
17
+ "members": [
18
+ {
19
+ "kind": "field",
20
+ "name": "assessmentId",
21
+ "type": {
22
+ "text": "string"
23
+ },
24
+ "attribute": "assessment-id"
25
+ },
26
+ {
27
+ "kind": "field",
28
+ "name": "attemptId",
29
+ "type": {
30
+ "text": "string"
31
+ },
32
+ "attribute": "attempt-id"
33
+ },
34
+ {
35
+ "kind": "field",
36
+ "name": "assessment",
37
+ "type": {
38
+ "text": "AssessmentDefinition | null"
39
+ }
40
+ },
41
+ {
42
+ "kind": "field",
43
+ "name": "hooks",
44
+ "type": {
45
+ "text": "AssessmentPlayerHooks | null"
46
+ }
47
+ },
48
+ {
49
+ "kind": "field",
50
+ "name": "session",
51
+ "type": {
52
+ "text": "AssessmentSession | null"
53
+ },
54
+ "description": "Before a controller is ready, the assigned session; after, a copy of the controller's."
55
+ },
56
+ {
57
+ "kind": "field",
58
+ "name": "locale",
59
+ "type": {
60
+ "text": "string"
61
+ },
62
+ "attribute": "locale"
63
+ },
64
+ {
65
+ "kind": "field",
66
+ "name": "env",
67
+ "type": {
68
+ "text": "Env | null"
69
+ }
70
+ },
71
+ {
72
+ "kind": "field",
73
+ "name": "coordinator",
74
+ "type": {
75
+ "text": "unknown"
76
+ }
77
+ },
78
+ {
79
+ "kind": "field",
80
+ "name": "showNavigation",
81
+ "type": {
82
+ "text": "boolean | string | null | undefined"
83
+ },
84
+ "attribute": "show-navigation"
85
+ },
86
+ {
87
+ "kind": "field",
88
+ "name": "sectionPlayerLayout",
89
+ "type": {
90
+ "text": "\"splitpane\" | \"vertical\""
91
+ },
92
+ "attribute": "section-player-layout"
93
+ },
94
+ {
95
+ "kind": "field",
96
+ "name": "playerType",
97
+ "type": {
98
+ "text": "\"iife\" | \"esm\" | \"preloaded\""
99
+ },
100
+ "attribute": "player-type"
101
+ },
102
+ {
103
+ "kind": "field",
104
+ "name": "debug",
105
+ "type": {
106
+ "text": "boolean | string | null | undefined"
107
+ },
108
+ "attribute": "debug"
109
+ },
110
+ {
111
+ "kind": "field",
112
+ "name": "sectionPlayerRuntime",
113
+ "type": {
114
+ "text": "AssessmentPlayerRuntimeConfig[\"sectionPlayerRuntime\"]"
115
+ }
116
+ },
117
+ {
118
+ "kind": "method",
119
+ "name": "getSnapshot",
120
+ "return": {
121
+ "type": {
122
+ "text": "AssessmentPlayerSnapshot"
123
+ }
124
+ }
125
+ },
126
+ {
127
+ "kind": "method",
128
+ "name": "selectNavigation"
129
+ },
130
+ {
131
+ "kind": "method",
132
+ "name": "selectReadiness"
133
+ },
134
+ {
135
+ "kind": "method",
136
+ "name": "selectProgress",
137
+ "return": {
138
+ "type": {
139
+ "text": "AssessmentPlayerProgressSnapshot"
140
+ }
141
+ }
142
+ },
143
+ {
144
+ "kind": "method",
145
+ "name": "navigateTo",
146
+ "parameters": [
147
+ {
148
+ "name": "indexOrIdentifier",
149
+ "type": {
150
+ "text": "number | string"
151
+ }
152
+ }
153
+ ],
154
+ "return": {
155
+ "type": {
156
+ "text": "boolean"
157
+ }
158
+ }
159
+ },
160
+ {
161
+ "kind": "method",
162
+ "name": "navigateNext",
163
+ "return": {
164
+ "type": {
165
+ "text": "boolean"
166
+ }
167
+ }
168
+ },
169
+ {
170
+ "kind": "method",
171
+ "name": "navigatePrevious",
172
+ "return": {
173
+ "type": {
174
+ "text": "boolean"
175
+ }
176
+ }
177
+ },
178
+ {
179
+ "kind": "method",
180
+ "name": "getAssessmentController",
181
+ "return": {
182
+ "type": {
183
+ "text": "AssessmentControllerHandle | null"
184
+ }
185
+ }
186
+ },
187
+ {
188
+ "kind": "method",
189
+ "name": "waitForAssessmentController",
190
+ "parameters": [
191
+ {
192
+ "name": "timeoutMs",
193
+ "type": {
194
+ "text": "number"
195
+ },
196
+ "optional": true,
197
+ "default": "5000"
198
+ }
199
+ ]
200
+ }
201
+ ],
202
+ "attributes": [
203
+ {
204
+ "name": "assessment-id",
205
+ "type": {
206
+ "text": "string"
207
+ },
208
+ "fieldName": "assessmentId"
209
+ },
210
+ {
211
+ "name": "attempt-id",
212
+ "type": {
213
+ "text": "string"
214
+ },
215
+ "fieldName": "attemptId"
216
+ },
217
+ {
218
+ "name": "show-navigation",
219
+ "type": {
220
+ "text": "boolean | string | null | undefined"
221
+ },
222
+ "fieldName": "showNavigation"
223
+ },
224
+ {
225
+ "name": "section-player-layout",
226
+ "type": {
227
+ "text": "\"splitpane\" | \"vertical\""
228
+ },
229
+ "fieldName": "sectionPlayerLayout"
230
+ },
231
+ {
232
+ "name": "player-type",
233
+ "type": {
234
+ "text": "\"iife\" | \"esm\" | \"preloaded\""
235
+ },
236
+ "fieldName": "playerType"
237
+ },
238
+ {
239
+ "name": "debug",
240
+ "type": {
241
+ "text": "boolean | string | null | undefined"
242
+ },
243
+ "fieldName": "debug"
244
+ },
245
+ {
246
+ "name": "locale",
247
+ "type": {
248
+ "text": "string"
249
+ },
250
+ "fieldName": "locale"
251
+ }
252
+ ],
253
+ "events": [
254
+ {
255
+ "name": "assessment-controller-ready",
256
+ "type": {
257
+ "text": "CustomEvent<{ controller: AssessmentControllerHandle }>"
258
+ },
259
+ "description": "When the controller has initialized and hydrated the attempt."
260
+ },
261
+ {
262
+ "name": "assessment-navigation-requested",
263
+ "type": {
264
+ "text": "CustomEvent<AssessmentNavigationRequestedDetail>"
265
+ },
266
+ "description": "Before Back, Next or a navigate method moves to another section, cancelable to stay."
267
+ },
268
+ {
269
+ "name": "assessment-route-changed",
270
+ "type": {
271
+ "text": "CustomEvent<AssessmentRouteChangedDetail>"
272
+ },
273
+ "description": "When the current section changes."
274
+ },
275
+ {
276
+ "name": "assessment-session-applied",
277
+ "type": {
278
+ "text": "CustomEvent<{ type: \"assessment-session-applied\"; timestamp: number }>"
279
+ },
280
+ "description": "When hydration applies a persisted or assigned session."
281
+ },
282
+ {
283
+ "name": "assessment-session-changed",
284
+ "type": {
285
+ "text": "CustomEvent<{ type: \"assessment-session-changed\"; timestamp: number }>"
286
+ },
287
+ "description": "When navigation or a section snapshot changes the assessment session."
288
+ },
289
+ {
290
+ "name": "assessment-progress-changed",
291
+ "type": {
292
+ "text": "CustomEvent<AssessmentProgressChangedDetail>"
293
+ },
294
+ "description": "When the visited-section count changes."
295
+ },
296
+ {
297
+ "name": "assessment-submission-state-changed",
298
+ "type": {
299
+ "text": "CustomEvent<AssessmentSubmissionStateChangedDetail>"
300
+ },
301
+ "description": "When `submit()` records the submission state."
302
+ },
303
+ {
304
+ "name": "assessment-error",
305
+ "type": {
306
+ "text": "CustomEvent<{ error: unknown }>"
307
+ },
308
+ "description": "When the attempt fails to load, a ready hook rejects, or a section fails to load or restore."
309
+ }
310
+ ],
311
+ "x-pie": {
312
+ "shadow": "none"
313
+ }
314
+ },
315
+ {
316
+ "kind": "class",
317
+ "description": "Layout scaffold for a host-composed assessment: host navigation above a body that fills the remaining height. The scaffold renders in an open shadow root and the host's children stay in its light DOM, where document styles reach them, projecting into the slots.",
318
+ "name": "AssessmentPlayerShellElement",
319
+ "tagName": "pie-assessment-player-shell",
320
+ "customElement": true,
321
+ "superclass": {
322
+ "name": "HTMLElement"
323
+ },
324
+ "members": [
325
+ {
326
+ "kind": "field",
327
+ "name": "showNavigation",
328
+ "type": {
329
+ "text": "boolean | string | null | undefined"
330
+ },
331
+ "default": "true",
332
+ "attribute": "show-navigation"
333
+ }
334
+ ],
335
+ "attributes": [
336
+ {
337
+ "name": "show-navigation",
338
+ "type": {
339
+ "text": "boolean | string | null | undefined"
340
+ },
341
+ "fieldName": "showNavigation"
342
+ }
343
+ ],
344
+ "slots": [
345
+ {
346
+ "name": "",
347
+ "description": "The body: the host's section player and any other assessment UI."
348
+ },
349
+ {
350
+ "name": "navigation",
351
+ "description": "Assessment navigation above the body; not rendered while `show-navigation` is false."
352
+ }
353
+ ],
354
+ "x-pie": {
355
+ "shadow": "open"
356
+ }
357
+ }
358
+ ],
359
+ "exports": [
360
+ {
361
+ "kind": "custom-element-definition",
362
+ "name": "pie-assessment-player-default",
363
+ "declaration": {
364
+ "name": "AssessmentPlayerDefaultElement"
365
+ }
366
+ },
367
+ {
368
+ "kind": "custom-element-definition",
369
+ "name": "pie-assessment-player-shell",
370
+ "declaration": {
371
+ "name": "AssessmentPlayerShellElement"
372
+ }
373
+ },
374
+ {
375
+ "kind": "custom-element-definition",
376
+ "name": "pie-assessment-toolkit",
377
+ "declaration": {
378
+ "name": "PieAssessmentToolkit",
379
+ "module": "dist/components/pie-assessment-toolkit-element.js",
380
+ "package": "@pie-players/pie-assessment-toolkit"
381
+ }
382
+ },
383
+ {
384
+ "kind": "custom-element-definition",
385
+ "name": "pie-item-player",
386
+ "declaration": {
387
+ "name": "PieItemPlayer",
388
+ "module": "dist/pie-item-player.js",
389
+ "package": "@pie-players/pie-item-player"
390
+ }
391
+ },
392
+ {
393
+ "kind": "custom-element-definition",
394
+ "name": "pie-item-scope",
395
+ "declaration": {
396
+ "name": "ItemScope",
397
+ "module": "dist/components/item-scope-element.js",
398
+ "package": "@pie-players/pie-assessment-toolkit"
399
+ }
400
+ },
401
+ {
402
+ "kind": "custom-element-definition",
403
+ "name": "pie-item-toolbar",
404
+ "declaration": {
405
+ "name": "ItemToolBar",
406
+ "module": "dist/components/item-toolbar-element.js",
407
+ "package": "@pie-players/pie-assessment-toolkit"
408
+ }
409
+ },
410
+ {
411
+ "kind": "custom-element-definition",
412
+ "name": "pie-passage-shell",
413
+ "declaration": {
414
+ "name": "PassageShellElement",
415
+ "module": "dist/pie-section-player.js",
416
+ "package": "@pie-players/pie-section-player"
417
+ }
418
+ },
419
+ {
420
+ "kind": "custom-element-definition",
421
+ "name": "pie-section-player-base",
422
+ "declaration": {
423
+ "name": "PieSectionPlayerBaseElement",
424
+ "module": "dist/pie-section-player.js",
425
+ "package": "@pie-players/pie-section-player"
426
+ }
427
+ },
428
+ {
429
+ "kind": "custom-element-definition",
430
+ "name": "pie-section-player-item-card",
431
+ "declaration": {
432
+ "name": "SectionItemCard",
433
+ "module": "dist/pie-section-player.js",
434
+ "package": "@pie-players/pie-section-player"
435
+ }
436
+ },
437
+ {
438
+ "kind": "custom-element-definition",
439
+ "name": "pie-section-player-items-pane",
440
+ "declaration": {
441
+ "name": "SectionItemsPane",
442
+ "module": "dist/pie-section-player.js",
443
+ "package": "@pie-players/pie-section-player"
444
+ }
445
+ },
446
+ {
447
+ "kind": "custom-element-definition",
448
+ "name": "pie-section-player-kernel-host",
449
+ "declaration": {
450
+ "name": "PieSectionPlayerKernelHostElement",
451
+ "module": "dist/pie-section-player.js",
452
+ "package": "@pie-players/pie-section-player"
453
+ }
454
+ },
455
+ {
456
+ "kind": "custom-element-definition",
457
+ "name": "pie-section-player-passage-card",
458
+ "declaration": {
459
+ "name": "SectionPassageCard",
460
+ "module": "dist/pie-section-player.js",
461
+ "package": "@pie-players/pie-section-player"
462
+ }
463
+ },
464
+ {
465
+ "kind": "custom-element-definition",
466
+ "name": "pie-section-player-passages-pane",
467
+ "declaration": {
468
+ "name": "SectionPassagesPane",
469
+ "module": "dist/pie-section-player.js",
470
+ "package": "@pie-players/pie-section-player"
471
+ }
472
+ },
473
+ {
474
+ "kind": "custom-element-definition",
475
+ "name": "pie-section-player-shell",
476
+ "declaration": {
477
+ "name": "SectionPlayerShell",
478
+ "module": "dist/pie-section-player.js",
479
+ "package": "@pie-players/pie-section-player"
480
+ }
481
+ },
482
+ {
483
+ "kind": "custom-element-definition",
484
+ "name": "pie-section-player-splitpane",
485
+ "declaration": {
486
+ "name": "PieSectionPlayerSplitPaneElement",
487
+ "module": "dist/pie-section-player.js",
488
+ "package": "@pie-players/pie-section-player"
489
+ }
490
+ },
491
+ {
492
+ "kind": "custom-element-definition",
493
+ "name": "pie-section-player-tabbed",
494
+ "declaration": {
495
+ "name": "PieSectionPlayerTabbedElement",
496
+ "module": "dist/pie-section-player.js",
497
+ "package": "@pie-players/pie-section-player"
498
+ }
499
+ },
500
+ {
501
+ "kind": "custom-element-definition",
502
+ "name": "pie-section-player-vertical",
503
+ "declaration": {
504
+ "name": "PieSectionPlayerVerticalElement",
505
+ "module": "dist/pie-section-player.js",
506
+ "package": "@pie-players/pie-section-player"
507
+ }
508
+ },
509
+ {
510
+ "kind": "custom-element-definition",
511
+ "name": "pie-section-toolbar",
512
+ "declaration": {
513
+ "name": "SectionToolBar",
514
+ "module": "dist/components/section-toolbar-element.js",
515
+ "package": "@pie-players/pie-assessment-toolkit"
516
+ }
517
+ }
518
+ ]
519
+ }
520
+ ]
521
+ }
@@ -1,7 +1,32 @@
1
1
  import { Env } from '@pie-players/pie-players-shared/types';
2
2
  import { AssessmentControllerHandle } from '../controller/AssessmentController.js';
3
3
  import { AssessmentPlayerProgressSnapshot, AssessmentPlayerRuntimeHostContract, AssessmentPlayerSnapshot } from '../contracts/runtime-host-contract.js';
4
- import { AssessmentDefinition, AssessmentPlayerHooks, AssessmentPlayerRuntimeConfig } from '../types.js';
4
+ import { AssessmentDefinition, AssessmentPlayerHooks, AssessmentPlayerRuntimeConfig, AssessmentSession } from '../types.js';
5
+ /**
6
+ * Reference multi-section assessment player. A host gives it an `assessment`
7
+ * definition and `assessment-id`, optionally with `attempt-id`, `session` and
8
+ * `hooks`; it creates an `AssessmentController` for the attempt, mounts the
9
+ * current section in a split-pane or vertical section player, and renders the
10
+ * section position with Back and Next controls.
11
+ *
12
+ * @fires {CustomEvent<{ controller: AssessmentControllerHandle }>} assessment-controller-ready -
13
+ * When the controller has initialized and hydrated the attempt.
14
+ * @fires {CustomEvent<AssessmentNavigationRequestedDetail>} assessment-navigation-requested -
15
+ * Before Back, Next or a navigate method moves to another section, cancelable
16
+ * to stay.
17
+ * @fires {CustomEvent<AssessmentRouteChangedDetail>} assessment-route-changed -
18
+ * When the current section changes.
19
+ * @fires {CustomEvent<{ type: "assessment-session-applied"; timestamp: number }>} assessment-session-applied -
20
+ * When hydration applies a persisted or assigned session.
21
+ * @fires {CustomEvent<{ type: "assessment-session-changed"; timestamp: number }>} assessment-session-changed -
22
+ * When navigation or a section snapshot changes the assessment session.
23
+ * @fires {CustomEvent<AssessmentProgressChangedDetail>} assessment-progress-changed -
24
+ * When the visited-section count changes.
25
+ * @fires {CustomEvent<AssessmentSubmissionStateChangedDetail>} assessment-submission-state-changed -
26
+ * When `submit()` records the submission state.
27
+ * @fires {CustomEvent<{ error: unknown }>} assessment-error - When the attempt
28
+ * fails to load, a ready hook rejects, or a section fails to load or restore.
29
+ */
5
30
  export declare class AssessmentPlayerDefaultElement extends HTMLElement implements AssessmentPlayerRuntimeHostContract {
6
31
  static get observedAttributes(): string[];
7
32
  private _assessmentId;
@@ -16,6 +41,13 @@ export declare class AssessmentPlayerDefaultElement extends HTMLElement implemen
16
41
  private _env;
17
42
  private _coordinator;
18
43
  private _hooks;
44
+ /**
45
+ * The assigned session until a controller initializes from it. A later
46
+ * rebuild, such as one an `assessment` change causes, hydrates from the
47
+ * persistence strategy rather than reapplying a value the learner has since
48
+ * moved past.
49
+ */
50
+ private pendingSession;
19
51
  private _showNavigation;
20
52
  private _sectionPlayerLayout;
21
53
  private _playerType;
@@ -63,6 +95,12 @@ export declare class AssessmentPlayerDefaultElement extends HTMLElement implemen
63
95
  set assessment(value: AssessmentDefinition | null);
64
96
  get hooks(): AssessmentPlayerHooks | null;
65
97
  set hooks(value: AssessmentPlayerHooks | null);
98
+ /**
99
+ * Before a controller is ready, the assigned session; after, a copy of the
100
+ * controller's.
101
+ */
102
+ get session(): AssessmentSession | null;
103
+ set session(value: AssessmentSession | null);
66
104
  get locale(): string;
67
105
  set locale(value: string);
68
106
  get env(): Env | null;
@@ -133,6 +171,10 @@ export declare class AssessmentPlayerDefaultElement extends HTMLElement implemen
133
171
  private updateCurrentSectionRuntime;
134
172
  private cleanupTtsForSectionNavigation;
135
173
  private syncCurrentSectionSessionIntoAssessment;
174
+ /**
175
+ * `restoring` is whether the section was handed a saved session, which it
176
+ * applies while it creates its controller, before `engine-ready`.
177
+ */
136
178
  private attachSectionControllerReadyListener;
137
179
  private render;
138
180
  /** Locale and chrome changes must not remount a section or reload its attempt. */
@@ -1,6 +1,17 @@
1
+ /**
2
+ * Layout scaffold for a host-composed assessment: host navigation above a body
3
+ * that fills the remaining height. The scaffold renders in an open shadow root
4
+ * and the host's children stay in its light DOM, where document styles reach
5
+ * them, projecting into the slots.
6
+ *
7
+ * @slot navigation - Assessment navigation above the body; not rendered while
8
+ * `show-navigation` is false.
9
+ * @slot - The body: the host's section player and any other assessment UI.
10
+ */
1
11
  export declare class AssessmentPlayerShellElement extends HTMLElement {
2
12
  static get observedAttributes(): string[];
3
13
  showNavigation: boolean | string | null | undefined;
14
+ constructor();
4
15
  connectedCallback(): void;
5
16
  attributeChangedCallback(name: string, _oldValue: string | null, value: string | null): void;
6
17
  private render;
@@ -3,7 +3,7 @@ import { AssessmentPlayerRuntimeConfig } from '../types.js';
3
3
  export type ResolveAssessmentSectionPlayerRuntimeArgs = {
4
4
  sectionPlayerRuntime?: AssessmentPlayerRuntimeConfig["sectionPlayerRuntime"];
5
5
  playerType: "iife" | "esm" | "preloaded";
6
- attemptId?: string;
6
+ assessmentId?: string;
7
7
  env?: Record<string, unknown> | null;
8
8
  coordinator?: unknown;
9
9
  };
@@ -83,8 +83,8 @@ export interface AssessmentControllerHandle {
83
83
  * persistence strategy.
84
84
  *
85
85
  * Hosts call this on whatever cadence they choose (debounced on
86
- * change, on visibility change, on submit). `submit()` always
87
- * `persist()`s as its final step.
86
+ * change, on visibility change, on submit). Saves run in call order.
87
+ * A failed save goes to `onError` and the returned promise resolves.
88
88
  */
89
89
  persist(): Promise<void>;
90
90
  /**
@@ -123,9 +123,9 @@ export interface AssessmentControllerHandle {
123
123
  */
124
124
  navigatePrevious(): boolean;
125
125
  /**
126
- * Mark the assessment as submitted, emit
127
- * `assessment-submission-state-changed`, and `persist()` the final
128
- * snapshot.
126
+ * Save the final snapshot, then mark the assessment as submitted and
127
+ * emit `assessment-submission-state-changed`. A failed save goes to
128
+ * `onError` and rejects, and the assessment stays unsubmitted.
129
129
  */
130
130
  submit(): Promise<void>;
131
131
  /**
@@ -174,20 +174,34 @@ export declare class AssessmentController implements AssessmentControllerHandle
174
174
  private session;
175
175
  private readiness;
176
176
  private submitted;
177
+ private saveQueue;
177
178
  private persistenceStrategy?;
178
179
  private initializationPromise?;
179
180
  private disposalPromise?;
180
181
  private disposed;
181
182
  private readonly storageContext;
183
+ /** Taken by the first hydrate; a later `hydrate()` loads from the strategy. */
184
+ private initialSession;
182
185
  constructor(args: {
183
186
  assessmentId: string;
184
187
  attemptId?: string;
185
188
  assessment: AssessmentDefinition | null;
186
189
  hooks?: AssessmentPlayerHooks;
190
+ /**
191
+ * A host-supplied session, applied by the first hydrate in place of the
192
+ * persistence strategy's `loadSession` result. The controller keeps a
193
+ * copy, so later updates never write into the host's object.
194
+ */
195
+ initialSession?: AssessmentSession | null;
187
196
  });
188
197
  private emit;
189
198
  private assertActive;
190
199
  private handleError;
200
+ /**
201
+ * Marks a live controller failed and reports through `onError` when a phase is
202
+ * given. A throwing hook is logged so the caller still rethrows the original.
203
+ */
204
+ private reportFailure;
191
205
  private getPersistenceStrategy;
192
206
  private createDeliveryPlan;
193
207
  private ensureSession;
@@ -196,6 +210,11 @@ export declare class AssessmentController implements AssessmentControllerHandle
196
210
  hydrate(): Promise<void>;
197
211
  dispose(): Promise<void>;
198
212
  persist(): Promise<void>;
213
+ /**
214
+ * Saves run one at a time, in call order, so a slow older write can never
215
+ * land after a newer one. A failed save rejects only its own caller.
216
+ */
217
+ private save;
199
218
  getSession(): AssessmentSession | null;
200
219
  getRuntimeState(): AssessmentControllerRuntimeState;
201
220
  private buildNavigationSnapshot;