@pie-players/pie-assessment-toolkit 0.3.30 → 0.3.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +289 -5
- package/dist/components/ItemToolBar.custom-element.js +165 -92
- package/dist/components/PieAssessmentToolkit.custom-element.js +604 -107
- package/dist/components/SectionToolBar.custom-element.js +0 -36
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/policy/core/PolicySource.d.ts +64 -0
- package/dist/policy/core/PolicySource.d.ts.map +1 -0
- package/dist/policy/core/PolicySource.js +17 -0
- package/dist/policy/core/PolicySource.js.map +1 -0
- package/dist/policy/core/ToolPolicyEngine.d.ts +123 -0
- package/dist/policy/core/ToolPolicyEngine.d.ts.map +1 -0
- package/dist/policy/core/ToolPolicyEngine.js +217 -0
- package/dist/policy/core/ToolPolicyEngine.js.map +1 -0
- package/dist/policy/core/compose-decision.d.ts +37 -0
- package/dist/policy/core/compose-decision.d.ts.map +1 -0
- package/dist/policy/core/compose-decision.js +332 -0
- package/dist/policy/core/compose-decision.js.map +1 -0
- package/dist/policy/core/decision-types.d.ts +142 -0
- package/dist/policy/core/decision-types.d.ts.map +1 -0
- package/dist/policy/core/decision-types.js +13 -0
- package/dist/policy/core/decision-types.js.map +1 -0
- package/dist/policy/core/engine-context.d.ts +30 -0
- package/dist/policy/core/engine-context.d.ts.map +1 -0
- package/dist/policy/core/engine-context.js +28 -0
- package/dist/policy/core/engine-context.js.map +1 -0
- package/dist/policy/core/policy-source-tag.d.ts +32 -0
- package/dist/policy/core/policy-source-tag.d.ts.map +1 -0
- package/dist/policy/core/policy-source-tag.js +29 -0
- package/dist/policy/core/policy-source-tag.js.map +1 -0
- package/dist/policy/core/provenance.d.ts +163 -0
- package/dist/policy/core/provenance.d.ts.map +1 -0
- package/dist/policy/core/provenance.js +232 -0
- package/dist/policy/core/provenance.js.map +1 -0
- package/dist/policy/core/qti-inputs.d.ts +67 -0
- package/dist/policy/core/qti-inputs.d.ts.map +1 -0
- package/dist/policy/core/qti-inputs.js +139 -0
- package/dist/policy/core/qti-inputs.js.map +1 -0
- package/dist/policy/engine.d.ts +28 -0
- package/dist/policy/engine.d.ts.map +1 -0
- package/dist/policy/engine.js +24 -0
- package/dist/policy/engine.js.map +1 -0
- package/dist/policy/internal.d.ts +26 -0
- package/dist/policy/internal.d.ts.map +1 -0
- package/dist/policy/internal.js +26 -0
- package/dist/policy/internal.js.map +1 -0
- package/dist/policy/sources/QtiPolicySource.d.ts +146 -0
- package/dist/policy/sources/QtiPolicySource.d.ts.map +1 -0
- package/dist/policy/sources/QtiPolicySource.js +270 -0
- package/dist/policy/sources/QtiPolicySource.js.map +1 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +145 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.js +164 -3
- package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts +144 -0
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts.map +1 -0
- package/dist/runtime/adapter/SectionEngineAdapter.js +183 -0
- package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -0
- package/dist/runtime/adapter/coordinator-bridge.d.ts +93 -0
- package/dist/runtime/adapter/coordinator-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/coordinator-bridge.js +127 -0
- package/dist/runtime/adapter/coordinator-bridge.js.map +1 -0
- package/dist/runtime/adapter/dom-event-bridge.d.ts +63 -0
- package/dist/runtime/adapter/dom-event-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/dom-event-bridge.js +95 -0
- package/dist/runtime/adapter/dom-event-bridge.js.map +1 -0
- package/dist/runtime/adapter/framework-error-bridge.d.ts +37 -0
- package/dist/runtime/adapter/framework-error-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/framework-error-bridge.js +47 -0
- package/dist/runtime/adapter/framework-error-bridge.js.map +1 -0
- package/dist/runtime/adapter/instrumentation-bridge.d.ts +32 -0
- package/dist/runtime/adapter/instrumentation-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/instrumentation-bridge.js +39 -0
- package/dist/runtime/adapter/instrumentation-bridge.js.map +1 -0
- package/dist/runtime/adapter/subscriber-fanout.d.ts +35 -0
- package/dist/runtime/adapter/subscriber-fanout.d.ts.map +1 -0
- package/dist/runtime/adapter/subscriber-fanout.js +60 -0
- package/dist/runtime/adapter/subscriber-fanout.js.map +1 -0
- package/dist/runtime/core/SectionEngineCore.d.ts +52 -0
- package/dist/runtime/core/SectionEngineCore.d.ts.map +1 -0
- package/dist/runtime/core/SectionEngineCore.js +81 -0
- package/dist/runtime/core/SectionEngineCore.js.map +1 -0
- package/dist/runtime/core/cohort.d.ts +40 -0
- package/dist/runtime/core/cohort.d.ts.map +1 -0
- package/dist/runtime/core/cohort.js +49 -0
- package/dist/runtime/core/cohort.js.map +1 -0
- package/dist/runtime/core/engine-input.d.ts +114 -0
- package/dist/runtime/core/engine-input.d.ts.map +1 -0
- package/dist/runtime/core/engine-input.js +19 -0
- package/dist/runtime/core/engine-input.js.map +1 -0
- package/dist/runtime/core/engine-output.d.ts +62 -0
- package/dist/runtime/core/engine-output.d.ts.map +1 -0
- package/dist/runtime/core/engine-output.js +27 -0
- package/dist/runtime/core/engine-output.js.map +1 -0
- package/dist/runtime/core/engine-readiness.d.ts +49 -0
- package/dist/runtime/core/engine-readiness.d.ts.map +1 -0
- package/dist/runtime/core/engine-readiness.js +47 -0
- package/dist/runtime/core/engine-readiness.js.map +1 -0
- package/dist/runtime/core/engine-resolver.d.ts +172 -0
- package/dist/runtime/core/engine-resolver.d.ts.map +1 -0
- package/dist/runtime/core/engine-resolver.js +170 -0
- package/dist/runtime/core/engine-resolver.js.map +1 -0
- package/dist/runtime/core/engine-stage-derivation.d.ts +19 -0
- package/dist/runtime/core/engine-stage-derivation.d.ts.map +1 -0
- package/dist/runtime/core/engine-stage-derivation.js +30 -0
- package/dist/runtime/core/engine-stage-derivation.js.map +1 -0
- package/dist/runtime/core/engine-state.d.ts +83 -0
- package/dist/runtime/core/engine-state.d.ts.map +1 -0
- package/dist/runtime/core/engine-state.js +49 -0
- package/dist/runtime/core/engine-state.js.map +1 -0
- package/dist/runtime/core/engine-transition.d.ts +49 -0
- package/dist/runtime/core/engine-transition.d.ts.map +1 -0
- package/dist/runtime/core/engine-transition.js +261 -0
- package/dist/runtime/core/engine-transition.js.map +1 -0
- package/dist/runtime/engine-context.d.ts +40 -0
- package/dist/runtime/engine-context.d.ts.map +1 -0
- package/dist/runtime/engine-context.js +32 -0
- package/dist/runtime/engine-context.js.map +1 -0
- package/dist/runtime/engine.d.ts +24 -0
- package/dist/runtime/engine.d.ts.map +1 -0
- package/dist/runtime/engine.js +24 -0
- package/dist/runtime/engine.js.map +1 -0
- package/dist/runtime/internal.d.ts +44 -0
- package/dist/runtime/internal.d.ts.map +1 -0
- package/dist/runtime/internal.js +46 -0
- package/dist/runtime/internal.js.map +1 -0
- package/dist/runtime/runtime-id.d.ts +8 -0
- package/dist/runtime/runtime-id.d.ts.map +1 -0
- package/dist/runtime/runtime-id.js +10 -0
- package/dist/runtime/runtime-id.js.map +1 -0
- package/dist/runtime/section-runtime-engine-host-context.d.ts +74 -0
- package/dist/runtime/section-runtime-engine-host-context.d.ts.map +1 -0
- package/dist/runtime/section-runtime-engine-host-context.js +96 -0
- package/dist/runtime/section-runtime-engine-host-context.js.map +1 -0
- package/dist/runtime/stage-emit-gate.d.ts +93 -0
- package/dist/runtime/stage-emit-gate.d.ts.map +1 -0
- package/dist/runtime/stage-emit-gate.js +83 -0
- package/dist/runtime/stage-emit-gate.js.map +1 -0
- package/dist/services/ToolkitCoordinator.d.ts +280 -7
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +412 -32
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/framework-error-bus.d.ts +73 -0
- package/dist/services/framework-error-bus.d.ts.map +1 -0
- package/dist/services/framework-error-bus.js +68 -0
- package/dist/services/framework-error-bus.js.map +1 -0
- package/dist/services/framework-error.d.ts +36 -1
- package/dist/services/framework-error.d.ts.map +1 -1
- package/dist/services/framework-error.js +37 -0
- package/dist/services/framework-error.js.map +1 -1
- package/dist/services/interfaces.d.ts +78 -0
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/section-controller-types.d.ts +20 -0
- package/dist/services/section-controller-types.d.ts.map +1 -1
- package/dist/services/tools-config-normalizer.d.ts +23 -0
- package/dist/services/tools-config-normalizer.d.ts.map +1 -1
- package/dist/services/tools-config-normalizer.js +13 -1
- package/dist/services/tools-config-normalizer.js.map +1 -1
- package/dist/tools/client.d.ts +0 -1
- package/dist/tools/client.d.ts.map +1 -1
- package/dist/tools/client.js +0 -2
- package/dist/tools/client.js.map +1 -1
- package/dist/tools/types.d.ts +0 -26
- package/dist/tools/types.d.ts.map +1 -1
- package/package.json +23 -7
- package/dist/runtime/runtime-event-guards.d.ts +0 -2
- package/dist/runtime/runtime-event-guards.d.ts.map +0 -1
- package/dist/runtime/runtime-event-guards.js +0 -4
- package/dist/runtime/runtime-event-guards.js.map +0 -1
- package/dist/tools/tool-coordinator.d.ts +0 -72
- package/dist/tools/tool-coordinator.d.ts.map +0 -1
- package/dist/tools/tool-coordinator.js +0 -194
- package/dist/tools/tool-coordinator.js.map +0 -1
- package/dist/types/events.d.ts +0 -157
- package/dist/types/events.d.ts.map +0 -1
- package/dist/types/events.js +0 -11
- package/dist/types/events.js.map +0 -1
|
@@ -16,10 +16,12 @@ const __pieDefineSafely = (tagName, ctor) => {
|
|
|
16
16
|
|
|
17
17
|
import 'svelte/internal/disclose-version';
|
|
18
18
|
import * as $ from 'svelte/internal/client';
|
|
19
|
+
import { untrack } from "svelte";
|
|
19
20
|
import { ContextProvider, ContextRoot, requestContext } from "@pie-players/pie-context";
|
|
20
21
|
|
|
21
22
|
import {
|
|
22
23
|
attachInstrumentationEventBridge,
|
|
24
|
+
createStageTracker,
|
|
23
25
|
resolveInstrumentationProvider,
|
|
24
26
|
TOOLKIT_INSTRUMENTATION_EVENT_MAP
|
|
25
27
|
} from "@pie-players/pie-players-shared/pie";
|
|
@@ -34,7 +36,9 @@ import {
|
|
|
34
36
|
import { connectAssessmentToolkitHostRuntimeContext } from "../context/runtime-context-consumer.js";
|
|
35
37
|
import { ToolkitCoordinator } from "../services/ToolkitCoordinator.js";
|
|
36
38
|
import { formatFrameworkErrorForConsole, frameworkErrorFromUnknown } from "../services/framework-error.js";
|
|
39
|
+
import { FrameworkErrorBus } from "../services/framework-error-bus.js";
|
|
37
40
|
import { normalizeAndValidateToolsConfig } from "../services/tool-config-validation.js";
|
|
41
|
+
import { parseToolList } from "../services/tools-config-normalizer.js";
|
|
38
42
|
|
|
39
43
|
import {
|
|
40
44
|
PIE_INTERNAL_CONTENT_LOADED_EVENT,
|
|
@@ -46,7 +50,9 @@ import {
|
|
|
46
50
|
|
|
47
51
|
import { dispatchCrossBoundaryEvent } from "../runtime/tool-host-contract.js";
|
|
48
52
|
import { SectionRuntimeEngine } from "../runtime/SectionRuntimeEngine.js";
|
|
49
|
-
import {
|
|
53
|
+
import { connectSectionRuntimeEngineHostContext } from "../runtime/section-runtime-engine-host-context.js";
|
|
54
|
+
import { runStageEmitWithSuppression } from "../runtime/stage-emit-gate.js";
|
|
55
|
+
import { createRuntimeId } from "../runtime/runtime-id.js";
|
|
50
56
|
|
|
51
57
|
import {
|
|
52
58
|
createSessionEmitPolicyState,
|
|
@@ -68,6 +74,31 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
68
74
|
|
|
69
75
|
const runtimeId = createRuntimeId("toolkit");
|
|
70
76
|
const sectionEngine = new SectionRuntimeEngine();
|
|
77
|
+
|
|
78
|
+
// M7 PR 6: when wrapped by a section-player layout, the kernel
|
|
79
|
+
// publishes its engine via the cross-CE
|
|
80
|
+
// `sectionRuntimeEngineHostContext`. We track the upstream
|
|
81
|
+
// reference so wrapped-mode-specific behavior (stage-emit
|
|
82
|
+
// suppression to avoid double-emit on layout CE; framework-error
|
|
83
|
+
// FSM input forwarding) can branch off it. `null` means
|
|
84
|
+
// standalone — no upstream provider responded — and the toolkit
|
|
85
|
+
// keeps its existing pre-PR-6 behavior. The reference is set once
|
|
86
|
+
// per cohort by the cross-CE consumer effect below; it never
|
|
87
|
+
// becomes a substitute for `sectionEngine` (the local engine
|
|
88
|
+
// remains the controller-side facade for `register`,
|
|
89
|
+
// `handleContent*`, `initialize`, etc.). PR 7+ will collapse the
|
|
90
|
+
// local controller-side surface onto the upstream engine.
|
|
91
|
+
let upstreamEngine = $.state(null);
|
|
92
|
+
|
|
93
|
+
// Per-CE-instance framework-error bus. Shared with the owned
|
|
94
|
+
// `ToolkitCoordinator` so coordinator-side failures (provider-init,
|
|
95
|
+
// tts-init, ...) flow through the same fan-out as CE-side failures
|
|
96
|
+
// (coordinator-init, runtime-init, runtime-dispose). The bus is the
|
|
97
|
+
// single source for the canonical `onFrameworkError` prop, the
|
|
98
|
+
// `framework-error` DOM event, the banner UI, and the
|
|
99
|
+
// `subscribeFrameworkErrors` API on the coordinator.
|
|
100
|
+
const frameworkErrorBus = new FrameworkErrorBus();
|
|
101
|
+
|
|
71
102
|
const DEFAULT_ENV = { mode: "gather", role: "student" };
|
|
72
103
|
|
|
73
104
|
const DEFAULT_ITEM_PLAYER_BY_TYPE = {
|
|
@@ -81,27 +112,46 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
81
112
|
section = $.prop($$props, 'section', 7, null),
|
|
82
113
|
sectionId = $.prop($$props, 'sectionId', 7, ""),
|
|
83
114
|
attemptId = $.prop($$props, 'attemptId', 7, ""),
|
|
84
|
-
view = $.prop($$props, 'view', 7, "candidate"),
|
|
85
115
|
env = $.prop($$props, 'env', 23, () => ({})),
|
|
86
116
|
lazyInit = $.prop($$props, 'lazyInit', 7, true),
|
|
87
117
|
toolConfigStrictness = $.prop($$props, 'toolConfigStrictness', 7, "error"),
|
|
88
118
|
tools = $.prop($$props, 'tools', 23, () => ({})),
|
|
119
|
+
enabledTools = $.prop($$props, 'enabledTools', 7, ""),
|
|
89
120
|
toolRegistry = $.prop($$props, 'toolRegistry', 7, null),
|
|
90
121
|
accessibility = $.prop($$props, 'accessibility', 23, () => ({})),
|
|
91
122
|
player = $.prop($$props, 'player', 7, null),
|
|
92
123
|
playerType = $.prop($$props, 'playerType', 7, ""),
|
|
93
124
|
coordinator = $.prop($$props, 'coordinator', 7, null),
|
|
94
125
|
createSectionController = $.prop($$props, 'createSectionController', 7, null),
|
|
95
|
-
frameworkErrorCallback = $.prop($$props, 'frameworkErrorHook', 7, null),
|
|
96
126
|
onFrameworkError = $.prop($$props, 'onFrameworkError', 7, null),
|
|
97
|
-
onframeworkerror = $.prop($$props, 'onframeworkerror', 7, null),
|
|
98
127
|
errorRenderer = $.prop($$props, 'errorRenderer', 7, null),
|
|
128
|
+
onStageChange = $.prop($$props, 'onStageChange', 7, null),
|
|
129
|
+
// M8 PR 2 — additive Tool Policy Engine inputs. See the
|
|
130
|
+
// `<svelte:options>` props block above for the rationale.
|
|
131
|
+
// `qtiEnforcement` accepts `"on"`, `"off"`, or `null` (auto —
|
|
132
|
+
// the coordinator infers the effective mode from QTI material
|
|
133
|
+
// on the bound `assessment` / `currentItemRef`). Embedded
|
|
134
|
+
// under `<pie-section-player-*>` the same override flows via
|
|
135
|
+
// `runtime.tools.qtiEnforcement`; the effect below falls back
|
|
136
|
+
// to `tools.qtiEnforcement` when the explicit prop is `null`
|
|
137
|
+
// so both entry points converge on the same coordinator call.
|
|
138
|
+
assessment = $.prop($$props, 'assessment', 7, null),
|
|
139
|
+
currentItemRef = $.prop($$props, 'currentItemRef', 7, null),
|
|
140
|
+
qtiEnforcement = $.prop($$props, 'qtiEnforcement', 7, null),
|
|
99
141
|
isolation = $.prop($$props, 'isolation', 7, "inherit");
|
|
100
142
|
|
|
101
143
|
let anchor = $.state(null);
|
|
102
144
|
let ownedCoordinator = $.state(null);
|
|
103
145
|
let inheritedRuntime = $.state(null);
|
|
104
|
-
|
|
146
|
+
|
|
147
|
+
// `lastOwnership` is a self-comparison latch read and written only by
|
|
148
|
+
// the ownership-change `$effect` below. Keeping it as `$state` would
|
|
149
|
+
// make Svelte treat the same-effect read+write as a reactivity loop
|
|
150
|
+
// (effect_update_depth_exceeded). It does not feed any other reactive
|
|
151
|
+
// consumer, so a plain `let` is safer and matches the canonical Svelte
|
|
152
|
+
// 5 latch pattern from `.cursor/rules/svelte-subscription-safety.mdc`.
|
|
153
|
+
let lastOwnership = null;
|
|
154
|
+
|
|
105
155
|
let provider = null;
|
|
106
156
|
let contextRoot = null;
|
|
107
157
|
let hostRuntimeProvider = null;
|
|
@@ -112,8 +162,17 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
112
162
|
let frameworkErrorModel = $.state(null);
|
|
113
163
|
let frameworkErrorTitle = $.state("Unable to initialize assessment toolkit.");
|
|
114
164
|
let frameworkErrorDetails = $.state($.proxy([]));
|
|
115
|
-
|
|
116
|
-
|
|
165
|
+
|
|
166
|
+
// Self-comparison latches for the framework-error redelivery `$effect`
|
|
167
|
+
// and the owned-coordinator bootstrap `$effect` below. Same rationale
|
|
168
|
+
// as `lastOwnership`: keeping these as `$state` made the owning
|
|
169
|
+
// effects read+write the same source in a single pass, tripping
|
|
170
|
+
// `effect_update_depth_exceeded`. Neither value is read by any other
|
|
171
|
+
// reactive consumer, so plain `let` removes the loop without dropping
|
|
172
|
+
// any subscriber.
|
|
173
|
+
let deliveredFrameworkErrorKey = "";
|
|
174
|
+
|
|
175
|
+
let lastOwnedBootstrapFailureKey = "";
|
|
117
176
|
let lastCompositionRevisionKey = $.state("");
|
|
118
177
|
let pendingCompositionModel = null;
|
|
119
178
|
let pendingCompositionEmit = false;
|
|
@@ -121,6 +180,79 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
121
180
|
let pendingCrossBoundaryEvents = [];
|
|
122
181
|
const sessionEmitPolicyState = createSessionEmitPolicyState();
|
|
123
182
|
|
|
183
|
+
// M6 canonical stage tracker. Post-retro the toolkit applies the
|
|
184
|
+
// same four-stage canonical list (`composed`, `engine-ready`,
|
|
185
|
+
// `interactive`, `disposed`) as every layout CE; subscriber
|
|
186
|
+
// iteration order stays stable across `<pie-section-player-*>` and
|
|
187
|
+
// `<pie-assessment-toolkit>` CE shapes. Cohort changes
|
|
188
|
+
// (`(sectionId, attemptId)`) reset the tracker so the new cohort
|
|
189
|
+
// begins emitting from `composed`. Initial seed values are captured
|
|
190
|
+
// via `untrack` because the tracker explicitly absorbs subsequent
|
|
191
|
+
// cohort changes through `reset()`, not through prop reactivity at
|
|
192
|
+
// construction time.
|
|
193
|
+
const stageTracker = createStageTracker({
|
|
194
|
+
sourceCe: "pie-assessment-toolkit",
|
|
195
|
+
sourceCeShape: "toolkit",
|
|
196
|
+
runtimeId,
|
|
197
|
+
sectionId: untrack(() => sectionId() || undefined),
|
|
198
|
+
attemptId: untrack(() => attemptId() || undefined),
|
|
199
|
+
emit: (detail) => {
|
|
200
|
+
// M7 PR 6: route the toolkit's stage-emit through the
|
|
201
|
+
// extracted `runStageEmitWithSuppression` helper so the
|
|
202
|
+
// wrapped/standalone gating logic is testable in isolation
|
|
203
|
+
// (see `tests/runtime/stage-emit-gate.test.ts`). The
|
|
204
|
+
// thunks below preserve pre-PR-6 semantics:
|
|
205
|
+
//
|
|
206
|
+
// - `isSuppressed`: when wrapped by a section-player
|
|
207
|
+
// layout the kernel's engine emits the canonical
|
|
208
|
+
// `pie-stage-change` chain on the layout CE host and
|
|
209
|
+
// invokes the runtime-tier `onStageChange` callback
|
|
210
|
+
// itself. Suppressing here prevents the toolkit CE
|
|
211
|
+
// from also emitting (which would bubble through
|
|
212
|
+
// `composed: true` to the layout CE host and produce a
|
|
213
|
+
// duplicate emission for outside listeners). The
|
|
214
|
+
// internal stage-tracker latch state advances before
|
|
215
|
+
// `opts.emit` runs, so suppression only short-circuits
|
|
216
|
+
// externally-visible side effects — the toolkit's own
|
|
217
|
+
// readiness path (`waitUntilReady` / `engine-ready` /
|
|
218
|
+
// `interactive`) stays correct in standalone mode and
|
|
219
|
+
// degenerates into a series of suppressed emits in
|
|
220
|
+
// wrapped mode without losing any latch transitions.
|
|
221
|
+
//
|
|
222
|
+
// The reactive `upstreamEngine` is read inside the
|
|
223
|
+
// thunk (i.e. at emit time, not at construction time)
|
|
224
|
+
// and all callers of `stageTracker.enter(...)` are
|
|
225
|
+
// themselves wrapped in `untrack(...)` (see effects
|
|
226
|
+
// below), so the read does not feed back into any
|
|
227
|
+
// tracked dependency graph.
|
|
228
|
+
//
|
|
229
|
+
// - `getOnStageChange`: the prop is read on every emit
|
|
230
|
+
// so reassignments (cohort change, host swap) always
|
|
231
|
+
// reach the latest handler.
|
|
232
|
+
runStageEmitWithSuppression(
|
|
233
|
+
{
|
|
234
|
+
isSuppressed: () => $.get(upstreamEngine) !== null,
|
|
235
|
+
dispatchDomEvent: (d) => emit("pie-stage-change", d),
|
|
236
|
+
getOnStageChange: () => onStageChange()
|
|
237
|
+
},
|
|
238
|
+
detail
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
let lastStageCohortKey = $.state($.proxy(untrack(() => `${sectionId()}|${attemptId()}`)));
|
|
244
|
+
let composedStageEntered = $.state(false);
|
|
245
|
+
let engineReadyStageEntered = $.state(false);
|
|
246
|
+
let interactiveStageEntered = $.state(false);
|
|
247
|
+
|
|
248
|
+
// `sectionInitialized` is the second of two preconditions for the
|
|
249
|
+
// `interactive` stage emission. The toolkit's interactive entrance
|
|
250
|
+
// requires *both* `engine-ready` (coordinator bring-up settled) and
|
|
251
|
+
// `sectionInitialized` (section composition + controller hydrated)
|
|
252
|
+
// before it fires. A reactive effect below joins them so neither
|
|
253
|
+
// race ordering silently drops a stage.
|
|
254
|
+
let sectionInitialized = $.state(false);
|
|
255
|
+
|
|
124
256
|
function getHostElement() {
|
|
125
257
|
if (!$.get(anchor)) return null;
|
|
126
258
|
|
|
@@ -200,6 +332,20 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
200
332
|
}
|
|
201
333
|
}
|
|
202
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Build a `FrameworkErrorModel` and publish it on this CE's
|
|
337
|
+
* framework-error bus.
|
|
338
|
+
*
|
|
339
|
+
* All delivery side-effects (banner UI, `framework-error` DOM event,
|
|
340
|
+
* hook delivery, console logging) live in the bus subscriber
|
|
341
|
+
* registered below — keeping this helper a thin publish-side wrapper
|
|
342
|
+
* that CE-internal callers can use.
|
|
343
|
+
*
|
|
344
|
+
* Coordinator-side failures (provider-init, tts-init, ...) reach the
|
|
345
|
+
* same subscriber by way of the shared bus (passed into the owned
|
|
346
|
+
* coordinator via `buildOwnedCoordinator`), so consumers see one
|
|
347
|
+
* canonical fan-out per error.
|
|
348
|
+
*/
|
|
203
349
|
function reportFrameworkError(args) {
|
|
204
350
|
const model = frameworkErrorFromUnknown({
|
|
205
351
|
kind: args.kind,
|
|
@@ -208,49 +354,69 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
208
354
|
recoverable: args.recoverable
|
|
209
355
|
});
|
|
210
356
|
|
|
211
|
-
|
|
357
|
+
frameworkErrorBus.reportFrameworkError(model);
|
|
212
358
|
|
|
213
|
-
|
|
359
|
+
return model;
|
|
360
|
+
}
|
|
214
361
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Whether this CE should surface the bootstrap banner for `kind`.
|
|
364
|
+
*
|
|
365
|
+
* The banner is the visible "we could not start the toolkit" surface
|
|
366
|
+
* and is intentionally narrower than the canonical contract: it only
|
|
367
|
+
* fires for fatal CE-bootstrap kinds (`coordinator-init`,
|
|
368
|
+
* `runtime-init`, `tool-config`). Coordinator-internal failures
|
|
369
|
+
* (provider-init, tts-init, ...) still flow through the canonical
|
|
370
|
+
* hook + DOM event but do not replace the toolkit content with an
|
|
371
|
+
* error card — they are degradations, not bootstrap failures.
|
|
372
|
+
*/
|
|
373
|
+
function isBootstrapKind(kind) {
|
|
374
|
+
return kind === "coordinator-init" || kind === "runtime-init" || kind === "tool-config";
|
|
375
|
+
}
|
|
220
376
|
|
|
221
|
-
|
|
377
|
+
function deliverFrameworkErrorHook(model) {
|
|
378
|
+
if (!onFrameworkError()) return;
|
|
222
379
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
console.error(`[pie-framework:runtime-init:pie-assessment-toolkit] framework error hook failed`, hookError);
|
|
229
|
-
}
|
|
380
|
+
try {
|
|
381
|
+
onFrameworkError()(model);
|
|
382
|
+
deliveredFrameworkErrorKey = `${model.kind}|${model.source}|${model.message}`;
|
|
383
|
+
} catch(hookError) {
|
|
384
|
+
console.error(`[pie-framework:${model.kind}:${model.source}] framework error hook failed`, hookError);
|
|
230
385
|
}
|
|
231
|
-
|
|
232
|
-
return model;
|
|
233
386
|
}
|
|
234
387
|
|
|
235
388
|
$.user_effect(() => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
389
|
+
const detach = frameworkErrorBus.subscribeFrameworkErrors((model) => {
|
|
390
|
+
console.error(formatFrameworkErrorForConsole(model), model.cause);
|
|
239
391
|
|
|
240
|
-
|
|
392
|
+
if (isBootstrapKind(model.kind)) {
|
|
393
|
+
const rendered = applyErrorRenderer(model);
|
|
241
394
|
|
|
242
|
-
|
|
395
|
+
$.set(frameworkErrorModel, model, true);
|
|
396
|
+
$.set(frameworkErrorTitle, rendered.title, true);
|
|
397
|
+
$.set(frameworkErrorDetails, rendered.details, true);
|
|
398
|
+
}
|
|
243
399
|
|
|
244
|
-
|
|
400
|
+
emit("framework-error", model);
|
|
401
|
+
deliverFrameworkErrorHook(model);
|
|
402
|
+
});
|
|
245
403
|
|
|
246
|
-
|
|
404
|
+
return () => {
|
|
405
|
+
detach();
|
|
406
|
+
};
|
|
407
|
+
});
|
|
247
408
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
409
|
+
$.user_effect(() => {
|
|
410
|
+
if (!$.get(frameworkErrorModel)) return;
|
|
411
|
+
|
|
412
|
+
const frameworkErrorKey = `${$.get(frameworkErrorModel).kind}|${$.get(frameworkErrorModel).source}|${$.get(frameworkErrorModel).message}`;
|
|
413
|
+
|
|
414
|
+
if (deliveredFrameworkErrorKey === frameworkErrorKey) return;
|
|
415
|
+
|
|
416
|
+
// Hook prop became available after the framework-error model was
|
|
417
|
+
// already produced (e.g. host wired it asynchronously). Re-deliver
|
|
418
|
+
// once for the current model so late-binding hosts see the error.
|
|
419
|
+
deliverFrameworkErrorHook($.get(frameworkErrorModel));
|
|
254
420
|
});
|
|
255
421
|
|
|
256
422
|
function hashString(input) {
|
|
@@ -392,8 +558,41 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
392
558
|
throw new Error("pie-assessment-toolkit requires createSectionController when no coordinator hook provides one");
|
|
393
559
|
}
|
|
394
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Merge the `enabled-tools` shorthand into a `tools.placement.section`
|
|
563
|
+
* list, leaving the `tools` object form authoritative when both are
|
|
564
|
+
* provided. Mirrors `resolveToolsConfig` in the section-player layer
|
|
565
|
+
* so direct toolkit hosts get the same easy-tier behavior.
|
|
566
|
+
*
|
|
567
|
+
* Precedence (highest first):
|
|
568
|
+
* 1. Explicit `tools.placement.section` array on the `tools` prop
|
|
569
|
+
* 2. `enabled-tools` shorthand attribute / `enabledTools` prop
|
|
570
|
+
*
|
|
571
|
+
* Section-player CEs already merge before passing `tools` here, so for
|
|
572
|
+
* the embedded path this helper is a no-op in practice; it only fires
|
|
573
|
+
* when a host mounts `<pie-assessment-toolkit>` directly.
|
|
574
|
+
*/
|
|
575
|
+
function buildEffectiveToolsInput() {
|
|
576
|
+
const baseTools = tools() || {};
|
|
577
|
+
const sectionShorthand = parseToolList(enabledTools());
|
|
578
|
+
|
|
579
|
+
if (sectionShorthand.length === 0) return baseTools;
|
|
580
|
+
|
|
581
|
+
const placement = baseTools.placement || {};
|
|
582
|
+
const explicitSectionPlacement = Array.isArray(placement.section) ? placement.section : null;
|
|
583
|
+
|
|
584
|
+
if (explicitSectionPlacement && explicitSectionPlacement.length > 0) {
|
|
585
|
+
return baseTools;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
...baseTools,
|
|
590
|
+
placement: { ...placement, section: sectionShorthand }
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
395
594
|
function validateToolsConfigForBootstrap() {
|
|
396
|
-
return normalizeAndValidateToolsConfig(
|
|
595
|
+
return normalizeAndValidateToolsConfig(buildEffectiveToolsInput(), {
|
|
397
596
|
strictness: toolConfigStrictness(),
|
|
398
597
|
source: "pie-assessment-toolkit.bootstrap",
|
|
399
598
|
toolRegistry: toolRegistry()
|
|
@@ -404,7 +603,7 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
404
603
|
let toolsSignature = "";
|
|
405
604
|
|
|
406
605
|
try {
|
|
407
|
-
toolsSignature = JSON.stringify(
|
|
606
|
+
toolsSignature = JSON.stringify(buildEffectiveToolsInput());
|
|
408
607
|
} catch {
|
|
409
608
|
toolsSignature = "[unserializable-tools]";
|
|
410
609
|
}
|
|
@@ -416,6 +615,39 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
416
615
|
].join("|");
|
|
417
616
|
}
|
|
418
617
|
|
|
618
|
+
// Coerce the public `qti-enforcement` CE attribute (string or null)
|
|
619
|
+
// into the engine's `QtiEnforcementMode | null` contract. Anything
|
|
620
|
+
// outside the canonical "on" / "off" set — typos like "ON",
|
|
621
|
+
// missing-attribute artifacts like "" or "null" strings — collapses
|
|
622
|
+
// to `null` (auto-mode) instead of silently degrading inside the
|
|
623
|
+
// engine. This is the single canonical entry point for the prop;
|
|
624
|
+
// keep all `setQtiEnforcement(...)` calls in this file routed
|
|
625
|
+
// through it.
|
|
626
|
+
function coerceQtiEnforcement(value) {
|
|
627
|
+
return value === "on" || value === "off" ? value : null;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Resolve the effective override that flows into the coordinator.
|
|
631
|
+
// The explicit `qti-enforcement` attribute (standalone path) wins
|
|
632
|
+
// over `tools.qtiEnforcement` (embedded path via
|
|
633
|
+
// `runtime.tools.qtiEnforcement`); both fall back to `null`
|
|
634
|
+
// (auto-mode) so the coordinator's
|
|
635
|
+
// {@link resolveDefaultQtiEnforcement} helper runs on the bound
|
|
636
|
+
// assessment / item ref.
|
|
637
|
+
function resolveQtiEnforcementInput(explicit, toolsConfig) {
|
|
638
|
+
const explicitMode = coerceQtiEnforcement(explicit);
|
|
639
|
+
|
|
640
|
+
if (explicitMode) return explicitMode;
|
|
641
|
+
|
|
642
|
+
if (toolsConfig && typeof toolsConfig === "object") {
|
|
643
|
+
const candidate = toolsConfig.qtiEnforcement;
|
|
644
|
+
|
|
645
|
+
return coerceQtiEnforcement(typeof candidate === "string" ? candidate : null);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
|
|
419
651
|
function buildOwnedCoordinator(validatedTools) {
|
|
420
652
|
const fallbackAssessmentId = assessmentId() || section()?.identifier || `assessment-${Math.random().toString(16).slice(2)}`;
|
|
421
653
|
|
|
@@ -426,7 +658,8 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
426
658
|
deferToolConfigValidation: true,
|
|
427
659
|
tools: validatedTools,
|
|
428
660
|
toolRegistry: toolRegistry(),
|
|
429
|
-
accessibility: accessibility()
|
|
661
|
+
accessibility: accessibility(),
|
|
662
|
+
frameworkErrorBus
|
|
430
663
|
});
|
|
431
664
|
}
|
|
432
665
|
|
|
@@ -438,52 +671,69 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
438
671
|
return coordinator() || $.get(ownedCoordinator);
|
|
439
672
|
});
|
|
440
673
|
|
|
674
|
+
// Owned-coordinator bootstrap. The effect must re-run when ownership
|
|
675
|
+
// inputs (`host`, `coordinator`, `isolation`, `inheritedRuntime`)
|
|
676
|
+
// change so the toolkit can swap between owned, passed-in, and
|
|
677
|
+
// inherited coordinators. It must *not* re-run on its own writes to
|
|
678
|
+
// `ownedCoordinator` / `lastOwnedBootstrapFailureKey` /
|
|
679
|
+
// `frameworkError*` / `runtimeError` — those self-mutations were the
|
|
680
|
+
// observed source of the `effect_update_depth_exceeded` warnings in
|
|
681
|
+
// the assessment-player smoke flow. We therefore explicitly track
|
|
682
|
+
// only the ownership inputs and run the bootstrap body inside
|
|
683
|
+
// `untrack`, matching `.cursor/rules/svelte-subscription-safety.mdc`.
|
|
441
684
|
$.user_effect(() => {
|
|
442
|
-
|
|
685
|
+
void $.get(host);
|
|
686
|
+
void coordinator();
|
|
687
|
+
void isolation();
|
|
688
|
+
void $.get(inheritedRuntime);
|
|
443
689
|
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
$.set(ownedCoordinator, null);
|
|
447
|
-
}
|
|
690
|
+
untrack(() => {
|
|
691
|
+
if (!$.get(host)) return;
|
|
448
692
|
|
|
449
|
-
|
|
450
|
-
|
|
693
|
+
if (coordinator()) {
|
|
694
|
+
if ($.get(ownedCoordinator)) {
|
|
695
|
+
$.set(ownedCoordinator, null);
|
|
696
|
+
}
|
|
451
697
|
|
|
452
|
-
|
|
453
|
-
if ($.get(ownedCoordinator)) {
|
|
454
|
-
$.set(ownedCoordinator, null);
|
|
698
|
+
return;
|
|
455
699
|
}
|
|
456
700
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const failureKey = getOwnedBootstrapFailureKey();
|
|
701
|
+
if (isolation() !== "force" && $.get(inheritedRuntime)?.coordinator) {
|
|
702
|
+
if ($.get(ownedCoordinator)) {
|
|
703
|
+
$.set(ownedCoordinator, null);
|
|
704
|
+
}
|
|
462
705
|
|
|
463
|
-
if ($.get(lastOwnedBootstrapFailureKey) === failureKey) {
|
|
464
706
|
return;
|
|
465
707
|
}
|
|
466
708
|
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
$.set(ownedCoordinator, buildOwnedCoordinator(validatedTools), true);
|
|
471
|
-
$.set(lastOwnedBootstrapFailureKey, "");
|
|
472
|
-
$.set(frameworkErrorModel, null);
|
|
473
|
-
$.set(frameworkErrorTitle, "Unable to initialize assessment toolkit.");
|
|
474
|
-
$.set(frameworkErrorDetails, [], true);
|
|
475
|
-
} catch(error) {
|
|
476
|
-
$.set(runtimeError, error, true);
|
|
477
|
-
$.set(ownedCoordinator, null);
|
|
478
|
-
$.set(lastOwnedBootstrapFailureKey, failureKey, true);
|
|
709
|
+
if (!$.get(ownedCoordinator)) {
|
|
710
|
+
const failureKey = getOwnedBootstrapFailureKey();
|
|
479
711
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
712
|
+
if (lastOwnedBootstrapFailureKey === failureKey) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
try {
|
|
717
|
+
const validatedTools = validateToolsConfigForBootstrap();
|
|
718
|
+
|
|
719
|
+
$.set(ownedCoordinator, buildOwnedCoordinator(validatedTools), true);
|
|
720
|
+
lastOwnedBootstrapFailureKey = "";
|
|
721
|
+
$.set(frameworkErrorModel, null);
|
|
722
|
+
$.set(frameworkErrorTitle, "Unable to initialize assessment toolkit.");
|
|
723
|
+
$.set(frameworkErrorDetails, [], true);
|
|
724
|
+
} catch(error) {
|
|
725
|
+
$.set(runtimeError, error, true);
|
|
726
|
+
$.set(ownedCoordinator, null);
|
|
727
|
+
lastOwnedBootstrapFailureKey = failureKey;
|
|
728
|
+
|
|
729
|
+
reportFrameworkError({
|
|
730
|
+
kind: "coordinator-init",
|
|
731
|
+
source: "pie-assessment-toolkit",
|
|
732
|
+
error
|
|
733
|
+
});
|
|
734
|
+
}
|
|
485
735
|
}
|
|
486
|
-
}
|
|
736
|
+
});
|
|
487
737
|
});
|
|
488
738
|
|
|
489
739
|
const effectiveAssessmentId = $.derived(() => assessmentId() || $.get(effectiveCoordinator)?.assessmentId || "");
|
|
@@ -626,12 +876,33 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
626
876
|
});
|
|
627
877
|
});
|
|
628
878
|
|
|
879
|
+
// M7 PR 6: cross-CE engine bridge consumer. When the toolkit CE
|
|
880
|
+
// renders inside a section-player layout, the kernel publishes its
|
|
881
|
+
// `SectionRuntimeEngine` via `sectionRuntimeEngineHostContext` on
|
|
882
|
+
// the layout CE host. The consumer's `context-request` bubbles up
|
|
883
|
+
// across the toolkit's shadow boundary, the kernel's provider
|
|
884
|
+
// answers, and we record the upstream reference. When standalone,
|
|
885
|
+
// no provider responds within the consumer's retry window and
|
|
886
|
+
// `upstreamEngine` stays `null` — that is the standalone path's
|
|
887
|
+
// contract. `isolation === "force"` does not opt out of this
|
|
888
|
+
// bridge: it is a runtime-engine seam, not a coordinator-isolation
|
|
889
|
+
// seam, and `force` still wants the canonical engine path.
|
|
890
|
+
$.user_effect(() => {
|
|
891
|
+
if (!$.get(host)) return;
|
|
892
|
+
|
|
893
|
+
return connectSectionRuntimeEngineHostContext($.get(host), (value) => {
|
|
894
|
+
if (value.engine === $.get(upstreamEngine)) return;
|
|
895
|
+
|
|
896
|
+
$.set(upstreamEngine, value.engine, true);
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
|
|
629
900
|
$.user_effect(() => {
|
|
630
901
|
const parentRuntimeId = isolation() !== "force" && $.get(inheritedRuntime) ? $.get(inheritedRuntime).runtimeId : null;
|
|
631
902
|
const ownership = parentRuntimeId ? "inherited" : "owned";
|
|
632
903
|
|
|
633
|
-
if (ownership !==
|
|
634
|
-
|
|
904
|
+
if (ownership !== lastOwnership) {
|
|
905
|
+
lastOwnership = ownership;
|
|
635
906
|
emit(ownership === "inherited" ? "runtime-inherited" : "runtime-owned", { runtimeId, parentRuntimeId });
|
|
636
907
|
}
|
|
637
908
|
});
|
|
@@ -723,6 +994,62 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
723
994
|
});
|
|
724
995
|
});
|
|
725
996
|
|
|
997
|
+
// M8 — push Tool Policy Engine inputs (`assessment`,
|
|
998
|
+
// `currentItemRef`, `qtiEnforcement`) into the toolkit-owned
|
|
999
|
+
// coordinator whenever they change. The coordinator's
|
|
1000
|
+
// `updateAssessment` / `updateCurrentItemRef` / `setQtiEnforcement`
|
|
1001
|
+
// forwards land on `ToolPolicyEngine.updateInputs`, which value-
|
|
1002
|
+
// diffs each key with `Object.is` and only emits an `inputs` event
|
|
1003
|
+
// on real changes — so re-running this effect on coordinator swap
|
|
1004
|
+
// is safe and self-idempotent. We touch the reactive props
|
|
1005
|
+
// explicitly and then run the side effect inside `untrack(...)`
|
|
1006
|
+
// per `.cursor/rules/svelte-subscription-safety.mdc`.
|
|
1007
|
+
//
|
|
1008
|
+
// `qtiEnforcement` resolves through {@link resolveQtiEnforcementInput}
|
|
1009
|
+
// so the embedded path (`<pie-section-player-*>` setting
|
|
1010
|
+
// `runtime.tools.qtiEnforcement`) and the standalone path (the
|
|
1011
|
+
// explicit `qti-enforcement` attribute on `<pie-assessment-toolkit>`)
|
|
1012
|
+
// converge on a single coordinator call. The explicit prop wins
|
|
1013
|
+
// over `tools.qtiEnforcement` so a host can pin a stricter mode
|
|
1014
|
+
// without rewriting the runtime overlay.
|
|
1015
|
+
//
|
|
1016
|
+
// CRITICAL: only push when the toolkit *owns* the coordinator
|
|
1017
|
+
// (i.e. `effectiveCoordinator === ownedCoordinator`). When the
|
|
1018
|
+
// host passes a coordinator via the `coordinator` prop or shares
|
|
1019
|
+
// one through `assessmentToolkitHostRuntimeContext`, that
|
|
1020
|
+
// coordinator's policy inputs are the host's contract — overwriting
|
|
1021
|
+
// them with our prop defaults would silently null out a host's
|
|
1022
|
+
// pre-bound `AssessmentEntity`, etc. Hosts that share a coordinator
|
|
1023
|
+
// drive policy inputs directly via `coord.updateAssessment(...)`.
|
|
1024
|
+
$.user_effect(() => {
|
|
1025
|
+
void assessment();
|
|
1026
|
+
void currentItemRef();
|
|
1027
|
+
void qtiEnforcement();
|
|
1028
|
+
void tools();
|
|
1029
|
+
|
|
1030
|
+
const coord = $.get(effectiveCoordinator);
|
|
1031
|
+
|
|
1032
|
+
if (!coord) return;
|
|
1033
|
+
|
|
1034
|
+
// Host-owned coordinators are off-limits for this effect.
|
|
1035
|
+
if (coord !== $.get(ownedCoordinator)) return;
|
|
1036
|
+
|
|
1037
|
+
untrack(() => {
|
|
1038
|
+
// Apply order matters: `setQtiEnforcement` lands the override
|
|
1039
|
+
// (or clears it) FIRST so `updateAssessment(...)`'s
|
|
1040
|
+
// auto-promote path sees the final override and doesn't
|
|
1041
|
+
// briefly resolve to "on" (when binding an assessment with
|
|
1042
|
+
// `qtiEnforcement="off"`) or "off" (when clearing an
|
|
1043
|
+
// assessment with `qtiEnforcement="on"`). Without this
|
|
1044
|
+
// ordering we would emit a transient wrong-state policy
|
|
1045
|
+
// event between the calls.
|
|
1046
|
+
coord.setQtiEnforcement(resolveQtiEnforcementInput(qtiEnforcement(), tools()));
|
|
1047
|
+
|
|
1048
|
+
coord.updateAssessment(assessment());
|
|
1049
|
+
coord.updateCurrentItemRef(currentItemRef());
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
|
|
726
1053
|
$.user_effect(() => {
|
|
727
1054
|
if (!$.get(effectiveCoordinator)) return;
|
|
728
1055
|
|
|
@@ -730,7 +1057,11 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
730
1057
|
if (!isInstrumentationProvider($.get(instrumentationProvider))) return;
|
|
731
1058
|
if (!$.get(instrumentationProvider).isReady()) return;
|
|
732
1059
|
|
|
733
|
-
|
|
1060
|
+
// Telemetry event names are prefixed at the emit site in
|
|
1061
|
+
// `ToolkitCoordinator.emitTelemetry`. See the JSDoc on that
|
|
1062
|
+
// method for the namespace convention. No fallback here.
|
|
1063
|
+
const instrumentationEventName = eventName;
|
|
1064
|
+
|
|
734
1065
|
const timestamp = new Date().toISOString();
|
|
735
1066
|
|
|
736
1067
|
const attributes = {
|
|
@@ -792,7 +1123,35 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
792
1123
|
});
|
|
793
1124
|
|
|
794
1125
|
emit("section-ready", { sectionId: $.get(effectiveSectionId) });
|
|
1126
|
+
|
|
1127
|
+
// Mark section initialization complete. The reactive
|
|
1128
|
+
// effect below joins this with `engineReadyStageEntered`
|
|
1129
|
+
// so `interactive` only fires once the canonical
|
|
1130
|
+
// predecessor is in. Direct emit here would race with
|
|
1131
|
+
// `waitUntilReady()` and silently auto-skip
|
|
1132
|
+
// `engine-ready` on fast hydration paths.
|
|
1133
|
+
untrack(() => {
|
|
1134
|
+
$.set(sectionInitialized, true);
|
|
1135
|
+
});
|
|
795
1136
|
}).catch((error) => {
|
|
1137
|
+
untrack(() => {
|
|
1138
|
+
// If `engine-ready` has not latched yet, mark it
|
|
1139
|
+
// explicitly skipped so subscribers see a monotonic
|
|
1140
|
+
// chain (`composed → engine-ready:skipped →
|
|
1141
|
+
// interactive:failed`) instead of `engine-ready`
|
|
1142
|
+
// silently disappearing from the canonical stream.
|
|
1143
|
+
// `waitUntilReady()` is still in flight at this
|
|
1144
|
+
// point; `engineReadyStageEntered` guards its
|
|
1145
|
+
// resolver so a late success cannot regress past
|
|
1146
|
+
// the recorded skip.
|
|
1147
|
+
if (!$.get(engineReadyStageEntered)) {
|
|
1148
|
+
$.set(engineReadyStageEntered, true);
|
|
1149
|
+
stageTracker.enter("engine-ready", "skipped");
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
stageTracker.enter("interactive", "failed");
|
|
1153
|
+
});
|
|
1154
|
+
|
|
796
1155
|
$.set(runtimeError, error, true);
|
|
797
1156
|
sectionEngine.reportSectionError({ source: "section-runtime", error, timestamp: Date.now() });
|
|
798
1157
|
|
|
@@ -955,6 +1314,122 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
955
1314
|
await sectionEngine.hydrate();
|
|
956
1315
|
}
|
|
957
1316
|
|
|
1317
|
+
// Stage: `disposed` — wired on CE unmount. The four-stage retro
|
|
1318
|
+
// dropped `attached` (it had no consumers); the toolkit no longer
|
|
1319
|
+
// emits anything on mount and starts the canonical chain at
|
|
1320
|
+
// `composed` once the section composition lands. `disposed` still
|
|
1321
|
+
// fires on CE teardown so subscribers see a monotonic close-out.
|
|
1322
|
+
$.user_effect(() => {
|
|
1323
|
+
return () => {
|
|
1324
|
+
untrack(() => {
|
|
1325
|
+
if (stageTracker.getCurrent() !== null) {
|
|
1326
|
+
stageTracker.enter("disposed");
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
};
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
// Cohort change handler: when `(sectionId, attemptId)` changes, emit
|
|
1333
|
+
// `disposed` for the outgoing cohort and reset the tracker so the
|
|
1334
|
+
// new cohort begins emitting from `composed` against the same DOM
|
|
1335
|
+
// element. The boolean stage flags must reset in lockstep.
|
|
1336
|
+
$.user_effect(() => {
|
|
1337
|
+
const nextKey = `${sectionId()}|${attemptId()}`;
|
|
1338
|
+
|
|
1339
|
+
untrack(() => {
|
|
1340
|
+
if (nextKey === $.get(lastStageCohortKey)) return;
|
|
1341
|
+
|
|
1342
|
+
if (stageTracker.getCurrent() !== null) {
|
|
1343
|
+
stageTracker.enter("disposed");
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
stageTracker.reset({
|
|
1347
|
+
sectionId: sectionId() || undefined,
|
|
1348
|
+
attemptId: attemptId() || undefined
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
$.set(lastStageCohortKey, nextKey);
|
|
1352
|
+
$.set(composedStageEntered, false);
|
|
1353
|
+
$.set(engineReadyStageEntered, false);
|
|
1354
|
+
$.set(interactiveStageEntered, false);
|
|
1355
|
+
$.set(sectionInitialized, false);
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
// `interactive` fires once both canonical predecessors land:
|
|
1360
|
+
// `engine-ready` (coordinator bring-up settled) and
|
|
1361
|
+
// `sectionInitialized` (section composition + controller hydrated).
|
|
1362
|
+
// Joining here prevents the race that would otherwise silently
|
|
1363
|
+
// auto-skip `engine-ready` when the section initializer wins.
|
|
1364
|
+
$.user_effect(() => {
|
|
1365
|
+
const ready = $.get(engineReadyStageEntered) && $.get(sectionInitialized);
|
|
1366
|
+
|
|
1367
|
+
untrack(() => {
|
|
1368
|
+
if (!ready) return;
|
|
1369
|
+
if ($.get(interactiveStageEntered)) return;
|
|
1370
|
+
|
|
1371
|
+
$.set(interactiveStageEntered, true);
|
|
1372
|
+
stageTracker.enter("interactive");
|
|
1373
|
+
});
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
// Canonical M6 stage progression for the toolkit CE (post-retro:
|
|
1377
|
+
// 4 stages). The tracker is idempotent and rejects backward
|
|
1378
|
+
// transitions; gating with `composedStageEntered` keeps the emit
|
|
1379
|
+
// order monotonic across asynchronous sources.
|
|
1380
|
+
$.user_effect(() => {
|
|
1381
|
+
const composed = $.get(compositionVersion) > 0 || $.get(compositionModel) !== null;
|
|
1382
|
+
|
|
1383
|
+
untrack(() => {
|
|
1384
|
+
if (!$.get(composedStageEntered) && composed) {
|
|
1385
|
+
$.set(composedStageEntered, true);
|
|
1386
|
+
stageTracker.enter("composed");
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
// `engine-ready` resolves once `effectiveCoordinator.waitUntilReady()`
|
|
1392
|
+
// settles — that promise covers state load, TTS bring-up, and provider
|
|
1393
|
+
// initialization. Failures still flow through the framework-error bus;
|
|
1394
|
+
// here we record the position with `failed` so subscribers see a
|
|
1395
|
+
// monotonic stage chain even when the coordinator throws during
|
|
1396
|
+
// readiness. Gated on `composedStageEntered` so the engine-ready
|
|
1397
|
+
// emission cannot precede `composed` even if the coordinator
|
|
1398
|
+
// resolves before composition lands (rare but possible during
|
|
1399
|
+
// fast-hydration paths).
|
|
1400
|
+
$.user_effect(() => {
|
|
1401
|
+
if (!$.get(effectiveCoordinator)) return;
|
|
1402
|
+
if (!$.get(composedStageEntered)) return;
|
|
1403
|
+
if ($.get(engineReadyStageEntered)) return;
|
|
1404
|
+
|
|
1405
|
+
const coord = $.get(effectiveCoordinator);
|
|
1406
|
+
let cancelled = false;
|
|
1407
|
+
|
|
1408
|
+
void coord.waitUntilReady().then(() => {
|
|
1409
|
+
if (cancelled) return;
|
|
1410
|
+
|
|
1411
|
+
untrack(() => {
|
|
1412
|
+
if ($.get(engineReadyStageEntered)) return;
|
|
1413
|
+
|
|
1414
|
+
$.set(engineReadyStageEntered, true);
|
|
1415
|
+
stageTracker.enter("engine-ready");
|
|
1416
|
+
});
|
|
1417
|
+
}).catch(() => {
|
|
1418
|
+
if (cancelled) return;
|
|
1419
|
+
|
|
1420
|
+
untrack(() => {
|
|
1421
|
+
if ($.get(engineReadyStageEntered)) return;
|
|
1422
|
+
|
|
1423
|
+
$.set(engineReadyStageEntered, true);
|
|
1424
|
+
stageTracker.enter("engine-ready", "failed");
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
return () => {
|
|
1429
|
+
cancelled = true;
|
|
1430
|
+
};
|
|
1431
|
+
});
|
|
1432
|
+
|
|
958
1433
|
$.user_effect(() => {
|
|
959
1434
|
return () => {
|
|
960
1435
|
if (compositionEmitRaf !== null && typeof window !== "undefined") {
|
|
@@ -973,6 +1448,8 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
973
1448
|
error,
|
|
974
1449
|
recoverable: true
|
|
975
1450
|
});
|
|
1451
|
+
}).finally(() => {
|
|
1452
|
+
frameworkErrorBus.dispose();
|
|
976
1453
|
});
|
|
977
1454
|
};
|
|
978
1455
|
});
|
|
@@ -1022,15 +1499,6 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
1022
1499
|
$.flush();
|
|
1023
1500
|
},
|
|
1024
1501
|
|
|
1025
|
-
get view() {
|
|
1026
|
-
return view();
|
|
1027
|
-
},
|
|
1028
|
-
|
|
1029
|
-
set view($$value) {
|
|
1030
|
-
view($$value);
|
|
1031
|
-
$.flush();
|
|
1032
|
-
},
|
|
1033
|
-
|
|
1034
1502
|
get env() {
|
|
1035
1503
|
return env();
|
|
1036
1504
|
},
|
|
@@ -1067,6 +1535,15 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
1067
1535
|
$.flush();
|
|
1068
1536
|
},
|
|
1069
1537
|
|
|
1538
|
+
get enabledTools() {
|
|
1539
|
+
return enabledTools();
|
|
1540
|
+
},
|
|
1541
|
+
|
|
1542
|
+
set enabledTools($$value) {
|
|
1543
|
+
enabledTools($$value);
|
|
1544
|
+
$.flush();
|
|
1545
|
+
},
|
|
1546
|
+
|
|
1070
1547
|
get toolRegistry() {
|
|
1071
1548
|
return toolRegistry();
|
|
1072
1549
|
},
|
|
@@ -1121,39 +1598,57 @@ export default function PieAssessmentToolkit($$anchor, $$props) {
|
|
|
1121
1598
|
$.flush();
|
|
1122
1599
|
},
|
|
1123
1600
|
|
|
1124
|
-
get
|
|
1125
|
-
return
|
|
1601
|
+
get onFrameworkError() {
|
|
1602
|
+
return onFrameworkError();
|
|
1603
|
+
},
|
|
1604
|
+
|
|
1605
|
+
set onFrameworkError($$value) {
|
|
1606
|
+
onFrameworkError($$value);
|
|
1607
|
+
$.flush();
|
|
1608
|
+
},
|
|
1609
|
+
|
|
1610
|
+
get errorRenderer() {
|
|
1611
|
+
return errorRenderer();
|
|
1126
1612
|
},
|
|
1127
1613
|
|
|
1128
|
-
set
|
|
1129
|
-
|
|
1614
|
+
set errorRenderer($$value) {
|
|
1615
|
+
errorRenderer($$value);
|
|
1130
1616
|
$.flush();
|
|
1131
1617
|
},
|
|
1132
1618
|
|
|
1133
|
-
get
|
|
1134
|
-
return
|
|
1619
|
+
get onStageChange() {
|
|
1620
|
+
return onStageChange();
|
|
1135
1621
|
},
|
|
1136
1622
|
|
|
1137
|
-
set
|
|
1138
|
-
|
|
1623
|
+
set onStageChange($$value) {
|
|
1624
|
+
onStageChange($$value);
|
|
1139
1625
|
$.flush();
|
|
1140
1626
|
},
|
|
1141
1627
|
|
|
1142
|
-
get
|
|
1143
|
-
return
|
|
1628
|
+
get assessment() {
|
|
1629
|
+
return assessment();
|
|
1144
1630
|
},
|
|
1145
1631
|
|
|
1146
|
-
set
|
|
1147
|
-
|
|
1632
|
+
set assessment($$value) {
|
|
1633
|
+
assessment($$value);
|
|
1148
1634
|
$.flush();
|
|
1149
1635
|
},
|
|
1150
1636
|
|
|
1151
|
-
get
|
|
1152
|
-
return
|
|
1637
|
+
get currentItemRef() {
|
|
1638
|
+
return currentItemRef();
|
|
1153
1639
|
},
|
|
1154
1640
|
|
|
1155
|
-
set
|
|
1156
|
-
|
|
1641
|
+
set currentItemRef($$value) {
|
|
1642
|
+
currentItemRef($$value);
|
|
1643
|
+
$.flush();
|
|
1644
|
+
},
|
|
1645
|
+
|
|
1646
|
+
get qtiEnforcement() {
|
|
1647
|
+
return qtiEnforcement();
|
|
1648
|
+
},
|
|
1649
|
+
|
|
1650
|
+
set qtiEnforcement($$value) {
|
|
1651
|
+
qtiEnforcement($$value);
|
|
1157
1652
|
$.flush();
|
|
1158
1653
|
},
|
|
1159
1654
|
|
|
@@ -1230,22 +1725,24 @@ __pieDefineSafely('pie-assessment-toolkit', $.create_custom_element(
|
|
|
1230
1725
|
section: { attribute: 'section', type: 'Object' },
|
|
1231
1726
|
sectionId: { attribute: 'section-id', type: 'String' },
|
|
1232
1727
|
attemptId: { attribute: 'attempt-id', type: 'String' },
|
|
1233
|
-
view: { attribute: 'view', type: 'String' },
|
|
1234
1728
|
env: { attribute: 'env', type: 'Object' },
|
|
1235
1729
|
lazyInit: { attribute: 'lazy-init', type: 'Boolean' },
|
|
1236
1730
|
toolConfigStrictness: { attribute: 'tool-config-strictness', type: 'String' },
|
|
1237
1731
|
tools: { attribute: 'tools', type: 'Object' },
|
|
1732
|
+
enabledTools: { attribute: 'enabled-tools', type: 'String' },
|
|
1238
1733
|
toolRegistry: { type: 'Object' },
|
|
1239
|
-
accessibility: {
|
|
1734
|
+
accessibility: { type: 'Object' },
|
|
1240
1735
|
player: { attribute: 'player', type: 'Object' },
|
|
1241
1736
|
playerType: { attribute: 'player-type', type: 'String' },
|
|
1242
1737
|
coordinator: { type: 'Object' },
|
|
1243
1738
|
createSectionController: { type: 'Object' },
|
|
1244
|
-
frameworkErrorHook: { type: 'Object' },
|
|
1245
1739
|
onFrameworkError: { type: 'Object' },
|
|
1246
|
-
onframeworkerror: { type: 'Object' },
|
|
1247
1740
|
errorRenderer: { type: 'Object' },
|
|
1248
|
-
|
|
1741
|
+
onStageChange: { type: 'Object' },
|
|
1742
|
+
assessment: { type: 'Object' },
|
|
1743
|
+
currentItemRef: { type: 'Object' },
|
|
1744
|
+
qtiEnforcement: { attribute: 'qti-enforcement', type: 'String' },
|
|
1745
|
+
isolation: { type: 'Object' }
|
|
1249
1746
|
},
|
|
1250
1747
|
['default'],
|
|
1251
1748
|
[
|