@pie-players/pie-assessment-toolkit 0.3.67 → 0.3.68

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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/attempt/AssessmentSession.d.ts +7 -27
  3. package/dist/components/ItemToolBar.custom-element.js +1 -1
  4. package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
  5. package/dist/components/SectionToolBar.custom-element.js +1 -1
  6. package/dist/components/chunks/ItemToolBar-38mhtjsq.js +51 -0
  7. package/dist/components/chunks/ItemToolBar-9ymm7pd1.js +46 -0
  8. package/dist/components/item-toolbar-element.js +1 -0
  9. package/dist/components/pie-assessment-toolkit-element.js +1 -0
  10. package/dist/components/section-toolbar-element.js +1 -0
  11. package/dist/context/assessment-toolkit-context.d.ts +28 -0
  12. package/dist/index.d.ts +7 -3
  13. package/dist/index.js +4 -2
  14. package/dist/runtime/SectionRuntimeEngine.d.ts +56 -0
  15. package/dist/runtime/SectionRuntimeEngine.js +66 -1
  16. package/dist/runtime/core/engine-resolver.d.ts +25 -1
  17. package/dist/runtime/registration-events.d.ts +52 -0
  18. package/dist/runtime/registration-events.js +2 -0
  19. package/dist/services/AccessibilityCatalogResolver.js +21 -5
  20. package/dist/services/I18nService.d.ts +28 -100
  21. package/dist/services/I18nService.js +43 -233
  22. package/dist/services/TTSService.d.ts +12 -0
  23. package/dist/services/TTSService.js +18 -17
  24. package/dist/services/ToolRegistry.d.ts +86 -2
  25. package/dist/services/ToolRegistry.js +30 -0
  26. package/dist/services/ToolkitCoordinator.d.ts +39 -37
  27. package/dist/services/ToolkitCoordinator.js +40 -0
  28. package/dist/services/audio-handoff.d.ts +39 -0
  29. package/dist/services/audio-handoff.js +58 -0
  30. package/dist/services/catalog-media.d.ts +35 -4
  31. package/dist/services/catalog-media.js +92 -3
  32. package/dist/services/framework-error.d.ts +15 -1
  33. package/dist/services/interfaces.d.ts +28 -0
  34. package/dist/services/pnp-standard-features.d.ts +1 -1
  35. package/dist/services/section-controller-types.d.ts +218 -7
  36. package/dist/services/selection-action.d.ts +49 -0
  37. package/dist/services/selection-action.js +10 -0
  38. package/dist/services/spoken-audio-cards.js +5 -1
  39. package/dist/services/tool-context.d.ts +6 -5
  40. package/dist/services/tool-context.js +197 -152
  41. package/dist/services/tool-icons.d.ts +18 -0
  42. package/dist/services/tool-icons.js +31 -0
  43. package/dist/services/tool-providers/DesmosToolProvider.d.ts +6 -5
  44. package/dist/services/tool-request.d.ts +106 -0
  45. package/dist/services/tool-request.js +127 -0
  46. package/dist/services/toolbar-items.d.ts +6 -0
  47. package/dist/tools/client.d.ts +0 -1
  48. package/dist/tools/client.js +0 -2
  49. package/dist/tools/internal.d.ts +6 -0
  50. package/dist/tools/internal.js +7 -0
  51. package/dist/tools/tool-surface-host.d.ts +57 -0
  52. package/dist/tools/tool-surface-host.js +610 -0
  53. package/package.json +10 -10
  54. package/dist/components/chunks/ItemToolBar-8jgdz50p.js +0 -51
  55. package/dist/components/chunks/ItemToolBar-cvs646j3.js +0 -36
  56. package/dist/tools/calculators/desmos-provider.d.ts +0 -46
  57. package/dist/tools/calculators/desmos-provider.js +0 -393
