@pie-players/pie-players-shared 1.0.0-beta.8 → 1.0.0-beta.9

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 (31) hide show
  1. package/README.md +13 -13
  2. package/dist/i18n/messages/en-US.d.ts +4 -4
  3. package/dist/i18n/messages/en-US.js +4 -4
  4. package/dist/i18n/types.d.ts +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/item-session-control/index.d.ts +16 -0
  7. package/dist/item-session-control/index.js +5 -0
  8. package/dist/{formative → item-session-control}/mastery.d.ts +5 -5
  9. package/dist/{formative → item-session-control}/mastery.js +3 -3
  10. package/dist/{formative → item-session-control}/outcome.d.ts +3 -3
  11. package/dist/{formative → item-session-control}/outcome.js +2 -2
  12. package/dist/{formative → item-session-control}/policy.d.ts +11 -11
  13. package/dist/{formative → item-session-control}/policy.js +12 -12
  14. package/dist/item-session-control/session.d.ts +19 -0
  15. package/dist/{formative → item-session-control}/session.js +12 -12
  16. package/dist/{formative → item-session-control}/state.d.ts +26 -26
  17. package/dist/{formative → item-session-control}/state.js +13 -13
  18. package/dist/{formative → item-session-control}/types.d.ts +38 -40
  19. package/dist/{formative → item-session-control}/types.js +2 -2
  20. package/dist/timed-media/index.d.ts +1 -1
  21. package/dist/timed-media/index.js +1 -1
  22. package/dist/timed-media/policy.d.ts +2 -2
  23. package/dist/timed-media/policy.js +2 -2
  24. package/dist/timed-media/session.d.ts +1 -1
  25. package/dist/timed-media/session.js +1 -1
  26. package/dist/timed-media/types.d.ts +11 -11
  27. package/dist/types/index.d.ts +16 -16
  28. package/package.json +4 -4
  29. package/dist/formative/index.d.ts +0 -16
  30. package/dist/formative/index.js +0 -5
  31. package/dist/formative/session.d.ts +0 -19
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Shared runtime utilities for the `@pie-players/*` player suite: the element
4
4
  loader, PIE config and session helpers, markup security, i18n, and the
5
- formative, timed-media and media contracts. For framework code and host
5
+ item session control, timed-media and media contracts. For framework code and host
6
6
  integrations that need the helpers the player packages use.
7
7
 
8
8
  ## Public Exports
@@ -23,7 +23,7 @@ The subpaths below are the package's `exports`; nothing else is importable.
23
23
  | `/security/private-hosts` | `isPrivateHostname` and `isCloudMetadataHostname`, the host classification for a URL a server fetches on someone else's behalf. Node-safe: it loads none of the markup sanitizers |
24
24
  | `/object` | `mergeObjectsIgnoringNullUndefined`, `cloneDeep`, `isPlainRecord` |
