@pie-players/pie-section-player 0.3.66 → 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.
package/README.md CHANGED
@@ -103,6 +103,164 @@ policy](#js-api-example-for-advanced-host-policy). Key event types:
103
103
  loading.
104
104
  - `section-items-complete-changed` — aggregate completion flip.
105
105
  - `section-navigation-change` — the controller's section identity changed.
106
+ - `formative-try-recorded` — a learner checked an answer.
107
+ - `formative-reveal-changed` — the reveal state changed without a Try: a learner
108
+ dismissed feedback, or a host forced or withdrew a reveal (`source` says which).
109
+ - `section-mastery-changed` — the mastery rollup changed. Emitted on change
110
+ only, like `section-items-complete-changed`.
111
+ - `timed-media-cue-changed` — a cue activated, a gate released, or aggregate
112
+ completion flipped. Not emitted for media position: `timeupdate` fires about four
113
+ times a second and moves nothing a layout renders.
114
+ - `timed-media-audio-started` — media audio is running, so read-aloud must yield.
115
+ Emitted only where playback actually stood; a gate that re-paused on the same
116
+ `play` produced no audio.
117
+ - `timed-media-policy-degraded` — the attached media time source cannot carry out a
118
+ playback policy, so it is advisory from here.
119
+ - `timed-media-invalid` — authored `timedMedia` that cannot be delivered; the
120
+ section renders without cue behavior.
121
+
122
+ ### Formative delivery
123
+
124
+ Set `formative` on the section and the player renders a check-answer control per
125
+ item, records Tries, and reveals feedback:
126
+
127
+ ```ts
128
+ const section: AssessmentSection = {
129
+ identifier: "practice-set",
130
+ formative: { enabled: true, maxTries: 3, feedback: "correctness" },
131
+ assessmentItemRefs: [
132
+ { identifier: "q1", item },
133
+ // Overrides the section default field by field.
134
+ { identifier: "q2", item, formative: { maxTries: 1, feedback: "solution" } },
135
+ { identifier: "q3", item, formative: { enabled: false } },
136
+ ],
137
+ };
138
+ ```
139
+
140
+ Absent, or `enabled: false`, and delivery is unchanged: no control, no state, no
141
+ `env` override, and `getSession()` does not carry the key.
142
+
143
+ PIE renders no feedback of its own. A revealed item gets `mode: "evaluate"`
144
+ projected over the section env — with `role: "instructor"` under
145
+ `feedback: "solution"` — and the element draws the rest. Only that item's env
146
+ changes; its neighbours stay editable. A retry withdraws the projection.
147
+
148
+ Read the resolved state from `getFormativeProjection()`, or off
149
+ `compositionModel.formative` in a custom layout. Drive it from a host through the
150
+ same handle:
151
+
152
+ ```ts
153
+ const controller = await host.waitForSectionController?.(5000);
154
+ // The learner's actions, budget-respecting.
155
+ controller?.recordFormativeTry?.({ itemId, outcomes }); // outcomes from provideScore()
156
+ controller?.retryFormativeItem?.({ itemId });
157
+ // Host authority: a teacher-driven "show the answer". Spends no Try, ignores the
158
+ // Try budget, works on an item with no Try yet.
159
+ controller?.revealFormativeItem?.({ itemId, feedback: "solution" });
160
+ controller?.hideFormativeItem?.({ itemId });
161
+ ```
162
+
163
+ `feedback` is stated rather than taken from the policy, because a reveal under
164
+ `feedback: "none"` would project nothing. A learner retry clears it, so a forced
165
+ solution does not upgrade every later reveal on that item.
166
+
167
+ Try state persists inside `SectionControllerSessionState.formative` and hydrates
168
+ with the rest of the snapshot. See
169
+ [`docs/prds/formative-delivery-contract.md`](../../docs/prds/formative-delivery-contract.md)
170
+ for the full contract, its QTI 3 mapping, and the mastery denominator rule.
171
+
172
+ ### Timed media
173
+
174
+ Set `sectionType: "timed-media"` and a `timedMedia` block, and the section's cue
175
+ timeline decides when its items are delivered:
176
+
177
+ ```ts
178
+ const section: AssessmentSection = {
179
+ identifier: "water-cycle",
180
+ sectionType: "timed-media",
181
+ // A correctness gate needs unlimited Tries; see below.
182
+ formative: { enabled: true, maxTries: "unlimited", feedback: "correctness" },
183
+ rubricBlocks: [
184
+ {
185
+ identifier: "video-stimulus-1",
186
+ class: "stimulus",
187
+ view: ["candidate"],
188
+ // An ordinary passage. Its config mounts the media element — a PIE element,
189
+ // or authored `<video>` markup — and it owns the accessibility catalogs that
190
+ // carry captions, transcript and signed alternates.
191
+ passage: videoPassage,
192
+ },
193
+ ],
194
+ assessmentItemRefs: [{ identifier: "q1", item }, { identifier: "q2", item }],
195
+ timedMedia: {
196
+ stimulusRef: "video-stimulus-1",
197
+ cues: [
198
+ { identifier: "c1", range: { startSeconds: 4 }, itemRefs: ["q1"], policy: { activation: "reveal" } },
199
+ {
200
+ identifier: "c2",
201
+ range: { startSeconds: 10 },
202
+ itemRefs: ["q2"],
203
+ policy: { activation: "gate", releaseOn: "correct", onUnknownCorrectness: "release" },
204
+ },
205
+ ],
206
+ playbackPolicy: { allowSeekAhead: false, pauseOnRequiredCue: true, requireMediaCompletion: false },
207
+ },
208
+ };
209
+ ```
210
+
211
+ Every item a gate names must satisfy its `releaseOn`. To split must-answer items from
212
+ optional ones, author two cues at the same timestamp — a gate over the first set, a
213
+ reveal over the second. Both activate in the same pass, the reveal completes at once,
214
+ and only the gate holds playback.
215
+
216
+ Absent `sectionType` and delivery is unchanged: no projection, no session slice, no
217
+ cue behavior. An item no `reveal` or `gate` cue names is delivered normally —
218
+ including one a `metadata` cue names, since metadata records state and reveals
219
+ nothing. A cued item is mounted and hidden until its cue fires, so its session and
220
+ shell registration survive a seek backwards.
221
+
222
+ The section reaches media only through a **Media Time Source**. The stimulus card
223
+ finds the media element its passage mounted and registers a native adapter; a host
224
+ with its own player registers its own port instead, and that port outranks the
225
+ card's discovery for as long as it is attached:
226
+
227
+ ```ts
228
+ const controller = await host.waitForSectionController?.(5000);
229
+ // No `renderableId`: a host is asserting its own port, where a renderable's adapter
230
+ // has to name itself and is ignored unless it is the resolved stimulus.
231
+ controller?.attachMediaTimeSource?.(myThirdPartyPort);
232
+ controller?.detachMediaTimeSource?.();
233
+ controller?.getTimedMediaProjection?.(); // cues, gate, enforcement, revealed items
234
+ // One half of the read-aloud handoff; `false` means the port cannot pause, so the
235
+ // overlap stands rather than the accommodation being withheld.
236
+ controller?.pauseMediaForCompetingAudio?.();
237
+ ```
238
+
239
+ Read-aloud and media audio never run at once, and the action the learner just took
240
+ wins: starting read-aloud pauses media, starting media pauses read-aloud. The section
241
+ supplies both halves — the method above and `timed-media-audio-started` — and the
242
+ toolkit arbitrates between them, because only the toolkit holds the TTS service and
243
+ the section. Neither direction resumes what it silenced.
244
+
245
+ Where the port reports `canPause: false` or `canRestrictSeeking: false`, the
246
+ matching policy degrades to **advisory**: cues still fire, state is still recorded,
247
+ the projection says `enforcement: "advisory"`, and a recoverable `timed-media`
248
+ framework warning names the policy that lost its teeth. Nothing silently pretends to
249
+ hold.
250
+
251
+ Three authoring mistakes fail loudly rather than delivering inert cues: a
252
+ `stimulusRef` that resolves to no renderable in the section; a gate on correctness
253
+ over an item without unlimited Tries, where a learner who spent a finite budget could
254
+ never release playback again; and a `stimulusRef` that resolves to a renderable which
255
+ mounts no media, reported once the section's content has loaded and no time source
256
+ has attached. Each reports a `timed-media` framework error, after which the section
257
+ delivers as an ordinary section with every item visible.
258
+
259
+ Cue state persists inside `SectionControllerSessionState.timedMedia` and hydrates
260
+ with the rest of the snapshot, including the furthest position reached, which is what
261
+ `allowSeekAhead: false` clamps against across a reload. See
262
+ [`docs/prds/timed-media-section-contract.md`](../../docs/prds/timed-media-section-contract.md)
263
+ for the contract and the decision record.
106
264
 
107
265
  ## Usage
108
266