@@ -1,3 +1,12 @@
1
+ import type { FormativeFeedbackReveal, FormativeMasteryRollup, FormativeSectionProjection, FormativeTryOutcome } from "@pie-players/pie-players-shared/formative";
2
+ import type { MediaTimeSource, TimedMediaDegradation, TimedMediaSectionProjection, TimedMediaValidationError } from "@pie-players/pie-players-shared/timed-media";
3
+ import type { SectionControllerSessionState } from "@pie-players/pie-players-shared/types";
4
+ /**
5
+ * Re-exported so every existing import site keeps its specifier. The shape is
6
+ * canonical in `@pie-players/pie-players-shared/types`, beside
7
+ * `AssessmentSection` and the delivery slices it carries.
8
+ */
9
+ export type { SectionControllerSessionState };
1
10
  export interface SectionControllerKey {
2
11
  assessmentId: string;
3
12
  sectionId: string;
@@ -62,11 +71,6 @@ export interface SectionControllerRuntimeState {
62
71
  */
63
72
  loadedRenderables?: ReadonlyArray<SectionControllerLoadedRenderable>;
64
73
  }
65
- export interface SectionControllerSessionState {
66
- currentItemIndex?: number;
67
- visitedItemIdentifiers?: string[];
68
- itemSessions: Record<string, unknown>;
69
- }
70
74
  type SectionControllerEventBase = {
71
75
  timestamp: number;
72
76
  };
@@ -156,7 +160,100 @@ export type SectionControllerSectionErrorEvent = SectionControllerEventBase & {
156
160
  contentKind?: "item" | "passage" | "rubric" | "unknown";
157
161
  currentItemIndex: number;
158
162
  };
159
- export type SectionControllerEvent = SectionControllerItemSessionDataChangedEvent | SectionControllerItemSessionMetaChangedEvent | SectionControllerItemSelectedEvent | SectionControllerSectionNavigationChangedEvent | SectionControllerSectionSessionAppliedEvent | SectionControllerContentLoadedEvent | SectionControllerItemPlayerErrorEvent | SectionControllerItemCompleteChangedEvent | SectionControllerSectionLoadingCompleteEvent | SectionControllerSectionItemsCompleteChangedEvent | SectionControllerSectionErrorEvent;
163
+ export type SectionControllerFormativeTryRecordedEvent = SectionControllerEventBase & {
164
+ type: "formative-try-recorded";
165
+ itemId: string;
166
+ canonicalItemId: string;
167
+ tryCount: number;
168
+ outcome: FormativeTryOutcome;
169
+ /** Whether feedback is now on screen for this item. */
170
+ revealed: boolean;
171
+ currentItemIndex: number;
172
+ };
173
+ /**
174
+ * The reveal state changed without a Try being recorded: a learner dismissed
175
+ * feedback to edit again, or a host forced a reveal or withdrew one.
176
+ *
177
+ * A Try that reveals reports through `formative-try-recorded`, which carries its
178
+ * own `revealed`. This event is for every other transition.
179
+ */
180
+ export type SectionControllerFormativeRevealChangedEvent = SectionControllerEventBase & {
181
+ type: "formative-reveal-changed";
182
+ itemId: string;
183
+ canonicalItemId: string;
184
+ revealed: boolean;
185
+ /** Reveal level in force, when a host overrode the policy's. */
186
+ feedback?: FormativeFeedbackReveal;
187
+ /** Tries already spent; none of these transitions consume one. */
188
+ tryCount: number;
189
+ source: "learner" | "host";
190
+ currentItemIndex: number;
191
+ };
192
+ export type SectionControllerSectionMasteryChangedEvent = SectionControllerEventBase & {
193
+ type: "section-mastery-changed";
194
+ mastery: FormativeMasteryRollup;
195
+ currentItemIndex: number;
196
+ };
197
+ /**
198
+ * Cue state changed: a cue activated, a gate released, or the section's
199
+ * aggregate completion flipped.
200
+ *
201
+ * Deliberately not emitted for media position. A `timeupdate` fires about four
202
+ * times a second and moves nothing a layout renders — the media element draws its
203
+ * own clock — so position is recorded in the session slice and reported here only
204
+ * through the cue transitions it caused.
205
+ */
206
+ export type SectionControllerTimedMediaCueChangedEvent = SectionControllerEventBase & {
207
+ type: "timed-media-cue-changed";
208
+ /** The cue that activated in this transition, where one did. */
209
+ activatedCueIdentifier?: string;
210
+ /** The gate that released in this transition, where one did. */
211
+ releasedCueIdentifier?: string;
212
+ activeCueIdentifier?: string;
213
+ visitedCueIdentifiers: string[];
214
+ completedCueIdentifiers: string[];
215
+ /** Canonical ids of every item a visited cue has revealed. */
216
+ revealedItemIds: string[];
217
+ /** The gate holding playback, or `null`. */
218
+ gateCueIdentifier: string | null;
219
+ mediaCompleted: boolean;
220
+ aggregateComplete: boolean;
221
+ currentItemIndex: number;
222
+ };
223
+ /**
224
+ * Media audio is now running, so any other audio owner must yield.
225
+ *
226
+ * Emitted only where playback actually stood: a gate that re-paused on the same
227
+ * `play` produced no audio. Carries no payload — that media audio started is the
228
+ * whole fact, and cue state travels on `timed-media-cue-changed`.
229
+ */
230
+ export type SectionControllerTimedMediaAudioStartedEvent = SectionControllerEventBase & {
231
+ type: "timed-media-audio-started";
232
+ currentItemIndex: number;
233
+ };
234
+ /**
235
+ * A playback policy the attached media time source cannot carry out, reported so
236
+ * the gap is visible rather than silent. Cues still fire and state is still
237
+ * recorded; only enforcement is lost.
238
+ */
239
+ export type SectionControllerTimedMediaPolicyDegradedEvent = SectionControllerEventBase & {
240
+ type: "timed-media-policy-degraded";
241
+ degradations: TimedMediaDegradation[];
242
+ currentItemIndex: number;
243
+ };
244
+ /**
245
+ * Authored `timedMedia` this section cannot deliver — an unresolvable
246
+ * `stimulusRef`, a cue naming an item the section does not hold, a gate on
247
+ * correctness with no `onUnknownCorrectness`. The section still renders, as an
248
+ * ordinary section with every item visible, because cues that silently never fire
249
+ * is the failure mode this contract exists to avoid.
250
+ */
251
+ export type SectionControllerTimedMediaInvalidEvent = SectionControllerEventBase & {
252
+ type: "timed-media-invalid";
253
+ errors: TimedMediaValidationError[];
254
+ currentItemIndex: number;
255
+ };
256
+ export type SectionControllerEvent = SectionControllerItemSessionDataChangedEvent | SectionControllerItemSessionMetaChangedEvent | SectionControllerItemSelectedEvent | SectionControllerSectionNavigationChangedEvent | SectionControllerSectionSessionAppliedEvent | SectionControllerContentLoadedEvent | SectionControllerItemPlayerErrorEvent | SectionControllerItemCompleteChangedEvent | SectionControllerSectionLoadingCompleteEvent | SectionControllerSectionItemsCompleteChangedEvent | SectionControllerSectionErrorEvent | SectionControllerFormativeTryRecordedEvent | SectionControllerFormativeRevealChangedEvent | SectionControllerSectionMasteryChangedEvent | SectionControllerTimedMediaCueChangedEvent | SectionControllerTimedMediaAudioStartedEvent | SectionControllerTimedMediaPolicyDegradedEvent | SectionControllerTimedMediaInvalidEvent;
160
257
  export type SectionControllerEventType = SectionControllerEvent["type"];
161
258
  /**
162
259
  * Host-facing controller for a single section cohort.
@@ -295,6 +392,121 @@ export interface SectionControllerHandle {
295
392
  * is what the strategy load/save methods exchange.
296
393
  */
297
394
  configureSessionPersistence?(config: SectionSessionPersistenceConfig): void | Promise<void>;
395
+ /**
396
+ * Record one Try for an item: derive correctness from the element outcomes,
397
+ * increment the Try count, and reveal feedback if the item's resolved
398
+ * formative policy says so.
399
+ *
400
+ * `outcomes` is what `pie-item-player.provideScore()` returned. The caller
401
+ * does not interpret it — correctness derivation belongs to the controller so
402
+ * one aggregation policy applies everywhere.
403
+ *
404
+ * No-op when the item's policy is disabled, when feedback is already
405
+ * revealed, or when Tries are spent, which makes a double submit safe.
406
+ */
407
+ recordFormativeTry?(args: {
408
+ itemId: string;
409
+ outcomes?: unknown[];
410
+ }): void;
411
+ /**
412
+ * Dismiss a revealed item's feedback and reopen it for editing. Withdrawing
413
+ * the reveal is what withdraws the `mode: "evaluate"` projection.
414
+ *
415
+ * The learner action, so it respects the Try budget: no-op when nothing is
416
+ * revealed or Tries are spent. Use `hideFormativeItem` for host authority.
417
+ */
418
+ retryFormativeItem?(args: {
419
+ itemId: string;
420
+ }): void;
421
+ /**
422
+ * Reveal an item on host authority — a teacher-driven "show the answer".
423
+ *
424
+ * Spends no Try and ignores the Try budget and `revealOn`, none of which bound
425
+ * a decision the host has already taken. Works on an item with no Try yet.
426
+ *
427
+ * `feedback` is stated rather than taken from the policy, because a reveal
428
+ * under `feedback: "none"` would project nothing.
429
+ */
430
+ revealFormativeItem?(args: {
431
+ itemId: string;
432
+ feedback: "correctness" | "solution";
433
+ }): void;
434
+ /**
435
+ * Withdraw a reveal on host authority. Unlike `retryFormativeItem` this
436
+ * ignores the Try budget, so a host that revealed a spent item can put it
437
+ * back.
438
+ */
439
+ hideFormativeItem?(args: {
440
+ itemId: string;
441
+ }): void;
442
+ /**
443
+ * The section's formative projection — resolved policies, per-item state, and
444
+ * the mastery rollup.
445
+ *
446
+ * `null` when no item in the section delivers formatively. The same
447
+ * projection rides on the composition model, so layouts normally read it from
448
+ * there rather than calling this.
449
+ */
450
+ getFormativeProjection?(): FormativeSectionProjection | null;
451
+ /**
452
+ * Bind the section's Media Time Source — the only way it reaches media.
453
+ *
454
+ * Called by the stimulus card once it has a media element to adapt, and
455
+ * callable directly by a host that supplies its own port: a third-party embed,
456
+ * a remote-controlled player, anything that can report time. That is the point
457
+ * of the port. Attaching replaces any previous source.
458
+ *
459
+ * The port declares `canPause` / `canRestrictSeeking`; where a capability is
460
+ * missing the matching policy degrades to advisory and the controller emits
461
+ * `timed-media-policy-degraded` rather than appearing to enforce.
462
+ *
463
+ * No-op on a section that carries no valid `timedMedia`.
464
+ */
465
+ attachMediaTimeSource?(source: MediaTimeSource, options?: {
466
+ /**
467
+ * `"native-adapter"` marks the stimulus card's own discovery, which never
468
+ * displaces a source a host attached explicitly.
469
+ */
470
+ origin?: "native-adapter" | "host";
471
+ /**
472
+ * The renderable the source was found in. Checked against the renderable
473
+ * `stimulusRef` resolved to, so a second video passage's adapter cannot
474
+ * drive the timeline. Omitted by a host attaching its own port, which names
475
+ * no renderable and is taken at its word.
476
+ */
477
+ renderableId?: string;
478
+ }): void;
479
+ /**
480
+ * Release the current source. Cue state is kept; nothing advances it.
481
+ *
482
+ * A `"native-adapter"` detach is ignored while a host-attached source is live:
483
+ * the stimulus card tears its adapter down whenever its content re-renders, and
484
+ * that must not take a host's own player with it.
485
+ */
486
+ detachMediaTimeSource?(options?: {
487
+ origin?: "native-adapter" | "host";
488
+ }): void;
489
+ /**
490
+ * Silence media audio so read-aloud can speak over the same stimulus.
491
+ *
492
+ * One half of the TTS/media handoff. The section stops media and announces
493
+ * `timed-media-audio-started` when media audio resumes; the toolkit owns the
494
+ * policy, because only the toolkit holds both the TTS service and the section.
495
+ *
496
+ * Returns whether media audio is now silent — `false` for a playing source that
497
+ * reports no `canPause`, where the overlap stands rather than read-aloud being
498
+ * withheld.
499
+ */
500
+ pauseMediaForCompetingAudio?(): boolean;
501
+ /**
502
+ * The section's timed-media projection — cues, enforcement, revealed items and
503
+ * the gate currently holding playback.
504
+ *
505
+ * `null` when the section is not timed media or its `timedMedia` failed
506
+ * validation. The same projection rides on the composition model, so layouts
507
+ * read it from there rather than calling this.
508
+ */
509
+ getTimedMediaProjection?(): TimedMediaSectionProjection | null;
298
510
  }
