@conform-ed/qti-react 0.0.15 → 0.0.17

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.
@@ -11,18 +11,20 @@
11
11
  * - `gapChoices` split into the runtime's `gapTexts` (gapMatch) / `gapImgs` (graphic)
12
12
  * - media/upload/positionObjectStage flatten to the descriptor fields
13
13
  * - processing trees: `children` → `expressions`, `actions` → `rules`,
14
- * `responseElseIf`/`templateElseIf` pluralize
14
+ * `responseElseIf`/`templateElseIf` pluralize; fragment rules keep their
15
+ * nested `rules` verbatim
15
16
  *
16
17
  * Used by the corpus delivery meter (ADR-0002) and by any consumer ingesting
17
18
  * normalized XML.
18
19
  */
19
- import type { AssessmentItemView } from "./runtime";
20
+ import type { AssessmentItemView, StimulusContentView } from "./runtime";
20
21
  import type { AssessmentTestView } from "./test";
22
+ export declare function assessmentItemViewFromNormalized(document: unknown): AssessmentItemView | null;
21
23
  /**
22
- * Reshape a normalized QTI document (the `normalizedDocument` from qti-xml validation)
23
- * into an `AssessmentItemView`, or null when it is not an assessment item.
24
+ * The renderable body of a normalized AssessmentStimulus document, for
25
+ * `QtiRuntimeConfig.resolveStimulus`. Returns null for non-stimulus documents.
24
26
  */