25
25
  | `/types` | The shared types: item, passage, section and session entities, PIE models and controllers, accessibility catalogs, and the [media vocabulary](#media-validation) |
26
- | `/formative` | [Formative delivery](#formative-delivery) |
26
+ | `/item-session-control` | [Item session control](#item-session-control) |
27
27
  | `/timed-media` | [Timed media](#timed-media) |
28
28
  | `/media` | [Media validation](#media-validation) |
29
29
  | `/pie` | PIE runtime utilities: config helpers, bundle initialization, `ItemController` and the item session contract, session commit and snapshots, authoring helpers, math renderer setup, instrumentation event maps ([PIE utilities](src/pie/README.md)) |
@@ -40,9 +40,9 @@ The subpaths below are the package's `exports`; nothing else is importable.
40
40
  | `/ui/fontawesome` | The FontAwesome `<nds-icon-button>` glyphs need: the one FA Free pin, Pro detection, the `<head>` and shadow-root installs, and the glyph weight. The assessment toolkit, the section player and `@pie-players/pie-tool-tts-inline` use it |
41
41
  | `/tools/term-lookup` | Term normalization and the lookup session the dictionary tools share; the host supplies the endpoint or a resolver |
42
42
 
43
- ## Formative Delivery
43
+ ## Item Session Control
44
44
 
45
- `@pie-players/pie-players-shared/formative` holds the policy resolution, outcome
45
+ `@pie-players/pie-players-shared/item-session-control` holds the policy resolution, outcome
46
46
  aggregation, Try-state reducer, item view, and mastery rollup that
47
47
  `pie-section-player` drives. It is pure (no DOM, no timers, no element
48
48
  registry), so it is testable without a browser and importable by an adapter that
@@ -50,18 +50,18 @@ has no player.
50
50
 
51
51
  ```ts
52
52
  import {
53
- aggregateFormativeOutcome,
54
- resolveFormativePolicy,
55
- resolveFormativeItemView,
56
- rollupFormativeMastery,
57
- } from "@pie-players/pie-players-shared/formative";
53
+ aggregateTryOutcome,
54
+ resolveItemSessionControl,
55
+ resolveItemTryView,
56
+ rollupMastery,
57
+ } from "@pie-players/pie-players-shared/item-session-control";
58
58
  ```
59
59
 
60
- The authored half, `FormativeDeliveryPolicy` on `AssessmentSection` and
61
- `FormativeItemPolicy` on `AssessmentItemRef`, is re-exported from the package
60
+ The authored half, `ItemSessionControlPolicy` on `AssessmentSection` and
61
+ `AssessmentItemRef`, is re-exported from the package
62
62
  root and from `/types`, beside the section types it annotates.
63
63
 
64
- The contract is [`docs/prds/formative-delivery-contract.md`](../../docs/prds/formative-delivery-contract.md),
64
+ The contract is [`docs/prds/item-session-control-contract.md`](../../docs/prds/item-session-control-contract.md),
65
65
  including the QTI 3 mapping and why PIE says **Try** rather than "attempt".
66
66
 
67
67
  ## Timed Media
@@ -94,7 +94,7 @@ re-exported from the package root and from `/types`, beside the section types it
94
94
  annotates.
95
95
 
96
96
  The contract is [`docs/prds/timed-media-section-contract.md`](../../docs/prds/timed-media-section-contract.md),
97
- including why cue gate conditions name the formative vocabulary and why an
97
+ including why cue gate conditions name the Try vocabulary and why an
98
98
  unenforceable policy degrades to advisory rather than failing closed.
99
99
 
100
100
  ## Media Validation
@@ -109,16 +109,16 @@ declare const enUS: {
109
109
  loadFailed: string;
110
110
  restoreFailed: string;
111
111
  };
112
- /** Formative delivery: check-answer control and its outcome announcements. */
113
- formative: {
114
- checkAnswer: string;
112
+ /** Item session control: check-answer control and its outcome announcements. */
113
+ check: {
114
+ answer: string;
115
115
  tryAgain: string;
116
116
  answerRecorded: string;
117
117
  correct: string;
118
118
  partlyCorrect: string;
119
119
  notCorrect: string;
120
120
  notAutoScored: string;
121
- checkFailed: string;
121
+ failed: string;
122
122
  /**
123
123
  * Appended after the outcome, so the announcement reads
124
124
  * "Not correct. 2 tries left." Kept a separate plural group rather than
@@ -109,16 +109,16 @@ const enUS = {
109
109
  loadFailed: "The assessment could not be loaded. Try again.",
110
110
  restoreFailed: "Your saved answers for this section could not be restored. Try again.",
111
111
  },
112
- /** Formative delivery: check-answer control and its outcome announcements. */
113
- formative: {
114
- checkAnswer: "Check answer",
112
+ /** Item session control: check-answer control and its outcome announcements. */
113
+ check: {
114
+ answer: "Check answer",
115
115
  tryAgain: "Try again",
116
116
  answerRecorded: "Answer recorded.",
117
117
  correct: "Correct.",
118
118
  partlyCorrect: "Partly correct.",
119
119
  notCorrect: "Not correct.",
120
120
  notAutoScored: "Answer recorded. This question is not scored automatically.",
121
- checkFailed: "This question could not be checked. Try again.",
121
+ failed: "This question could not be checked. Try again.",
122
122
  /**
123
123
  * Appended after the outcome, so the announcement reads
124
124
  * "Not correct. 2 tries left." Kept a separate plural group rather than
@@ -57,7 +57,7 @@ type PluralCategoryKey = keyof PluralTranslation;
57
57
  * Dot-notation keys of a catalog shape.
58
58
  *
59
59
  * A plural group terminates the path at the group itself: `plural()` is called
60
- * with `player.formative.triesLeft`, never with `…triesLeft.other`. A group is
60
+ * with `player.check.triesLeft`, never with `…triesLeft.other`. A group is
61
61
  * recognized by every one of its keys being a CLDR plural category — the
62
62
  * containment has to run this way round, because a group carrying only
63
63
  * `one`/`other` does not contain the whole category union.
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export * from "./security/index.js";
6
6
  export * from "./loaders/index.js";
7
7
  export * from "./object/index.js";
8
8
  export * from "./pie/index.js";
9
- export type { AssessmentEntity, AssessmentItemRef, AssessmentSection, AdvancedItemConfig, ConfigEntity, ConfigResource, Env, FormativeDeliveryPolicy, FormativeItemPolicy, TimedMediaSectionData, ItemConfig, ItemEntity, ItemSession, OutcomeResponse, PassageEntity, PieController, PieContent, PieDefaultModel, PieItemElement, PieModel, PieUpdateSession, ReferencedItemEntity, ReferencedPassageEntity, RubricBlock, TestPart, } from "./types/index.js";
9
+ export type { AssessmentEntity, AssessmentItemRef, AssessmentSection, AdvancedItemConfig, ConfigEntity, ConfigResource, Env, ItemSessionControlPolicy, TimedMediaSectionData, ItemConfig, ItemEntity, ItemSession, OutcomeResponse, PassageEntity, PieController, PieContent, PieDefaultModel, PieItemElement, PieModel, PieUpdateSession, ReferencedItemEntity, ReferencedPassageEntity, RubricBlock, TestPart, } from "./types/index.js";
10
10
  export { editorPostFix } from "./types/index.js";
11
11
  export * from "./ui/attribute-coercion.js";
12
12
  export * from "./ui/content-styles.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Item session control: Try state, feedback reveal, and section mastery.
3
+ *
4
+ * Pure and Node-safe by design — no DOM, no timers, no element registry — so
5
+ * the contract is testable without a browser and an adapter can import it
6
+ * without pulling in a player. Everything that touches the DOM stays in
7
+ * `@pie-players/pie-section-player`.
8
+ *
9
+ * Contract: `docs/prds/item-session-control-contract.md`.
10
+ */
11
+ export type { TryAction, TryCorrectness, ItemSessionControlPolicy, FeedbackEnvOverride, FeedbackReveal, ItemTryState, ItemTryView, MasteryRollup, FeedbackRevealTiming, ScoredElementOutcome, TrySectionProjection, TrySectionSlice, TryLimit, TryOutcome, ResolvedItemSessionControlPolicy, } from "./types.js";
12
+ export { ITEM_SESSION_CONTROL_DEFAULTS, isItemSessionControlEnabledInSection, resolveItemSessionControls, resolveItemSessionControl, } from "./policy.js";
13
+ export { aggregateTryOutcome } from "./outcome.js";
14
+ export { ITEM_TRY_STATE_VERSION, createItemTryState, hideItemFeedback, recordTry, resolveItemTryView, retryItem, revealItemFeedback, } from "./state.js";
15
+ export { MASTERY_ROLLUP_VERSION, rollupMastery } from "./mastery.js";
16
+ export { TRY_SLICE_VERSION, normalizeTrySectionSlice, toTrySectionSlice, } from "./session.js";
@@ -0,0 +1,5 @@
1
+ export { ITEM_SESSION_CONTROL_DEFAULTS, isItemSessionControlEnabledInSection, resolveItemSessionControls, resolveItemSessionControl, } from "./policy.js";
2
+ export { aggregateTryOutcome } from "./outcome.js";
3
+ export { ITEM_TRY_STATE_VERSION, createItemTryState, hideItemFeedback, recordTry, resolveItemTryView, retryItem, revealItemFeedback, } from "./state.js";
4
+ export { MASTERY_ROLLUP_VERSION, rollupMastery } from "./mastery.js";
5
+ export { TRY_SLICE_VERSION, normalizeTrySectionSlice, toTrySectionSlice, } from "./session.js";
@@ -1,5 +1,5 @@
1
- import type { FormativeItemState, FormativeMasteryRollup } from "./types.js";
2
- export declare const FORMATIVE_MASTERY_VERSION: 1;
1
+ import type { ItemTryState, MasteryRollup } from "./types.js";
2
+ export declare const MASTERY_ROLLUP_VERSION: 1;
3
3
  /**
4
4
  * Roll Try outcomes up to the section.
5
5
  *
@@ -10,7 +10,7 @@ export declare const FORMATIVE_MASTERY_VERSION: 1;
10
10
  * excluded: nothing yet says it cannot be scored, so it keeps the section from
11
11
  * reading as complete after one correct answer.
12
12
  */
13
- export declare function rollupFormativeMastery(args: {
13
+ export declare function rollupMastery(args: {
14
14
  itemIdentifiers: readonly string[];
15
- states: Record<string, FormativeItemState>;
16
- }): FormativeMasteryRollup;
15
+ states: Record<string, ItemTryState>;
16
+ }): MasteryRollup;
@@ -1,4 +1,4 @@
1
- export const FORMATIVE_MASTERY_VERSION = 1;
1
+ export const MASTERY_ROLLUP_VERSION = 1;
2
2
  /**
3
3
  * Roll Try outcomes up to the section.
4
4
  *
@@ -9,7 +9,7 @@ export const FORMATIVE_MASTERY_VERSION = 1;
9
9
  * excluded: nothing yet says it cannot be scored, so it keeps the section from
10
10
  * reading as complete after one correct answer.
11
11
  */
12
- export function rollupFormativeMastery(args) {
12
+ export function rollupMastery(args) {
13
13
  const identifiers = args.itemIdentifiers ?? [];
14
14
  const totalItems = identifiers.length;
15
15
  let notScorableItems = 0;
@@ -31,7 +31,7 @@ export function rollupFormativeMastery(args) {
31
31
  }
32
32
  const scorableItems = Math.max(0, totalItems - notScorableItems);
33
33
  return {
34
- version: FORMATIVE_MASTERY_VERSION,
34
+ version: MASTERY_ROLLUP_VERSION,
35
35
  totalItems,
36
36
  scorableItems,
37
37
  masteredItems,
@@ -1,10 +1,10 @@
1
- import type { FormativeScoredOutcome, FormativeTryOutcome } from "./types.js";
1
+ import type { ScoredElementOutcome, TryOutcome } from "./types.js";
2
2
  /**
3
3
  * Reduce the array `pie-item-player.provideScore()` returns to one Try outcome.
4
4
  *
5
5
  * The aggregation mirrors the policy the persisted API scoring path already
6
6
  * documents (`docs/item-player/scoring-and-rubrics.md`), so a browser-derived
7
- * formative result and a server-derived score do not disagree about what a
7
+ * Try result and a server-derived score do not disagree about what a
8
8
  * multi-element item is worth:
9
9
  *
10
10
  * - one scored outcome: `points = score`, `max = max ?? 1`;
@@ -16,4 +16,4 @@ import type { FormativeScoredOutcome, FormativeTryOutcome } from "./types.js";
16
16
  * those slots count toward `totalElementCount` and not toward
17
17
  * `scoredElementCount`.
18
18
  */
19
- export declare function aggregateFormativeOutcome(outcomes: ReadonlyArray<FormativeScoredOutcome | null | undefined> | null): FormativeTryOutcome;
19
+ export declare function aggregateTryOutcome(outcomes: ReadonlyArray<ScoredElementOutcome | null | undefined> | null): TryOutcome;
@@ -39,7 +39,7 @@ function classify(points, max) {
39
39
  *
40
40
  * The aggregation mirrors the policy the persisted API scoring path already
41
41
  * documents (`docs/item-player/scoring-and-rubrics.md`), so a browser-derived
42
- * formative result and a server-derived score do not disagree about what a
42
+ * Try result and a server-derived score do not disagree about what a
43
43
  * multi-element item is worth:
44
44
  *
45
45
  * - one scored outcome: `points = score`, `max = max ?? 1`;
@@ -51,7 +51,7 @@ function classify(points, max) {
51
51
  * those slots count toward `totalElementCount` and not toward
52
52
  * `scoredElementCount`.
53
53
  */
54
- export function aggregateFormativeOutcome(outcomes) {
54
+ export function aggregateTryOutcome(outcomes) {
55
55
  const all = Array.isArray(outcomes) ? outcomes : [];
56
56
  const scored = all.filter(isScored);
57
57
  const totalElementCount = all.length;
@@ -1,13 +1,13 @@
1
- import type { FormativeDeliveryPolicy, FormativeItemPolicy, ResolvedFormativePolicy } from "./types.js";
1
+ import type { ItemSessionControlPolicy, ResolvedItemSessionControlPolicy } from "./types.js";
2
2
  /**
3
3
  * Built-in defaults. `enabled: false` is what makes this contract invisible to
4
- * every section authored before it: an absent `formative` field resolves to a
4
+ * every section authored before it: an absent `itemSessionControl` field resolves to a
5
5
  * disabled policy, and a disabled policy produces no state, no projection and
6
6
  * no control.
7
7
  *
8
8
  * `maxTries: 1` matches QTI 3's `qti-item-session-control@max-attempts` default.
9
9
  */
10
- export declare const FORMATIVE_POLICY_DEFAULTS: ResolvedFormativePolicy;
10
+ export declare const ITEM_SESSION_CONTROL_DEFAULTS: ResolvedItemSessionControlPolicy;
11
11
  /**
12
12
  * Merge built-in defaults, the section policy, and one item ref's override, in
13
13
  * that order. Each field overrides independently, which is the order QTI 3 uses
@@ -17,21 +17,21 @@ export declare const FORMATIVE_POLICY_DEFAULTS: ResolvedFormativePolicy;
17
17
  * section: authored policy is host-supplied data, and a typo in one field should
18
18
  * not cost the learner the whole item.
19
19
  */
20
- export declare function resolveFormativePolicy(sectionPolicy?: FormativeDeliveryPolicy | null, itemPolicy?: FormativeItemPolicy | null): ResolvedFormativePolicy;
20
+ export declare function resolveItemSessionControl(sectionPolicy?: ItemSessionControlPolicy | null, itemPolicy?: ItemSessionControlPolicy | null): ResolvedItemSessionControlPolicy;
21
21
  /**
22
22
  * Resolve one policy per item in section order.
23
23
  *
24
24
  * Keyed by the identifier the caller supplies, which in the section runtime is
25
25
  * the canonical item id — the same key `itemSessions` and the completion map
26
- * use, so a formative state and an item session are always addressable by one
26
+ * use, so Try state and an item session are always addressable by one
27
27
  * id.
28
28
  */
29
- export declare function resolveFormativePolicies(args: {
30
- sectionPolicy?: FormativeDeliveryPolicy | null;
29
+ export declare function resolveItemSessionControls(args: {
30
+ sectionPolicy?: ItemSessionControlPolicy | null;
31
31
  items: ReadonlyArray<{
32
32
  identifier: string;
33
- policy?: FormativeItemPolicy | null;
33
+ policy?: ItemSessionControlPolicy | null;
34
34
  }>;
35
- }): Record<string, ResolvedFormativePolicy>;
36
- /** True when at least one item in the section delivers formatively. */
37
- export declare function isFormativeSectionEnabled(policies: Record<string, ResolvedFormativePolicy>): boolean;
35
+ }): Record<string, ResolvedItemSessionControlPolicy>;
36
+ /** True when at least one item in the section enables item session control. */
37
+ export declare function isItemSessionControlEnabledInSection(policies: Record<string, ResolvedItemSessionControlPolicy>): boolean;
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Built-in defaults. `enabled: false` is what makes this contract invisible to
3
- * every section authored before it: an absent `formative` field resolves to a
3
+ * every section authored before it: an absent `itemSessionControl` field resolves to a
4
4
  * disabled policy, and a disabled policy produces no state, no projection and
5
5
  * no control.
6
6
  *
7
7
  * `maxTries: 1` matches QTI 3's `qti-item-session-control@max-attempts` default.
8
8
  */
9
- export const FORMATIVE_POLICY_DEFAULTS = {
9
+ export const ITEM_SESSION_CONTROL_DEFAULTS = {
10
10
  enabled: false,
11
11
  maxTries: 1,
12
12
  feedback: "correctness",
@@ -40,21 +40,21 @@ function normalizeRevealTiming(value) {
40
40
  * section: authored policy is host-supplied data, and a typo in one field should
41
41
  * not cost the learner the whole item.
42
42
  */
43
- export function resolveFormativePolicy(sectionPolicy, itemPolicy) {
43
+ export function resolveItemSessionControl(sectionPolicy, itemPolicy) {
44
44
  const enabled = typeof itemPolicy?.enabled === "boolean"
45
45
  ? itemPolicy.enabled
46
46
  : typeof sectionPolicy?.enabled === "boolean"
47
47
  ? sectionPolicy.enabled
48
- : FORMATIVE_POLICY_DEFAULTS.enabled;
48
+ : ITEM_SESSION_CONTROL_DEFAULTS.enabled;
49
49
  const maxTries = normalizeTryLimit(itemPolicy?.maxTries) ??
50
50
  normalizeTryLimit(sectionPolicy?.maxTries) ??
51
- FORMATIVE_POLICY_DEFAULTS.maxTries;
51
+ ITEM_SESSION_CONTROL_DEFAULTS.maxTries;
52
52
  const feedback = normalizeFeedback(itemPolicy?.feedback) ??
53
53
  normalizeFeedback(sectionPolicy?.feedback) ??
54
- FORMATIVE_POLICY_DEFAULTS.feedback;
54
+ ITEM_SESSION_CONTROL_DEFAULTS.feedback;
55
55
  const requestedRevealOn = normalizeRevealTiming(itemPolicy?.revealOn) ??
56
56
  normalizeRevealTiming(sectionPolicy?.revealOn) ??
57
- FORMATIVE_POLICY_DEFAULTS.revealOn;
57
+ ITEM_SESSION_CONTROL_DEFAULTS.revealOn;
58
58
  // `"on-final-try"` has no referent under unlimited Tries — there is no final
59
59
  // Try to reveal on — so it resolves to the only reading that keeps feedback
60
60
  // reachable.
@@ -68,19 +68,19 @@ export function resolveFormativePolicy(sectionPolicy, itemPolicy) {
68
68
  *
69
69
  * Keyed by the identifier the caller supplies, which in the section runtime is
70
70
  * the canonical item id — the same key `itemSessions` and the completion map
71
- * use, so a formative state and an item session are always addressable by one
71
+ * use, so Try state and an item session are always addressable by one
72
72
  * id.
73
73
  */
74
- export function resolveFormativePolicies(args) {
74
+ export function resolveItemSessionControls(args) {
75
75
  const resolved = {};
76
76
  for (const item of args.items) {
77
77
  if (!item?.identifier)
78
78
  continue;
79
- resolved[item.identifier] = resolveFormativePolicy(args.sectionPolicy, item.policy);
79
+ resolved[item.identifier] = resolveItemSessionControl(args.sectionPolicy, item.policy);
80
80
  }
81
81
  return resolved;
82
82
  }
83
- /** True when at least one item in the section delivers formatively. */
84
- export function isFormativeSectionEnabled(policies) {
83
+ /** True when at least one item in the section enables item session control. */
84
+ export function isItemSessionControlEnabledInSection(policies) {
85
85
  return Object.values(policies).some((policy) => policy.enabled);
86
86
  }
@@ -0,0 +1,19 @@
1
+ import type { ItemTryState, TrySectionSlice } from "./types.js";
2
+ export declare const TRY_SLICE_VERSION: 1;
3
+ export declare function toTrySectionSlice(states: Record<string, ItemTryState>): TrySectionSlice;
4
+ /**
5
+ * Validate a persisted slice against the section it is being restored into.
6
+ *
7
+ * Returns `null` for a slice this build cannot read, which the caller treats as
8
+ * "start Try state clean". Rejection is deliberately narrow: it discards
9
+ * Try progress and nothing else, so a version bump here never costs a
10
+ * learner their responses — item sessions travel in the same snapshot and are
11
+ * normalized separately.
12
+ *
13
+ * An absent slice is `null` too, and indistinguishable from a save made
14
+ * without Try state, which is what keeps existing snapshots valid.
15
+ */
16
+ export declare function normalizeTrySectionSlice(args: {
17
+ slice: unknown;
18
+ allowedItemIdentifiers: readonly string[];
19
+ }): Record<string, ItemTryState> | null;
@@ -1,5 +1,5 @@
1
- import { FORMATIVE_ITEM_STATE_VERSION } from "./state.js";
2
- export const FORMATIVE_SLICE_VERSION = 1;
1
+ import { ITEM_TRY_STATE_VERSION } from "./state.js";
2
+ export const TRY_SLICE_VERSION = 1;
3
3
  const CORRECTNESS_VALUES = [
4
4
  "correct",
5
5
  "partial",
@@ -54,14 +54,14 @@ function normalizeItemState(itemIdentifier, value) {
54
54
  if (!value || typeof value !== "object")
55
55
  return null;
56
56
  const raw = value;
57
- if (raw.version !== FORMATIVE_ITEM_STATE_VERSION)
57
+ if (raw.version !== ITEM_TRY_STATE_VERSION)
58
58
  return null;
59
59
  const tryCount = readNonNegativeInteger(raw.tryCount);
60
60
  if (tryCount === undefined)
61
61
  return null;
62
62
  const firstCorrectTry = readNonNegativeInteger(raw.firstCorrectTry);
63
63
  return {
64
- version: FORMATIVE_ITEM_STATE_VERSION,
64
+ version: ITEM_TRY_STATE_VERSION,
65
65
  itemIdentifier,
66
66
  tryCount,
67
67
  revealed: raw.revealed === true,
@@ -72,9 +72,9 @@ function normalizeItemState(itemIdentifier, value) {
72
72
  revealOverride: normalizeRevealOverride(raw.revealOverride),
73
73
  };
74
74
  }
75
- export function toFormativeSectionSlice(states) {
75
+ export function toTrySectionSlice(states) {
76
76
  return {
77
- version: FORMATIVE_SLICE_VERSION,
77
+ version: TRY_SLICE_VERSION,
78
78
  items: { ...states },
79
79
  };
80
80
  }
@@ -82,20 +82,20 @@ export function toFormativeSectionSlice(states) {
82
82
  * Validate a persisted slice against the section it is being restored into.
83
83
  *
84
84
  * Returns `null` for a slice this build cannot read, which the caller treats as
85
- * "start formative state clean". Rejection is deliberately narrow: it discards
86
- * formative progress and nothing else, so a version bump here never costs a
85
+ * "start Try state clean". Rejection is deliberately narrow: it discards
86
+ * Try progress and nothing else, so a version bump here never costs a
87
87
  * learner their responses — item sessions travel in the same snapshot and are
88
88
  * normalized separately.
89
89
  *
90
- * An absent slice is `null` too, and indistinguishable from a pre-formative
91
- * save, which is what keeps existing snapshots valid.
90
+ * An absent slice is `null` too, and indistinguishable from a save made
91
+ * without Try state, which is what keeps existing snapshots valid.
92
92
  */
93
- export function normalizeFormativeSectionSlice(args) {
93
+ export function normalizeTrySectionSlice(args) {
94
94
  const { slice } = args;
95
95
  if (!slice || typeof slice !== "object")
96
96
  return null;
97
97
  const raw = slice;
98
- if (raw.version !== FORMATIVE_SLICE_VERSION)
98
+ if (raw.version !== TRY_SLICE_VERSION)
99
99
  return null;
100
100
  if (!raw.items || typeof raw.items !== "object")
101
101
  return null;
@@ -1,6 +1,6 @@
1
- import type { FormativeFeedbackReveal, FormativeItemState, FormativeItemView, FormativeTryOutcome, ResolvedFormativePolicy } from "./types.js";
2
- export declare const FORMATIVE_ITEM_STATE_VERSION: 1;
3
- export declare function createFormativeItemState(itemIdentifier: string): FormativeItemState;
1
+ import type { FeedbackReveal, ItemTryState, ItemTryView, TryOutcome, ResolvedItemSessionControlPolicy } from "./types.js";
2
+ export declare const ITEM_TRY_STATE_VERSION: 1;
3
+ export declare function createItemTryState(itemIdentifier: string): ItemTryState;
4
4
  /**
5
5
  * Record one Try.
6
6
  *
@@ -8,24 +8,24 @@ export declare function createFormativeItemState(itemIdentifier: string): Format
8
8
  * state can be checked, so a second click landing before the projection
9
9
  * round-trips is dropped rather than spending a Try.
10
10
  */
11
- export declare function recordFormativeTry(args: {
12
- state?: FormativeItemState;
11
+ export declare function recordTry(args: {
12
+ state?: ItemTryState;
13
13
  itemIdentifier: string;
14
- policy: ResolvedFormativePolicy;
15
- outcome: FormativeTryOutcome;
16
- }): FormativeItemState;
14
+ policy: ResolvedItemSessionControlPolicy;
15
+ outcome: TryOutcome;
16
+ }): ItemTryState;
17
17
  /**
18
18
  * Dismiss a reveal and reopen the item for editing. Withdrawing `revealed` is
19
19
  * what withdraws the env override, so the item returns to the section's own
20
20
  * mode.
21
21
  *
22
22
  * A learner action, so it respects the Try budget: an item with none left keeps
23
- * its feedback on screen. `hideFormativeItem` is the host-authority version.
23
+ * its feedback on screen. `hideItemFeedback` is the host-authority version.
24
24
  */
25
- export declare function retryFormativeItem(args: {
26
- state?: FormativeItemState;
27
- policy: ResolvedFormativePolicy;
28
- }): FormativeItemState | undefined;
25
+ export declare function retryItem(args: {
26
+ state?: ItemTryState;
27
+ policy: ResolvedItemSessionControlPolicy;
28
+ }): ItemTryState | undefined;
29
29
  /**
30
30
  * Reveal on host authority — a teacher-driven "show the answer".
31
31
  *
@@ -38,23 +38,23 @@ export declare function retryFormativeItem(args: {
38
38
  * whatever response is there, including none, which is what "show the answer"
39
39
  * means before the learner has answered.
40
40
  */
41
- export declare function revealFormativeItem(args: {
42
- state?: FormativeItemState;
41
+ export declare function revealItemFeedback(args: {
42
+ state?: ItemTryState;
43
43
  itemIdentifier: string;
44
- policy: ResolvedFormativePolicy;
45
- feedback: Exclude<FormativeFeedbackReveal, "none">;
46
- }): FormativeItemState;
44
+ policy: ResolvedItemSessionControlPolicy;
45
+ feedback: Exclude<FeedbackReveal, "none">;
46
+ }): ItemTryState;
47
47
  /**
48
48
  * Withdraw a reveal on host authority. Unlike a learner retry this ignores the
49
49
  * Try budget: a host that revealed an item with no Tries left must be able to
50
50
  * put it back.
51
51
  */
52
- export declare function hideFormativeItem(args: {
53
- state?: FormativeItemState;
54
- policy: ResolvedFormativePolicy;
55
- }): FormativeItemState | undefined;
52
+ export declare function hideItemFeedback(args: {
53
+ state?: ItemTryState;
54
+ policy: ResolvedItemSessionControlPolicy;
55
+ }): ItemTryState | undefined;
56
56
  /** Derive everything a rendering component needs from policy plus state. */
57
- export declare function resolveFormativeItemView(args: {
58
- policy: ResolvedFormativePolicy;
59
- state?: FormativeItemState;
60
- }): FormativeItemView;
57
+ export declare function resolveItemTryView(args: {
58
+ policy: ResolvedItemSessionControlPolicy;
59
+ state?: ItemTryState;
60
+ }): ItemTryView;
@@ -1,7 +1,7 @@
1
- export const FORMATIVE_ITEM_STATE_VERSION = 1;
2
- export function createFormativeItemState(itemIdentifier) {
1
+ export const ITEM_TRY_STATE_VERSION = 1;
2
+ export function createItemTryState(itemIdentifier) {
3
3
  return {
4
- version: FORMATIVE_ITEM_STATE_VERSION,
4
+ version: ITEM_TRY_STATE_VERSION,
5
5
  itemIdentifier,
6
6
  tryCount: 0,
7
7
  revealed: false,
@@ -20,7 +20,7 @@ function shouldReveal(policy, tryCount) {
20
20
  return false;
21
21
  if (policy.revealOn === "on-try")
22
22
  return true;
23
- // Defensive: `resolveFormativePolicy` already coerces `"on-final-try"` to
23
+ // Defensive: `resolveItemSessionControl` already coerces `"on-final-try"` to
24
24
  // `"on-try"` under unlimited Tries, so this branch only guards a
25
25
  // hand-constructed policy.
26
26
  if (policy.maxTries === "unlimited")
@@ -34,8 +34,8 @@ function shouldReveal(policy, tryCount) {
34
34
  * state can be checked, so a second click landing before the projection
35
35
  * round-trips is dropped rather than spending a Try.
36
36
  */
37
- export function recordFormativeTry(args) {
38
- const current = args.state ?? createFormativeItemState(args.itemIdentifier);
37
+ export function recordTry(args) {
38
+ const current = args.state ?? createItemTryState(args.itemIdentifier);
39
39
  if (!args.policy.enabled)
40
40
  return current;
41
41
  const remaining = triesRemaining(args.policy, current.tryCount);
@@ -44,7 +44,7 @@ export function recordFormativeTry(args) {
44
44
  const tryCount = current.tryCount + 1;
45
45
  return {
46
46
  ...current,
47
- version: FORMATIVE_ITEM_STATE_VERSION,
47
+ version: ITEM_TRY_STATE_VERSION,
48
48
  itemIdentifier: args.itemIdentifier,
49
49
  tryCount,
50
50
  revealed: shouldReveal(args.policy, tryCount),
@@ -62,9 +62,9 @@ export function recordFormativeTry(args) {
62
62
  * mode.
63
63
  *
64
64
  * A learner action, so it respects the Try budget: an item with none left keeps
65
- * its feedback on screen. `hideFormativeItem` is the host-authority version.
65
+ * its feedback on screen. `hideItemFeedback` is the host-authority version.
66
66
  */
67
- export function retryFormativeItem(args) {
67
+ export function retryItem(args) {
68
68
  const current = args.state;
69
69
  if (!current || !args.policy.enabled)
70
70
  return current;
@@ -87,8 +87,8 @@ export function retryFormativeItem(args) {
87
87
  * whatever response is there, including none, which is what "show the answer"
88
88
  * means before the learner has answered.
89
89
  */
90
- export function revealFormativeItem(args) {
91
- const current = args.state ?? createFormativeItemState(args.itemIdentifier);
90
+ export function revealItemFeedback(args) {
91
+ const current = args.state ?? createItemTryState(args.itemIdentifier);
92
92
  if (!args.policy.enabled)
93
93
  return current;
94
94
  if (current.revealed && current.revealOverride === args.feedback) {
@@ -101,7 +101,7 @@ export function revealFormativeItem(args) {
101
101
  * Try budget: a host that revealed an item with no Tries left must be able to
102
102
  * put it back.
103
103
  */
104
- export function hideFormativeItem(args) {
104
+ export function hideItemFeedback(args) {
105
105
  const current = args.state;
106
106
  if (!current || !args.policy.enabled)
107
107
  return current;
@@ -119,7 +119,7 @@ function envOverrideFor(policy, revealed, revealOverride) {
119
119
  };
120
120
  }
121
121
  /** Derive everything a rendering component needs from policy plus state. */
122
- export function resolveFormativeItemView(args) {
122
+ export function resolveItemTryView(args) {
123
123
  const { policy } = args;
124
124
  const tryCount = args.state?.tryCount ?? 0;
125
125
  const revealed = args.state?.revealed === true;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Formative delivery vocabulary.
2
+ * Item session control vocabulary.
3
3
  *
4
4
  * A **Try** is one submitted-for-checking pass over a single item. It is not
5
5
  * `TestAttemptSession` (the assessment administration) and not
@@ -7,11 +7,11 @@
7
7
  * an item). Both of those already own the word "attempt" in this codebase, at
8
8
  * two different scopes, which is why this contract does not use it.
9
9
  *
10
- * See `docs/prds/formative-delivery-contract.md` for the ratified contract and
10
+ * See `docs/prds/item-session-control-contract.md` for the ratified contract and
11
11
  * its QTI 3 mapping.
12
12
  */
13
13
  /** How many Tries a learner gets on one item. */
14
- export type FormativeTryLimit = number | "unlimited";
14
+ export type TryLimit = number | "unlimited";
15
15
  /**
16
16
  * What the learner sees after a Try.
17
17
  *
@@ -20,54 +20,52 @@ export type FormativeTryLimit = number | "unlimited";
20
20
  * `role: "instructor"`, which is the element convention for additionally
21
21
  * revealing the authored correct response.
22
22
  */
23
- export type FormativeFeedbackReveal = "none" | "correctness" | "solution";
23
+ export type FeedbackReveal = "none" | "correctness" | "solution";
24
24
  /** When a reveal happens: after every Try, or only once Tries are spent. */
25
- export type FormativeRevealTiming = "on-try" | "on-final-try";
25
+ export type FeedbackRevealTiming = "on-try" | "on-final-try";
26
26
  /**
27
- * Authored policy. Lives on `AssessmentSection.formative` as the section
28
- * default and on `AssessmentItemRef.formative` as a per-item override.
27
+ * Authored policy. Lives on `AssessmentSection.itemSessionControl` as the section
28
+ * default and on `AssessmentItemRef.itemSessionControl` as a per-item override.
29
29
  */
30
- export interface FormativeDeliveryPolicy {
30
+ export interface ItemSessionControlPolicy {
31
31
  /**
32
32
  * Absent or `false` leaves delivery exactly as it is without this contract:
33
33
  * no control, no projection, no state.
34
34
  */
35
35
  enabled?: boolean;
36
36
  /** Default 1. */
37
- maxTries?: FormativeTryLimit;
37
+ maxTries?: TryLimit;
38
38
  /** Default `"correctness"`. */
39
- feedback?: FormativeFeedbackReveal;
39
+ feedback?: FeedbackReveal;
40
40
  /** Default `"on-try"`. */
41
- revealOn?: FormativeRevealTiming;
41
+ revealOn?: FeedbackRevealTiming;
42
42
  }
43
- /** Same shape as the section policy; every field overrides independently. */
44
- export type FormativeItemPolicy = FormativeDeliveryPolicy;
45
43
  /** The policy after built-in defaults, section, and item ref are merged. */
46
- export interface ResolvedFormativePolicy {
44
+ export interface ResolvedItemSessionControlPolicy {
47
45
  enabled: boolean;
48
- maxTries: FormativeTryLimit;
49
- feedback: FormativeFeedbackReveal;
50
- revealOn: FormativeRevealTiming;
46
+ maxTries: TryLimit;
47
+ feedback: FeedbackReveal;
48
+ revealOn: FeedbackRevealTiming;
51
49
  }
52
50
  /**
53
51
  * `"unknown"` is the honest state for an item no loaded controller can score —
54
52
  * one holding a rubric element, or one whose element bundle exposes no
55
53
  * `outcome`. It is never collapsed into `"incorrect"`.
56
54
  */
57
- export type FormativeCorrectness = "correct" | "partial" | "incorrect" | "unknown";
55
+ export type TryCorrectness = "correct" | "partial" | "incorrect" | "unknown";
58
56
  /**
59
57
  * The subset of an element outcome that aggregation reads. `OutcomeResponse` is
60
58
  * assignable to this; the structural type keeps this module free of a
61
59
  * dependency on the entity types, which import policy types from here.
62
60
  */
63
- export interface FormativeScoredOutcome {
61
+ export interface ScoredElementOutcome {
64
62
  score?: number;
65
63
  max?: number;
66
64
  [key: string]: unknown;
67
65
  }
68
66
  /** The recorded result of one Try. */
69
- export interface FormativeTryOutcome {
70
- correctness: FormativeCorrectness;
67
+ export interface TryOutcome {
68
+ correctness: TryCorrectness;
71
69
  points?: number;
72
70
  max?: number;
73
71
  scoredElementCount: number;
@@ -84,10 +82,10 @@ export interface FormativeTryOutcome {
84
82
  * A trade: this persists inside the session slice, so a snapshot grows by
85
83
  * whatever the elements put in their outcomes — some include a scoring trace.
86
84
  */
87
- elementOutcomes?: FormativeScoredOutcome[];
85
+ elementOutcomes?: ScoredElementOutcome[];
88
86
  }
89
- /** Per-item delivery state. Persisted; see `FormativeSectionSlice`. */
90
- export interface FormativeItemState {
87
+ /** Per-item delivery state. Persisted; see `TrySectionSlice`. */
88
+ export interface ItemTryState {
91
89
  version: 1;
92
90
  itemIdentifier: string;
93
91
  tryCount: number;
@@ -98,7 +96,7 @@ export interface FormativeItemState {
98
96
  * reload restores the screen the learner left.
99
97
  */
100
98
  revealed: boolean;
101
- lastOutcome?: FormativeTryOutcome;
99
+ lastOutcome?: TryOutcome;
102
100
  /** 1-based Try on which the item first scored full credit. Never overwritten. */
103
101
  firstCorrectTry?: number;
104
102
  /**
@@ -106,19 +104,19 @@ export interface FormativeItemState {
106
104
  * than the learner earning it. Overrides the policy's `feedback` for the env
107
105
  * projection only — the policy still decides Tries.
108
106
  *
109
- * Set by `revealFormativeItem`, cleared by `hideFormativeItem` and by a
107
+ * Set by `revealItemFeedback`, cleared by `hideItemFeedback` and by a
110
108
  * learner retry, so a teacher-driven "show the answer" does not silently
111
109
  * upgrade every later reveal on that item.
112
110
  */
113
- revealOverride?: FormativeFeedbackReveal;
111
+ revealOverride?: FeedbackReveal;
114
112
  }
115
- /** The persisted slice, carried on `SectionControllerSessionState.formative`. */
116
- export interface FormativeSectionSlice {
113
+ /** The persisted slice, carried on `SectionControllerSessionState.tries`. */
114
+ export interface TrySectionSlice {
117
115
  version: 1;
118
- items: Record<string, FormativeItemState>;
116
+ items: Record<string, ItemTryState>;
119
117
  }
120
118
  /** Projected over the section env for one revealed item. */
121
- export interface FormativeEnvOverride {
119
+ export interface FeedbackEnvOverride {
122
120
  mode: "evaluate";
123
121
  role: "student" | "instructor";
124
122
  }
@@ -126,18 +124,18 @@ export interface FormativeEnvOverride {
126
124
  * What a rendering component needs, derived once so no component reimplements
127
125
  * the predicate. Carries no copy — wording belongs to the rendering package.
128
126
  */
129
- export interface FormativeItemView {
127
+ export interface ItemTryView {
130
128
  enabled: boolean;
131
129
  tryCount: number;
132
130
  triesRemaining: number | "unlimited";
133
131
  canCheck: boolean;
134
132
  canRetry: boolean;
135
133
  revealed: boolean;
136
- envOverride?: FormativeEnvOverride;
137
- lastOutcome?: FormativeTryOutcome;
134
+ envOverride?: FeedbackEnvOverride;
135
+ lastOutcome?: TryOutcome;
138
136
  }
139
137
  /** Section rollup over Try outcomes. */
140
- export interface FormativeMasteryRollup {
138
+ export interface MasteryRollup {
141
139
  version: 1;
142
140
  totalItems: number;
143
141
  /**
@@ -153,12 +151,12 @@ export interface FormativeMasteryRollup {
153
151
  complete: boolean;
154
152
  }
155
153
  /** The composition-model projection layouts read. */
156
- export interface FormativeSectionProjection {
154
+ export interface TrySectionProjection {
157
155
  version: 1;
158
156
  enabled: boolean;
159
- policies: Record<string, ResolvedFormativePolicy>;
160
- states: Record<string, FormativeItemState>;
161
- mastery: FormativeMasteryRollup;
157
+ policies: Record<string, ResolvedItemSessionControlPolicy>;
158
+ states: Record<string, ItemTryState>;
159
+ mastery: MasteryRollup;
162
160
  }
163
161
  /** The two learner actions a rendering component can request. */
164
- export type FormativeAction = "check" | "retry";
162
+ export type TryAction = "check" | "retry";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Formative delivery vocabulary.
2
+ * Item session control vocabulary.
3
3
  *
4
4
  * A **Try** is one submitted-for-checking pass over a single item. It is not
5
5
  * `TestAttemptSession` (the assessment administration) and not
@@ -7,7 +7,7 @@
7
7
  * an item). Both of those already own the word "attempt" in this codebase, at
8
8
  * two different scopes, which is why this contract does not use it.
9
9
  *
10
- * See `docs/prds/formative-delivery-contract.md` for the ratified contract and
10
+ * See `docs/prds/item-session-control-contract.md` for the ratified contract and
11
11
  * its QTI 3 mapping.
12
12
  */
13
13
  export {};
@@ -5,7 +5,7 @@
5
5
  * Pure apart from one adapter factory that is inert until called, so the contract
6
6
  * is testable without a browser and an adapter can import it without pulling in a
7
7
  * player. Live cue state belongs to `SectionController` in
8
- * `@pie-players/pie-section-player`, which is where the equivalent formative state
8
+ * `@pie-players/pie-section-player`, which is where the equivalent Try state
9
9
  * lives — one rollup over one item set, in one package.
10
10
  *
11
11
  * Contract: `docs/prds/timed-media-section-contract.md`.
@@ -5,7 +5,7 @@
5
5
  * Pure apart from one adapter factory that is inert until called, so the contract
6
6
  * is testable without a browser and an adapter can import it without pulling in a
7
7
  * player. Live cue state belongs to `SectionController` in
8
- * `@pie-players/pie-section-player`, which is where the equivalent formative state
8
+ * `@pie-players/pie-section-player`, which is where the equivalent Try state
9
9
  * lives — one rollup over one item set, in one package.
10
10
  *
11
11
  * Contract: `docs/prds/timed-media-section-contract.md`.
@@ -28,9 +28,9 @@ export declare function normalizeTimedMediaSectionData(args: {
28
28
  itemIdentifiers: readonly string[];
29
29
  resolveStimulusRenderableId: (stimulusRef: string) => string | undefined;
30
30
  /**
31
- * The Try budget each item's resolved formative policy leaves, so a gate on
31
+ * The Try budget each item's resolved item session control policy leaves, so a gate on
32
32
  * correctness can be refused where it would strand the learner. Supplied by the
33
- * caller because formative policy resolution is that contract's, not this one's.
33
+ * caller because item session control policy resolution is that contract's, not this one's.
34
34
  */
35
35
  resolveItemTryBudget: (itemIdentifier: string) => TimedMediaItemTryBudget;
36
36
  }): TimedMediaValidationResult;
@@ -240,7 +240,7 @@ export function normalizeTimedMediaSectionData(args) {
240
240
  // A correctness gate is only passable while the learner still has a Try
241
241
  // to spend. Over an item with a finite budget the gate becomes
242
242
  // unpassable the moment the budget runs out, and over an item that does
243
- // not deliver formatively there is never a Try at all — in both cases
243
+ // not enable item session control there is never a Try at all — in both cases
244
244
  // nothing the learner can do releases playback, and no host action
245
245
  // releases it either, because a forced reveal is not a correct answer.
246
246
  // Refused here rather than released at runtime: releasing would deliver
@@ -250,7 +250,7 @@ export function normalizeTimedMediaSectionData(args) {
250
250
  if (unpassable.length > 0) {
251
251
  errors.push({
252
252
  code: "gate-requires-unlimited-tries",
253
- message: `Gate cue "${identifier}" releases on correctness, so every item it names needs formative delivery with maxTries: "unlimited" — otherwise a learner who runs out of Tries can never release playback. Offending item refs: ${unpassable.join(", ")}.`,
253
+ message: `Gate cue "${identifier}" releases on correctness, so every item it names needs item session control with maxTries: "unlimited" — otherwise a learner who runs out of Tries can never release playback. Offending item refs: ${unpassable.join(", ")}.`,
254
254
  cueIdentifier: identifier,
255
255
  });
256
256
  continue;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Persistence for the timed-media slice.
3
3
  *
4
- * Same posture as the formative slice: an unreadable slice is rejected whole and
4
+ * Same posture as the Try slice: an unreadable slice is rejected whole and
5
5
  * delivery starts from clean cue state, while item sessions in the same snapshot
6
6
  * are normalized separately and survive. A version bump here must never cost a
7
7
  * learner their responses.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Persistence for the timed-media slice.
3
3
  *
4
- * Same posture as the formative slice: an unreadable slice is rejected whole and
4
+ * Same posture as the Try slice: an unreadable slice is rejected whole and
5
5
  * delivery starts from clean cue state, while item sessions in the same snapshot
6
6
  * are normalized separately and survive. A version bump here must never cost a
7
7
  * learner their responses.
@@ -9,7 +9,7 @@
9
9
  * See `docs/prds/timed-media-section-contract.md` for the ratified contract and
10
10
  * `docs/architecture/timed-media-section.md` for the layer-ownership record.
11
11
  */
12
- import type { FormativeCorrectness } from "../formative/types.js";
12
+ import type { TryCorrectness } from "../item-session-control/types.js";
13
13
  import type { MediaFragmentRange } from "../types/index.js";
14
14
  /**
15
15
  * What a cue does to the items it names.
@@ -23,15 +23,15 @@ export type TimedMediaCueActivation = "reveal" | "gate" | "metadata";
23
23
  /**
24
24
  * The condition over delivery state that releases a gate.
25
25
  *
26
- * Named from the shipped formative vocabulary rather than defined here, which is
27
- * the whole reason formative delivery sequenced first — see
26
+ * Named from the shipped Try vocabulary rather than defined here, which is
27
+ * the whole reason item session control sequenced first — see
28
28
  * `docs/adr/0001-formative-delivery-before-timed-media.md`. `"responded"` is the
29
29
  * response-only case, and it reads item completion rather than Try state so a
30
- * gate works in a section that does not deliver formatively at all.
30
+ * gate works in a section that does not enable item session control at all.
31
31
  */
32
32
  export type TimedMediaGateCondition = "responded" | "correct" | "partial-or-better";
33
33
  /**
34
- * What a gate does when the item cannot be auto-scored — `FormativeCorrectness`
34
+ * What a gate does when the item cannot be auto-scored — `TryCorrectness`
35
35
  * of `"unknown"`, which is the honest state of an item holding a rubric element
36
36
  * rather than a defect to design around.
37
37
  *
@@ -131,13 +131,13 @@ export interface ResolvedTimedMediaSectionData {
131
131
  scoringPolicy?: TimedMediaScoringPolicy;
132
132
  }
133
133
  /**
134
- * What a gated item's resolved formative policy leaves the learner.
134
+ * What a gated item's resolved item session control policy leaves the learner.
135
135
  *
136
136
  * Read by validation, not by the reduction: a gate on correctness over an item
137
137
  * with a finite Try budget is a dead end, and so is one over an item that does not
138
- * deliver formatively at all — no Try means no correctness, ever.
138
+ * enable item session control at all — no Try means no correctness, ever.
139
139
  */
140
- export type TimedMediaItemTryBudget = "unlimited" | "finite" | "not-formative";
140
+ export type TimedMediaItemTryBudget = "unlimited" | "finite" | "none";
141
141
  export interface TimedMediaValidationError {
142
142
  /** Machine-readable, so a host can branch without parsing prose. */
143
143
  code: "missing-stimulus-ref" | "unresolved-stimulus-ref"
@@ -219,7 +219,7 @@ export interface MediaTimeSource {
219
219
  /**
220
220
  * The persisted slice, carried on `SectionControllerSessionState.timedMedia`.
221
221
  *
222
- * One shape live and persisted, unlike formative's split between a per-item
222
+ * One shape live and persisted, unlike the Try state's split between a per-item
223
223
  * reducer state and a slice: timed-media state is section-scoped, so there is
224
224
  * nothing to key.
225
225
  */
@@ -241,14 +241,14 @@ export interface TimedMediaSectionSessionSlice {
241
241
  }
242
242
  /**
243
243
  * Delivery state a gate condition reads, supplied by the caller so this module
244
- * stays pure and depends on neither the controller nor the formative module's
244
+ * stays pure and depends on neither the controller nor the item session control module's
245
245
  * live state.
246
246
  */
247
247
  export interface TimedMediaDeliveryState {
248
248
  /** Item completion by canonical id: the `"responded"` condition's signal. */
249
249
  respondedByItemId: Record<string, boolean>;
250
250
  /** Last Try correctness by canonical id; absent means no Try yet. */
251
- correctnessByItemId: Record<string, FormativeCorrectness | undefined>;
251
+ correctnessByItemId: Record<string, TryCorrectness | undefined>;
252
252
  /** Whether every item in the section reports completion. */
253
253
  itemsComplete: boolean;
254
254
  }
@@ -8,10 +8,10 @@
8
8
  * them. The module depends on nothing beyond this package's own type modules,
9
9
  * so front-end and server code can both import it.
10
10
  */
11
- import type { FormativeDeliveryPolicy, FormativeItemPolicy, FormativeSectionSlice } from "../formative/types.js";
11
+ import type { ItemSessionControlPolicy, TrySectionSlice } from "../item-session-control/types.js";
12
12
  import type { TimedMediaSectionData, TimedMediaSectionSessionSlice } from "../timed-media/types.js";
13
13
  import type { AdaptationLink, CurriculumStandard, LanguagePolicy, SkillLevel } from "./interchange.js";
14
- export type { FormativeDeliveryPolicy, FormativeItemPolicy };
14
+ export type { ItemSessionControlPolicy };
15
15
  export type { TimedMediaSectionData };
16
16
  export type { AdaptationLink, ContentSubstitution, CurriculumStandard, LanguagePolicy, ServedContentIdentity, SkillLevel, } from "./interchange.js";
17
17
  export interface BaseEntity {
@@ -175,13 +175,13 @@ export interface AssessmentItemRef extends BaseEntity {
175
175
  /** Item-level settings for tool requirements and customization */
176
176
  settings?: ItemSettings;
177
177
  /**
178
- * Formative delivery override for this item. Every field overrides the
179
- * section's `formative` policy independently, which is the order QTI 3 uses
178
+ * Item session control override for this item. Every field overrides the
179
+ * section's `itemSessionControl` policy independently, which is the order QTI 3 uses
180
180
  * for `qti-item-session-control` on an item ref versus its section.
181
181
  *
182
- * See `docs/prds/formative-delivery-contract.md`.
182
+ * See `docs/prds/item-session-control-contract.md`.
183
183
  */
184
- formative?: FormativeItemPolicy;
184
+ itemSessionControl?: ItemSessionControlPolicy;
185
185
  }
186
186
  export interface AssessmentSection extends BaseEntity {
187
187
  id?: string;
@@ -208,15 +208,15 @@ export interface AssessmentSection extends BaseEntity {
208
208
  assessmentItemRefs?: AssessmentItemRef[];
209
209
  rubricBlocks?: RubricBlock[];
210
210
  /**
211
- * Formative delivery policy for this section: how many Tries a learner gets
211
+ * Item session control policy for this section: how many Tries a learner gets
212
212
  * on each item, when feedback is revealed, and whether the behavior is on at
213
213
  * all. Absent or `enabled: false` delivers exactly as a section without this
214
214
  * field — no control, no state, no env override.
215
215
  *
216
- * Individual items override it through `AssessmentItemRef.formative`. See
217
- * `docs/prds/formative-delivery-contract.md`.
216
+ * Individual items override it through `AssessmentItemRef.itemSessionControl`. See
217
+ * `docs/prds/item-session-control-contract.md`.
218
218
  */
219
- formative?: FormativeDeliveryPolicy;
219
+ itemSessionControl?: ItemSessionControlPolicy;
220
220
  /**
221
221
  * The section flavor. Absent for every section authored so far, and the only
222
222
  * value is `"timed-media"`, which pairs with `timedMedia` below.
@@ -258,19 +258,19 @@ export interface SectionControllerSessionState {
258
258
  visitedItemIdentifiers?: string[];
259
259
  itemSessions: Record<string, unknown>;
260
260
  /**
261
- * Formative delivery state — Try counts and reveal state per item.
261
+ * Try state — Try counts and reveal state per item.
262
262
  *
263
263
  * Optional, and its absence is indistinguishable from a snapshot saved
264
- * before formative delivery existed, which is what keeps existing persisted
264
+ * before item session control existed, which is what keeps existing persisted
265
265
  * sessions valid. A slice with an unrecognized `version` is rejected whole
266
- * and formative state starts clean; `itemSessions` in the same snapshot is
267
- * unaffected, so a formative version bump never costs a learner responses.
266
+ * and Try state starts clean; `itemSessions` in the same snapshot is
267
+ * unaffected, so a Try slice version bump never costs a learner responses.
268
268
  */
269
- formative?: FormativeSectionSlice;
269
+ tries?: TrySectionSlice;
270
270
  /**
271
271
  * Timed-media delivery state — media progress, cue visits and completions.
272
272
  *
273
- * Same posture as `formative`: optional, absence indistinguishable from a
273
+ * Same posture as `tries`: optional, absence indistinguishable from a
274
274
  * pre-timed-media snapshot, and an unrecognized `version` rejected whole so
275
275
  * cue progress restarts while item sessions in the same snapshot are applied
276
276
  * untouched.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-players-shared",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0-beta.9",
4
4
  "type": "module",
5
5
  "description": "Shared runtime + UI utilities for PIE players",
6
6
  "license": "MIT",
@@ -45,9 +45,9 @@
45
45
  "types": "./dist/types/index.d.ts",
46
46
  "import": "./dist/types/index.js"
47
47
  },
48
- "./formative": {
49
- "types": "./dist/formative/index.d.ts",
50
- "import": "./dist/formative/index.js"
48
+ "./item-session-control": {
49
+ "types": "./dist/item-session-control/index.d.ts",
50
+ "import": "./dist/item-session-control/index.js"
51
51
  },
52
52
  "./timed-media": {
53
53
  "types": "./dist/timed-media/index.d.ts",
@@ -1,16 +0,0 @@
1
- /**
2
- * Formative delivery: Try state, feedback reveal, and section mastery.
3
- *
4
- * Pure and Node-safe by design — no DOM, no timers, no element registry — so
5
- * the contract is testable without a browser and an adapter can import it
6
- * without pulling in a player. Everything that touches the DOM stays in
7
- * `@pie-players/pie-section-player`.
8
- *
9
- * Contract: `docs/prds/formative-delivery-contract.md`.
10
- */
11
- export type { FormativeAction, FormativeCorrectness, FormativeDeliveryPolicy, FormativeEnvOverride, FormativeFeedbackReveal, FormativeItemPolicy, FormativeItemState, FormativeItemView, FormativeMasteryRollup, FormativeRevealTiming, FormativeScoredOutcome, FormativeSectionProjection, FormativeSectionSlice, FormativeTryLimit, FormativeTryOutcome, ResolvedFormativePolicy, } from "./types.js";
12
- export { FORMATIVE_POLICY_DEFAULTS, isFormativeSectionEnabled, resolveFormativePolicies, resolveFormativePolicy, } from "./policy.js";
13
- export { aggregateFormativeOutcome } from "./outcome.js";
14
- export { FORMATIVE_ITEM_STATE_VERSION, createFormativeItemState, hideFormativeItem, recordFormativeTry, resolveFormativeItemView, retryFormativeItem, revealFormativeItem, } from "./state.js";
15
- export { FORMATIVE_MASTERY_VERSION, rollupFormativeMastery } from "./mastery.js";
16
- export { FORMATIVE_SLICE_VERSION, normalizeFormativeSectionSlice, toFormativeSectionSlice, } from "./session.js";
@@ -1,5 +0,0 @@
1
- export { FORMATIVE_POLICY_DEFAULTS, isFormativeSectionEnabled, resolveFormativePolicies, resolveFormativePolicy, } from "./policy.js";
2
- export { aggregateFormativeOutcome } from "./outcome.js";
3
- export { FORMATIVE_ITEM_STATE_VERSION, createFormativeItemState, hideFormativeItem, recordFormativeTry, resolveFormativeItemView, retryFormativeItem, revealFormativeItem, } from "./state.js";
4
- export { FORMATIVE_MASTERY_VERSION, rollupFormativeMastery } from "./mastery.js";
5
- export { FORMATIVE_SLICE_VERSION, normalizeFormativeSectionSlice, toFormativeSectionSlice, } from "./session.js";
@@ -1,19 +0,0 @@
1
- import type { FormativeItemState, FormativeSectionSlice } from "./types.js";
2
- export declare const FORMATIVE_SLICE_VERSION: 1;
3
- export declare function toFormativeSectionSlice(states: Record<string, FormativeItemState>): FormativeSectionSlice;
4
- /**
5
- * Validate a persisted slice against the section it is being restored into.
6
- *
7
- * Returns `null` for a slice this build cannot read, which the caller treats as
8
- * "start formative state clean". Rejection is deliberately narrow: it discards
9
- * formative progress and nothing else, so a version bump here never costs a
10
- * learner their responses — item sessions travel in the same snapshot and are
11
- * normalized separately.
12
- *
13
- * An absent slice is `null` too, and indistinguishable from a pre-formative
14
- * save, which is what keeps existing snapshots valid.
15
- */
16
- export declare function normalizeFormativeSectionSlice(args: {
17
- slice: unknown;
18
- allowedItemIdentifiers: readonly string[];
19
- }): Record<string, FormativeItemState> | null;