299
511
  export interface SectionControllerFactoryDefaults {
300
512
  createDefaultController: () => SectionControllerHandle | Promise<SectionControllerHandle>;
@@ -302,4 +514,3 @@ export interface SectionControllerFactoryDefaults {
302
514
  export interface SectionPersistenceFactoryDefaults {
303
515
  createDefaultPersistence: () => SectionSessionPersistenceStrategy | Promise<SectionSessionPersistenceStrategy>;
304
516
  }
305
- export {};
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Host-supplied actions on a text selection.
3
+ *
4
+ * The contract between a selection gateway and whoever composes it. The gateway
5
+ * renders the actions it is handed and knows nothing about what they do; the
6
+ * composer knows which capabilities exist and pairs them up. That split is what
7
+ * keeps a gateway from naming a dictionary, and what lets a host contribute an
8
+ * action for a capability PIE does not ship.
9
+ */
10
+ /** What the gateway hands an action when the learner activates it. */
11
+ export interface ToolSelectionContext {
12
+ /** The selected text, trimmed and whitespace-collapsed by the gateway. */
13
+ text: string;
14
+ /**
15
+ * The live range, or `null` when the gateway has already lost it.
16
+ *
17
+ * An action that only needs the words should use `text`; the range is for an
18
+ * action that has to know where in the content the words were.
19
+ */
20
+ range: Range | null;
21
+ }
22
+ export interface ToolSelectionAction {
23
+ /** Stable id, used as the button's key and its `data-pie-selection-action`. */
24
+ id: string;
25
+ /** Visible and accessible name. */
26
+ label: string;
27
+ /**
28
+ * Inline SVG markup for the button face.
29
+ *
30
+ * Markup rather than an icon name because a gateway has no icon registry, and
31
+ * the composer already resolves names through the toolkit's own map. Rendered
32
+ * into the gateway's shadow root and treated as trusted composer-authored
33
+ * markup, on the same footing as the shell's own action icons — never a place
34
+ * to put anything that reached the composer from content or a service.
35
+ */
36
+ iconSvg?: string;
37
+ /** Longer tooltip, when the label alone reads as terse in a strip of icons. */
38
+ tooltip?: string;
39
+ /**
40
+ * Whether to offer the action at all, asked on each render.
41
+ *
42
+ * An action whose target capability is not available answers `false` rather
43
+ * than being offered and failing: an affordance that does nothing costs a
44
+ * learner a keystroke and their confidence in the rest of the strip.
45
+ */
46
+ isAvailable?: () => boolean;
47
+ /** Perform the action. */
48
+ run: (selection: ToolSelectionContext) => void;
49
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Host-supplied actions on a text selection.
3
+ *
4
+ * The contract between a selection gateway and whoever composes it. The gateway
5
+ * renders the actions it is handed and knows nothing about what they do; the
6
+ * composer knows which capabilities exist and pairs them up. That split is what
7
+ * keeps a gateway from naming a dictionary, and what lets a host contribute an
8
+ * action for a capability PIE does not ship.
9
+ */
10
+ export {};
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * Part of PIE Assessment Toolkit.
22
22
  */
23
- import { normalizeMediaFragment, normalizeMediaSources, trimmedOrUndefined, } from "./catalog-media.js";
23
+ import { isUnsupportedMediaAssetVersion, normalizeMediaFragment, normalizeMediaSources, trimmedOrUndefined, } from "./catalog-media.js";
24
24
  /** Catalog type token for spoken alternates. Matches QTI 3's `support` value. */
25
25
  export const SPOKEN_CATALOG_TYPE = "spoken";
26
26
  /**
@@ -46,6 +46,10 @@ export function resolveSpokenAudioMedia(card) {
46
46
  console.warn("[spoken-audio] card carries a `payload` with no `media`; recorded speech needs `media.sources`, so this card is ignored and read-aloud falls back to the script or generated speech");
47
47
  return null;
48
48
  }
49
+ if (isUnsupportedMediaAssetVersion(media.version)) {
50
+ console.warn(`[spoken-audio] card's media declares version ${String(media.version)}, which this build does not render; the card is ignored and read-aloud falls back to the script or generated speech`);
51
+ return null;
52
+ }
49
53
  // A signing video filed under `spoken` is a mis-authored card, not an audio
50
54
  // track to guess at. Refusing it keeps the two card types from quietly
51
55
  // swapping roles.
@@ -125,14 +125,15 @@ export declare function isRubricContext(context: ToolContext): context is Rubric
125
125
  * Type guard to check if context is at element level
126
126
  */
127
127
  export declare function isElementContext(context: ToolContext): context is ElementToolContext;
128
- /**
129
- * Helper to extract text content from an item or element for analysis
130
- */
128
+ /** The plain text a context carries, tags removed. */
131
129
  export declare function extractTextContent(context: ToolContext): string;
132
130
  /**
133
- * Helper to check if context contains mathematical content
134
- * (Basic heuristic - can be overridden by tools)
131
+ * The authored markup a context carries, tags intact.
132
+ *
133
+ * For indicators that live in the markup rather than in the prose — `<math>`
134
+ * above all, whose whole signal is the element name.
135
135
  */
136
+ export declare function extractMarkupContent(context: ToolContext): string;
136
137
  export declare function hasMathContent(context: ToolContext): boolean;
137
138
  /**
138
139
  * Helper to check if context contains choice-based interactions