25
- export declare function assessmentItemViewFromNormalized(document: unknown): AssessmentItemView | null;
27
+ export declare function stimulusContentFromNormalized(document: unknown): StimulusContentView | null;
26
28
  /**
27
29
  * Reshape a normalized QTI document into the Test Controller's `AssessmentTestView`,
28
30
  * or null when it is not an assessment test.
package/dist/pnp.d.ts ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * AfA PNP (QTI 3.0 profile) views and catalog support resolution.
3
+ *
4
+ * The catalog holds "support-specific dormant content that can be made active … based
5
+ * on the candidate's PNP information (or an assessment program's settings)" (§5.28).
6
+ * This module owns the two halves of that sentence: which supports are active for a
7
+ * candidate (activation), and which card content realises an active support
8
+ * (matching). Rendering is the runtime's job; time-limit adjustments are the test
9
+ * controller's.
10
+ *
11
+ * Views are structural mirrors of the contracts AfA PNP and catalog schemas — the
12
+ * package depends on contracts only in tests, like the rest of qti-react.
13
+ */
14
+ import type { BodyNode } from "./runtime";
15
+ export interface PnpReplaceAccessModeView {
16
+ readonly replaceAccessModes?: readonly string[];
17
+ }
18
+ export interface PnpLanguageModeView extends PnpReplaceAccessModeView {
19
+ readonly xmlLang: string;
20
+ }
21
+ /** additional-testing-time: "Only one of the available options can be selected." */
22
+ export interface PnpAdditionalTestingTimeView extends PnpReplaceAccessModeView {
23
+ readonly timeMultiplier?: number;
24
+ readonly fixedMinutes?: number;
25
+ readonly unlimited?: boolean;
26
+ }
27
+ export interface PnpFeatureSetView {
28
+ readonly features?: readonly string[];
29
+ }
30
+ /**
31
+ * The candidate's preferences, shaped like the normalized access-for-all-pnp
32
+ * document. Feature preference objects carry the fields card-entry discriminators
33
+ * match against (xmlLang, readingType, …); unknown fields are preserved.
34
+ */
35
+ export interface PnpView {
36
+ readonly [feature: string]: unknown;
37
+ readonly languageOfInterface?: readonly PnpLanguageModeView[];
38
+ readonly keywordTranslation?: PnpLanguageModeView;
39
+ readonly itemTranslation?: PnpLanguageModeView;
40
+ readonly signLanguage?: PnpLanguageModeView;
41
+ readonly additionalTestingTime?: PnpAdditionalTestingTimeView;
42
+ readonly activateAtInitializationSet?: PnpFeatureSetView;
43
+ readonly activateAsOptionSet?: PnpFeatureSetView;
44
+ readonly prohibitSet?: PnpFeatureSetView;
45
+ }
46
+ export interface CatalogFileHrefView {
47
+ readonly href: string;
48
+ readonly mimeType: string;
49
+ }
50
+ export interface CatalogContentView {
51
+ readonly xmlLang?: string;
52
+ readonly dataAttributes?: Readonly<Record<string, string>>;
53
+ readonly content?: readonly BodyNode[];
54
+ }
55
+ export interface CatalogCardEntryView {
56
+ readonly xmlLang?: string;
57
+ readonly default?: boolean;
58
+ readonly dataAttributes?: Readonly<Record<string, string>>;
59
+ readonly htmlContent?: CatalogContentView;
60
+ readonly fileHrefs?: readonly CatalogFileHrefView[];
61
+ }
62
+ export interface CatalogCardView {
63
+ readonly support: string;
64
+ readonly xmlLang?: string;
65
+ readonly htmlContent?: CatalogContentView;
66
+ readonly fileHrefs?: readonly CatalogFileHrefView[];
67
+ readonly cardEntries?: readonly CatalogCardEntryView[];
68
+ }
69
+ export interface CatalogView {
70
+ readonly id: string;
71
+ readonly cards: readonly CatalogCardView[];
72
+ }
73
+ export interface PnpActivation {
74
+ /** Supports in effect from the start of the session. */
75
+ readonly active: ReadonlySet<string>;
76
+ /** Supports the candidate may turn on during the session (activate-as-option-set). */
77
+ readonly optional: ReadonlySet<string>;
78
+ /** Supports that must not be offered (prohibit-set) — wins over everything. */
79
+ readonly prohibited: ReadonlySet<string>;
80
+ }
81
+ /**
82
+ * Resolve the activation policy: prohibit-set wins; activate-at-initialization-set is
83
+ * active; activate-as-option-set is offered but off. A preference stated outside any
84
+ * set (e.g. a bare keyword-translation) is honored from the start — the PNP records
85
+ * the need, and without an activation policy there is nothing to defer to (designed
86
+ * policy, see the ADR).
87
+ */
88
+ export declare function resolvePnpActivation(pnp: PnpView | undefined): PnpActivation;
89
+ /**
90
+ * The PNP preference object stated for a feature — what card entries discriminate
91
+ * against, and what results reporting reads detail (language, time values) from.
92
+ */
93
+ export declare function pnpFeaturePreference(pnp: PnpView | undefined, feature: string): Record<string, unknown> | undefined;
94
+ /** A support's resolved alternative content for one catalog. */
95
+ export interface ResolvedCatalogSupport {
96
+ readonly support: string;
97
+ readonly xmlLang?: string;
98
+ readonly content?: readonly BodyNode[];
99
+ readonly fileHrefs?: readonly CatalogFileHrefView[];
100
+ }
101
+ export interface CatalogResolution {
102
+ readonly activation: PnpActivation;
103
+ readonly byCatalogId: ReadonlyMap<string, readonly ResolvedCatalogSupport[]>;
104
+ }
105
+ /**
106
+ * Resolve every catalog's active alternative content for a candidate. `activeSupports`
107
+ * is the delivery-engine channel — program settings and candidate-toggled options
108
+ * ("or an assessment program's settings", §5.28); prohibited supports stay out even
109
+ * when named there.
110
+ */
111
+ export declare function resolveCatalogSupports(options: {
112
+ readonly catalogs?: readonly CatalogView[] | undefined;
113
+ readonly pnp?: PnpView | undefined;
114
+ readonly activeSupports?: readonly string[] | undefined;
115
+ }): CatalogResolution;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Response validity (ItemSessionControl validate-responses): "An invalid response is
3
+ * defined to be a response which does not satisfy the constraints imposed by the
4
+ * interaction with which it is associated." The constraint vocabulary is what the
5
+ * view model carries on interaction nodes — min/max-choices, min/max-associations,
6
+ * min-strings, pattern-mask, min-plays. Only authored attributes are validated:
7
+ * rendering defaults (e.g. a radio group's single choice) are interaction behavior,
8
+ * not submission constraints.
9
+ */
10
+ import type { BodyNode } from "./runtime";
11
+ import type { ResponseValue } from "./types";
12
+ export type ResponseConstraintKind = "minChoices" | "maxChoices" | "minAssociations" | "maxAssociations" | "minStrings" | "patternMask" | "minPlays";
13
+ export interface InteractionConstraint {
14
+ readonly responseIdentifier: string;
15
+ readonly kind: ResponseConstraintKind;
16
+ /** The declared bound: a count for the min/max constraints, the XSD regex for patternMask. */
17
+ readonly bound: number | string;
18
+ }
19
+ /** A constraint the current response fails — the reason a submission is invalid. */
20
+ export type ResponseViolation = InteractionConstraint;
21
+ /**
22
+ * Walk the item body and collect the constraint attributes its interactions carry.
23
+ * Zero bounds impose nothing: "If max-choices is 0 then there is no restriction";
24
+ * "If min-choices is 0 then the candidate is not required to select any choices."
25
+ */
26
+ export declare function collectInteractionConstraints(content: readonly BodyNode[] | undefined): InteractionConstraint[];
27
+ /** The constraints the current responses fail; empty means the responses are valid. */
28
+ export declare function collectResponseViolations(constraints: readonly InteractionConstraint[], responses: Readonly<Record<string, ResponseValue>>): ResponseViolation[];
@@ -17,8 +17,13 @@ export interface EvalEnv {
17
17
  readonly random?: (() => number) | undefined;
18
18
  /** `testVariables` aggregation; present only in test-level outcome processing. */
19
19
  readonly testVariables?: (expression: RpExpressionView) => MaybeRpValue;
20
- /** The `number*` item-session aggregates; present only in test-level outcome processing. */
20
+ /**
21
+ * The test-level subset aggregates (`number*`, `outcomeMinimum`/`outcomeMaximum`);
22
+ * present only in test-level outcome processing.
23
+ */
21
24
  readonly testAggregate?: (expression: RpExpressionView) => MaybeRpValue;
25
+ /** Declared default of any item variable, for the `default` expression (§2.11.1.3). */
26
+ readonly variableDefault?: (identifier: string) => MaybeRpValue;
22
27
  /** Registered vendor operators by class; unregistered classes stay unsupported. */
23
28
  readonly customOperators?: Readonly<Record<string, CustomOperatorImplementation>> | undefined;
24
29
  }
@@ -1,4 +1,4 @@
1
1
  export { collectRpIssues, executeResponseProcessing } from "./interpreter";
2
- export { applyCorrectResponseOverrides, collectTemplateIssues, executeTemplateProcessing, mulberry32, type TemplateConditionBranch, type TemplateProcessingContext, type TemplateProcessingResult, type TemplateProcessingView, type TemplateRuleView, } from "./template-processing";
2
+ export { applyCorrectResponseOverrides, applyTemplateDefaultOverrides, collectTemplateIssues, executeTemplateProcessing, mulberry32, type TemplateConditionBranch, type TemplateProcessingContext, type TemplateProcessingResult, type TemplateProcessingView, type TemplateRuleView, } from "./template-processing";
3
3
  export { resolveTemplate } from "./templates";
4
- export type { CustomOperatorImplementation, MaybeRpValue, OutcomeDeclarationView, OutcomeValue, ResponseNormalization, ResponseProcessingContext, ResponseProcessingResult, ResponseProcessingView, RpConditionBranch, RpExpressionView, RpRecordField, RpRuleView, RpScalar, RpValue, TemplateDeclarationView, } from "./types";
4
+ export type { CustomOperatorImplementation, InterpolationTableEntryView, InterpolationTableView, MatchTableEntryView, MatchTableView, MaybeRpValue, OutcomeDeclarationView, OutcomeValue, ResponseNormalization, ResponseProcessingContext, ResponseProcessingResult, ResponseProcessingView, RpConditionBranch, RpExpressionView, RpRecordField, RpRuleView, RpScalar, RpValue, TemplateDeclarationView, } from "./types";
@@ -5,11 +5,17 @@
5
5
  * and is reported as an `unsupported-rp` Capability issue — never partial scoring.
6
6
  */
7
7
  import type { CapabilityIssue } from "../capability";
8
- import type { ResponseProcessingContext, ResponseProcessingResult, ResponseProcessingView } from "./types";
8
+ import type { OutcomeDeclarationView, ResponseProcessingContext, ResponseProcessingResult, ResponseProcessingView } from "./types";
9
9
  export declare function executeResponseProcessing(view: ResponseProcessingView | undefined, context: ResponseProcessingContext): ResponseProcessingResult;
10
10
  export interface RpIssueOptions {
11
11
  /** `customOperator` classes the consumer has registered implementations for. */
12
12
  readonly customOperatorClasses?: ReadonlySet<string>;
13
+ /**
14
+ * The item's outcome declarations; when supplied, a `lookupOutcomeValue` rule whose
15
+ * declaration carries no lookupTable is reported statically (gate parity with the
16
+ * runtime refusal).
17
+ */
18
+ readonly outcomeDeclarations?: readonly OutcomeDeclarationView[];
13
19
  }
14
20
  /** Static coverage walk for `canDeliver`: reports constructs the interpreter lacks without executing. */
15
21
  export declare function collectRpIssues(view: ResponseProcessingView | undefined, options?: RpIssueOptions): readonly CapabilityIssue[];
@@ -0,0 +1,17 @@
1
+ /**
2
+ * lookupTable evaluation for the `lookupOutcomeValue` rule (§5.87): "sets the value
3
+ * of an outcome variable to the value obtained by looking up the value of the
4
+ * associated expression in the lookupTable associated with the outcome's
5
+ * declaration." Shared by the item interpreter and the test controller — the test's
6
+ * own outcome declarations use the same view type.
7
+ */
8
+ import type { OutcomeDeclarationView } from "./types";
9
+ import { type MaybeRpValue } from "./values";
10
+ export declare function hasLookupTable(declaration: OutcomeDeclarationView | undefined): declaration is OutcomeDeclarationView;
11
+ /**
12
+ * Look `source` up in the declaration's lookupTable. A NULL or non-numeric source is
13
+ * read as "no matching table entry is found" (§5.90.1) — an interpretive call the
14
+ * spec leaves open — so it takes the defaultValue path, never a refusal. "If
15
+ * omitted, the NULL value is used." (§5.90.1/§5.78.1)
16
+ */
17
+ export declare function lookupTableValue(declaration: OutcomeDeclarationView, source: MaybeRpValue): MaybeRpValue;
@@ -41,6 +41,11 @@ export interface TemplateProcessingResult {
41
41
  /** mulberry32: a tiny, fast, seeded PRNG — deterministic across platforms. */
42
42
  export declare function mulberry32(seed: number): () => number;
43
43
  export declare function executeTemplateProcessing(view: TemplateProcessingView | undefined, context: TemplateProcessingContext): TemplateProcessingResult;
44
+ /**
45
+ * The effective template declarations for a clone: test-level `templateDefault`
46
+ * values (§5.152) replace the declared defaults. A NULL value clears the default.
47
+ */
48
+ export declare function applyTemplateDefaultOverrides(declarations: readonly TemplateDeclarationView[], overrides: Readonly<Record<string, OutcomeValue>>): readonly TemplateDeclarationView[];
44
49
  /** The effective response declarations for a clone: setCorrectResponse overrides applied. */
45
50
  export declare function applyCorrectResponseOverrides(declarations: readonly ResponseDeclarationView[], overrides: Readonly<Record<string, CorrectResponseView>>): readonly ResponseDeclarationView[];
46
51
  /** Static coverage walk for `canDeliver` over a templateProcessing tree. */
@@ -25,6 +25,38 @@ export interface RpValue {
25
25
  readonly fields?: readonly RpRecordField[];
26
26
  }
27
27
  export type MaybeRpValue = RpValue | null;
28
+ /** One matchTable row: exact integer source → target (§7.23). */
29
+ export interface MatchTableEntryView {
30
+ readonly sourceValue: number;
31
+ readonly targetValue: RpScalar;
32
+ }
33
+ /**
34
+ * "A matchTable transforms a source integer by finding the first
35
+ * qti-match-table-entry with an exact match to the source." (§5.90)
36
+ */
37
+ export interface MatchTableView {
38
+ /** "The default outcome value to be used when no matching table entry is found.
39
+ * If omitted, the NULL value is used." (§5.90.1) */
40
+ readonly defaultValue?: RpScalar;
41
+ readonly matchTableEntries: readonly MatchTableEntryView[];
42
+ }
43
+ /** One interpolationTable row; sourceValue is "the lower bound … to match this entry" (§7.18.1). */
44
+ export interface InterpolationTableEntryView {
45
+ readonly sourceValue: number;
46
+ readonly targetValue: RpScalar;
47
+ /** "If 'true', the default, then an exact match of the value is considered a match
48
+ * of this entry." (§7.18.2) */
49
+ readonly includeBoundary?: boolean;
50
+ }
51
+ /**
52
+ * "An interpolationTable transforms a source float (or integer) by finding the first
53
+ * interpolationTableEntry with a sourceValue that is less than or equal to (subject
54
+ * to includeBoundary) the source value." (§5.78)
55
+ */
56
+ export interface InterpolationTableView {
57
+ readonly defaultValue?: RpScalar;
58
+ readonly interpolationTableEntries: readonly InterpolationTableEntryView[];
59
+ }
28
60
  export interface OutcomeDeclarationView {
29
61
  readonly identifier: string;
30
62
  readonly cardinality: Cardinality;
@@ -34,6 +66,12 @@ export interface OutcomeDeclarationView {
34
66
  readonly value: RpScalar;
35
67
  }>;
36
68
  };
69
+ /** The declaration's lookupTable (at most one of the two), read by `lookupOutcomeValue` (§5.87). */
70
+ readonly matchTable?: MatchTableView;
71
+ readonly interpolationTable?: InterpolationTableView;
72
+ /** Declared score bounds, aggregated by `outcomeMaximum`/`outcomeMinimum` (§2.11.2.6-7). */
73
+ readonly normalMaximum?: number;
74
+ readonly normalMinimum?: number;
37
75
  }
38
76
  /**
39
77
  * One expression node. Deliberately loose (`kind: string`): kinds the interpreter
@@ -45,11 +83,14 @@ export interface RpExpressionView {
45
83
  readonly baseType?: string;
46
84
  readonly value?: RpScalar;
47
85
  readonly expressions?: readonly RpExpressionView[];
48
- /** Bounds/step for the random operators (template processing). */
49
- readonly min?: number;
50
- readonly max?: number;
51
- readonly step?: number;
52
- /** `equal` tolerance window; string entries are template references (unsupported). */
86
+ /**
87
+ * Bounds/step for the random operators and `anyN`; string values are variable
88
+ * references resolved at runtime (§2.11.3.6), bare or brace-enclosed (§7.13).
89
+ */
90
+ readonly min?: number | string;
91
+ readonly max?: number | string;
92
+ readonly step?: number | string;
93
+ /** `equal` tolerance window; string entries are variable references. */
53
94
  readonly toleranceMode?: "exact" | "absolute" | "relative";
54
95
  readonly tolerance?: ReadonlyArray<number | string>;
55
96
  readonly includeLowerBound?: boolean;
@@ -63,14 +104,18 @@ export interface RpExpressionView {
63
104
  readonly figures?: number | string;
64
105
  /** Pass count for `repeat`. */
65
106
  readonly numberRepeats?: number | string;
107
+ /** XSD-dialect pattern for `patternMatch`; "{ref}" resolves from a variable (§7.13). */
108
+ readonly pattern?: string;
66
109
  /** String comparison controls for `stringMatch` and `substring`. */
67
110
  readonly caseSensitive?: boolean;
68
111
  readonly substring?: boolean;
69
112
  /** Area for `inside` (QTI shape + coords string). */
70
113
  readonly shape?: string;
71
114
  readonly coords?: string;
72
- /** Test-level subset selection (`testVariables` and the `number*` aggregates). */
115
+ /** Test-level subset selection (`testVariables`, `outcomeMinimum`/`outcomeMaximum`, `number*`). */
73
116
  readonly variableIdentifier?: string;
117
+ /** Outcome variable whose declared bounds `outcomeMinimum`/`outcomeMaximum` look up (§7.28.4). */
118
+ readonly outcomeIdentifier?: string;
74
119
  readonly weightIdentifier?: string;
75
120
  readonly sectionIdentifier?: string;
76
121
  readonly includeCategory?: string | readonly string[];
@@ -93,11 +138,16 @@ export interface RpConditionBranch {
93
138
  readonly expression: RpExpressionView;
94
139
  readonly rules: readonly RpRuleView[];
95
140
  }
96
- /** One response rule: responseCondition, setOutcomeValue, or exitResponse. */
141
+ /**
142
+ * One response rule: responseCondition, setOutcomeValue, lookupOutcomeValue,
143
+ * responseProcessingFragment, or exitResponse.
144
+ */
97
145
  export interface RpRuleView {
98
146
  readonly kind: string;
99
147
  readonly identifier?: string;
100
148
  readonly expression?: RpExpressionView;
149
+ /** Nested rules of a `responseProcessingFragment` (§5.118). */
150
+ readonly rules?: readonly RpRuleView[];
101
151
  readonly responseIf?: RpConditionBranch;
102
152
  readonly responseElseIfs?: readonly RpConditionBranch[];
103
153
  readonly responseElse?: {
@@ -148,6 +198,20 @@ export interface ResponseProcessingContext {
148
198
  * submission sequence then replays the exact same outcomes (ADR-0004 determinism).
149
199
  */
150
200
  readonly random?: (() => number) | undefined;
201
+ /**
202
+ * Built-in session variables (reserved identifiers; items must not declare them).
203
+ * `duration` is the item session's elapsed seconds; `numAttempts` "increases by 1
204
+ * at the start of each attempt", so it includes the attempt being scored.
205
+ */
206
+ readonly duration?: number | undefined;
207
+ readonly numAttempts?: number | undefined;
208
+ /**
209
+ * The session's current `completionStatus` — the third built-in, "declared
210
+ * implicitly"; it enters the outcome map (defaulting to "not_attempted") so
211
+ * `setOutcomeValue` can change it (§2.2.2.3). An explicit declaration (legacy
212
+ * content) wins over this value.
213
+ */
214
+ readonly completionStatus?: string | undefined;
151
215
  /** Registered vendor `customOperator` implementations by class (opt-in). */
152
216
  readonly customOperators?: Readonly<Record<string, CustomOperatorImplementation>> | undefined;
153
217
  }
package/dist/runtime.d.ts CHANGED
@@ -11,12 +11,15 @@ import { type ComponentType, type ReactNode } from "react";
11
11
  import type { ZodType } from "zod";
12
12
  import type { CapabilityReport } from "./capability";
13
13
  import { type ContentModel } from "./content-model";
14
+ import { type CatalogView, type PnpView, type ResolvedCatalogSupport } from "./pnp";
14
15
  import type { CustomOperatorImplementation, OutcomeDeclarationView, OutcomeValue, ResponseNormalization, ResponseProcessingView, TemplateDeclarationView, TemplateProcessingView } from "./rp";
15
16
  import { type AttemptSnapshot, type AttemptStore } from "./store";
16
17
  import type { ResponseDeclarationView, ResponseValue, ScoreResult } from "./types";
17
18
  export type { CapabilityIssue, CapabilityIssueType, CapabilityReport } from "./capability";
18
19
  export interface XmlContentNode {
19
20
  kind: "xml";
21
+ /** XML namespace URI; foreign vocabularies (SSML) are recognized by it. */
22
+ namespace?: string;
20
23
  name: string;
21
24
  value?: string;
22
25
  attributes?: Record<string, unknown>;
@@ -39,6 +42,31 @@ export interface FeedbackView {
39
42
  showHide?: "show" | "hide";
40
43
  content?: readonly BodyNode[];
41
44
  }
45
+ /** An item's reference to a shared AssessmentStimulus document (§7.6). */
46
+ export interface AssessmentStimulusRefView {
47
+ readonly identifier: string;
48
+ readonly href: string;
49
+ readonly title?: string;
50
+ }
51
+ /** Companion materials, structurally as normalized (calculators, rules, protractors, materials). */
52
+ export interface CompanionMaterialsView {
53
+ readonly calculators?: readonly Record<string, unknown>[];
54
+ readonly rules?: readonly Record<string, unknown>[];
55
+ readonly protractors?: readonly Record<string, unknown>[];
56
+ readonly digitalMaterials?: readonly {
57
+ readonly fileHref: string;
58
+ readonly label?: string;
59
+ readonly mimeType?: string;
60
+ readonly resourceIcon?: string;
61
+ }[];
62
+ readonly physicalMaterials?: readonly string[];
63
+ }
64
+ /** The resolved stimulus body, rendered through the same content walk as the item body. */
65
+ export interface StimulusContentView {
66
+ readonly content: readonly BodyNode[];
67
+ /** The stimulus document's catalogs (dormant alternative content, §5.29). */
68
+ readonly catalogs?: readonly CatalogView[];
69
+ }
42
70
  export interface AssessmentItemView {
43
71
  responseDeclarations: readonly ResponseDeclarationView[];
44
72
  outcomeDeclarations?: readonly OutcomeDeclarationView[];
@@ -48,6 +76,15 @@ export interface AssessmentItemView {
48
76
  /** QTI adaptive item: multiple attempts until completionStatus reaches "completed". */
49
77
  adaptive?: boolean;
50
78
  modalFeedbacks?: readonly FeedbackView[];
79
+ assessmentStimulusRefs?: readonly AssessmentStimulusRefView[];
80
+ /** Every catalog in the item (item-level and nested), pooled for idref resolution. */
81
+ catalogs?: readonly CatalogView[];
82
+ /**
83
+ * Companion materials (§2.13.1): "content props that provide key information to be
84
+ * used when answering an Item, e.g. a calculator, protractor, lookup chart". The
85
+ * runtime exposes them; the delivery platform owns the tools themselves.
86
+ */
87
+ companionMaterials?: CompanionMaterialsView;
51
88
  itemBody: {
52
89
  content?: BodyNode[];
53
90
  };
@@ -100,6 +137,12 @@ export interface InteractionRenderProps {
100
137
  * interactions like PCI own their response state). Returns the unregister function.
101
138
  */
102
139
  registerResponseCollector: (collector: () => ResponseValue | undefined) => () => void;
140
+ /**
141
+ * Render the active catalog supports for a skin-owned node's data-catalog-idref
142
+ * (e.g. a choice label) — the same resolution and presentation the core walk
143
+ * applies to generic flow nodes. Null when nothing is active.
144
+ */
145
+ renderCatalogSupports: (catalogIdref: string | undefined) => ReactNode;
103
146
  }
104
147
  /** Per-kind render overrides a skin passes to `renderContent` for nodes it owns. */
105
148
  export type NodeOverrides = Readonly<Record<string, (node: BodyNode, key: number) => ReactNode>>;
@@ -123,6 +166,19 @@ export interface QtiRuntimeConfig {
123
166
  * registered classes pass the capability gate; everything else stays unsupported.
124
167
  */
125
168
  readonly customOperators?: Readonly<Record<string, CustomOperatorImplementation>>;
169
+ /**
170
+ * Resolve an item's shared-stimulus reference to its normalized body content
171
+ * (synchronous by design, like the session store's resolveItem: load the package's
172
+ * stimuli before mounting; `stimulusContentFromNormalized` reshapes a normalized
173
+ * document). Unresolved refs are capability issues, never silent drops (ADR-0003).
174
+ */
175
+ readonly resolveStimulus?: (ref: AssessmentStimulusRefView) => StimulusContentView | null;
176
+ /**
177
+ * Replaces the default rendering of an active catalog support (the note-role span
178
+ * appended beside the referenced content). The delivery engine owns presentation —
179
+ * tooltips, players, glossary panels — the runtime owns resolution.
180
+ */
181
+ readonly renderCatalogSupport?: (support: ResolvedCatalogSupport, catalogIdref: string) => ReactNode;
126
182
  }
127
183
  export interface ItemRendererProps {
128
184
  item: AssessmentItemView;
@@ -134,12 +190,42 @@ export interface ItemRendererProps {
134
190
  store?: AttemptStore | undefined;
135
191
  /** Clone seed for template processing; store it to replay the same clone. */
136
192
  seed?: number | undefined;
193
+ /**
194
+ * The item-session state to render. `review` is read-only: "the candidate can
195
+ * review the qti-item-body along with the responses they gave, but cannot update
196
+ * or resubmit them". `solution` additionally swaps in the clone's resolved correct
197
+ * responses ("a way of entering the solution state"); the show-solution gate is
198
+ * the consumer's (effective ItemSessionControl). Default: "interact".
199
+ */
200
+ mode?: ItemRenderMode | undefined;
201
+ /**
202
+ * Effective ItemSessionControl show-feedback; consulted only outside `interact`.
203
+ * `false` withholds modal and integrated feedback — visibility is then
204
+ * "determined by the default values of the outcome variables" — and is ignored
205
+ * for adaptive items, per spec.
206
+ */
207
+ showFeedback?: boolean | undefined;
208
+ /**
209
+ * The candidate's AfA PNP. Activates the item's dormant catalog supports (§5.29):
210
+ * "A candidate's profile (or assessment program settings) will indicate whether the
211
+ * candidate should be presented any of the possible supports."
212
+ */
213
+ pnp?: PnpView | undefined;
214
+ /**
215
+ * Supports in effect beyond the PNP's initial activation — program settings and
216
+ * candidate-toggled options (activate-as-option-set). Prohibited supports stay out.
217
+ */
218
+ activeSupports?: readonly string[] | undefined;
137
219
  children?: ReactNode;
138
220
  }
139
221
  export interface ContentRendererProps {
140
222
  nodes?: readonly BodyNode[] | undefined;
141
223
  /** Values for printedVariable (and showHide-gated feedback) inside the content. */
142
224
  outcomes?: Readonly<Record<string, OutcomeValue>> | undefined;
225
+ /** Catalogs referenced by this content (e.g. a test rubric block's catalogInfo). */
226
+ catalogs?: readonly CatalogView[] | undefined;
227
+ pnp?: PnpView | undefined;
228
+ activeSupports?: readonly string[] | undefined;
143
229
  }
144
230
  export interface QtiRuntime {
145
231
  ItemRenderer: ComponentType<ItemRendererProps>;
@@ -149,6 +235,13 @@ export interface QtiRuntime {
149
235
  */
150
236
  ContentRenderer: ComponentType<ContentRendererProps>;
151
237
  useAttempt: () => AttemptController;
238
+ /**
239
+ * The active supports resolved for a catalog idref — for skins whose own nodes
240
+ * carry data-catalog-idref (e.g. a choice label) and consumers building support
241
+ * UI (glossary panels, toggles). The core walk already decorates generic flow
242
+ * and block nodes; this is the same resolution by hand.
243
+ */
244
+ useCatalogSupports: (catalogIdref: string | undefined) => readonly ResolvedCatalogSupport[];
152
245
  /**
153
246
  * The Capability Report for an item against this runtime's injected descriptors,
154
247
  * skins, and content model. Consumers gate delivery on it (ADR-0003); the
@@ -161,4 +254,6 @@ export interface AttemptController extends AttemptSnapshot {
161
254
  submit: () => readonly ScoreResult[];
162
255
  reset: () => void;
163
256
  }
257
+ /** The item-session states the renderer knows (ItemSessionControl review/solution). */
258
+ export type ItemRenderMode = "interact" | "review" | "solution";
164
259
  export declare function createQtiRuntime(config: QtiRuntimeConfig): QtiRuntime;
package/dist/store.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  * support multiple attempts: outcomes carry over between RP runs and the item locks
12
12
  * only when `completionStatus` reaches "completed".
13
13
  */
14
+ import type { InteractionConstraint, ResponseViolation } from "./response-validity";
14
15
  import type { CustomOperatorImplementation, OutcomeDeclarationView, OutcomeValue, ResponseNormalization, ResponseProcessingView, TemplateDeclarationView, TemplateProcessingView } from "./rp";
15
16
  import type { ResponseDeclarationView, ResponseValue, ScoreResult } from "./types";
16
17
  export interface AttemptSnapshot {
@@ -24,6 +25,23 @@ export interface AttemptSnapshot {
24
25
  readonly templateValues: Readonly<Record<string, OutcomeValue>>;
25
26
  /** Completed attempts so far (only ever exceeds 1 for adaptive items). */
26
27
  readonly attemptCount: number;
28
+ /**
29
+ * Elapsed session seconds at the latest submit (the built-in `duration` response
30
+ * variable handed to RP); null before the first submit. Persist it alongside the
31
+ * responses for server-side replay parity (ADR-0004).
32
+ */
33
+ readonly durationSeconds: number | null;
34
+ /**
35
+ * Interaction constraints the current responses fail (see response-validity).
36
+ * Always visible so UIs can explain themselves; submission is blocked on them
37
+ * only under `validateResponses`.
38
+ */
39
+ readonly responseViolations: readonly ResponseViolation[];
40
+ /**
41
+ * This clone's resolved correct responses (template `setCorrectResponse` overrides
42
+ * applied), keyed by response identifier. The solution state renders these.
43
+ */
44
+ readonly correctResponses: Readonly<Record<string, ResponseValue>>;
27
45
  }
28
46
  /**
29
47
  * Consumer-supplied input. Optional members deliberately admit explicit
@@ -38,12 +56,32 @@ export interface AttemptStoreOptions {
38
56
  readonly normalization?: ResponseNormalization | undefined;
39
57
  readonly templateDeclarations?: readonly TemplateDeclarationView[] | undefined;
40
58
  readonly templateProcessing?: TemplateProcessingView | undefined;
59
+ /**
60
+ * Test-level `templateDefault` values (§5.152) overriding the template
61
+ * declarations' defaults for this clone; the test session store supplies them from
62
+ * the controller's recorded `templateDefaultValues`.
63
+ */
64
+ readonly templateDefaultValues?: Readonly<Record<string, OutcomeValue>> | undefined;
41
65
  /** Clone seed for template processing; store it to replay the same clone. */
42
66
  readonly seed?: number | undefined;
43
67
  /** QTI adaptive item: multiple attempts, outcome carry-over, completionStatus lock. */
44
68
  readonly adaptive?: boolean | undefined;
45
69
  /** Registered vendor `customOperator` implementations by class (opt-in). */
46
70
  readonly customOperators?: Readonly<Record<string, CustomOperatorImplementation>> | undefined;
71
+ /**
72
+ * Millisecond clock backing the built-in `duration` response variable (wall-clock
73
+ * from session start to submit). Injectable for deterministic tests and replays;
74
+ * defaults to Date.now.
75
+ */
76
+ readonly now?: (() => number) | undefined;
77
+ /** The item's interaction constraints (collectInteractionConstraints over the body). */
78
+ readonly constraints?: readonly InteractionConstraint[] | undefined;
79
+ /**
80
+ * ItemSessionControl validate-responses: "candidates are not allowed to submit the
81
+ * item until they have provided valid responses for all interactions". When set,
82
+ * submit() refuses while `responseViolations` is non-empty.
83
+ */
84
+ readonly validateResponses?: boolean | undefined;
47
85
  }
48
86
  export interface AttemptStore {
49
87
  readonly getSnapshot: () => AttemptSnapshot;
@@ -57,5 +95,14 @@ export interface AttemptStore {
57
95
  readonly registerResponseCollector: (responseIdentifier: string, collector: () => ResponseValue | undefined) => () => void;
58
96
  readonly submit: () => readonly ScoreResult[];
59
97
  readonly reset: () => void;
98
+ /**
99
+ * Stop the session clock: duration "records the accumulated time (in seconds) of
100
+ * all Candidate Sessions for all Attempts … minus any time the session was in the
101
+ * suspended state". Navigating away from an item suspends its session (spec);
102
+ * the test session store drives this. Idempotent.
103
+ */
104
+ readonly suspend: () => void;
105
+ /** Restart the session clock after `suspend()`. Idempotent. */
106
+ readonly resume: () => void;
60
107
  }
61
108
  export declare function createAttemptStore(declarations: readonly ResponseDeclarationView[], initialResponses: Readonly<Record<string, ResponseValue>>, options?: AttemptStoreOptions): AttemptStore;
@@ -4,8 +4,30 @@
4
4
  * navigation, branching, and outcome-processing question as a pure transition over the
5
5
  * consumer-persisted session state. It owns no storage and renders nothing.
6
6
  */
7
+ import { type PnpView } from "../pnp";
8
+ import type { OutcomeDeclarationView } from "../rp/types";
7
9
  import type { AssessmentTestView, TestController } from "./types";
8
10
  export interface TestControllerOptions {
9
11
  readonly seed: number;
12
+ /**
13
+ * Each item's outcome declarations, keyed by item-ref identifier (shared by every
14
+ * selected instance of the ref). Feeds `outcomeMaximum`/`outcomeMinimum` with the declared
15
+ * `normal-maximum`/`normal-minimum`; items absent here degrade per spec — maximum
16
+ * → NULL (§2.11.2.7), minimum → ignored (§2.11.2.6) — never a refusal. Consumers
17
+ * can pass `assessmentItemViewFromNormalized(...).outcomeDeclarations` verbatim.
18
+ */
19
+ readonly itemOutcomeDeclarations?: Readonly<Record<string, readonly OutcomeDeclarationView[]>> | undefined;
20
+ /**
21
+ * Millisecond clock backing the built-in test/part/section `duration` variables and
22
+ * timeLimits enforcement. Injectable for deterministic tests and replays; defaults
23
+ * to Date.now.
24
+ */
25
+ readonly now?: (() => number) | undefined;
26
+ /**
27
+ * The candidate's AfA PNP. The controller consumes additional-testing-time:
28
+ * "the durations may be changed depending on the relevant accessibility values
29
+ * in the Personal Needs & Preferences settings for the learner" (§2.8.5).
30
+ */
31
+ readonly pnp?: PnpView | undefined;
10
32
  }
11
33
  export declare function createTestController(view: AssessmentTestView, options: TestControllerOptions): TestController;
@@ -1,3 +1,4 @@
1
1
  export { createTestController, type TestControllerOptions } from "./controller";
2
+ export { assessmentResultFromNormalized, buildAssessmentResult, type AssessmentResultDocumentView, type AssessmentResultInput, type AssessmentResultItemDetails, type AssessmentResultView, type ItemResultView, type ResultContextView, type ResultOutcomeVariableView, type ResultResponseVariableView, type ResultSessionIdentifierView, type ResultSessionStatus, type ResultSupportView, type ResultValueView, type TestResultView, } from "./results";
2
3
  export { createTestSessionStore, type TestSessionSnapshot, type TestSessionStore, type TestSessionStoreOptions, } from "./session-store";
3
- export type { AssessmentItemRefView, AssessmentSectionView, AssessmentTestView, BranchRuleView, ItemSessionControlView, OutcomeConditionBranch, OutcomeRuleView, TestController, TestFeedbackView, TestItemResult, TestPartView, TestPlan, TestPlanItem, TestPlanPart, TestSessionState, TimeLimitsView, } from "./types";
4
+ export type { AssessmentItemRefView, RecordedAttempt, AssessmentSectionView, AssessmentTestView, BranchRuleView, ItemSessionControlView, OutcomeConditionBranch, OutcomeRuleView, RejectedSubmission, TemplateDefaultView, TestController, TestFeedbackView, TestItemResult, TestPartView, TestPlan, TestPlanItem, TestPlanPart, TestPlanSection, TestSessionState, TestTimingState, TimeLimitsView, TimingScopeRef, } from "./types";