High-level architecture overview for PIE-QTI.
- Item player: render and score a single QTI item in the browser (plus extensibility and optional isolation).
- Assessment player: multi-item shell (navigation, sections, selection/ordering, state persistence, optional backend adapter).
- QTI → PIE: parse QTI content and produce PIE JSON, with a lossless round-trip path when the source originated from PIE.
- PIE → QTI: generate QTI 2.2 XML from PIE JSON, including IMS Content Package support and a lossless round-trip path.
- CLI for batch conversion and analysis.
- Transform app for upload → analyze → transform → preview.
- Transforms: ingest QTI content and emit QTI 2.2 (
imsqti_v2p2) from PIE. Treat unsupported namespaces/variants as ingest-time compatibility work and validate early. - Players: designed for QTI content across the supported delivery scope (role/view-aware rendering, response processing, optional backend scoring patterns). For highest interoperability in legacy ecosystems, keep content aligned to the relevant QTI profile.
Everything lives under packages/ and apps/:
packages/types: shared transformation type contracts (input/output formats, storage interfaces)packages/schemas: PIE schema/validation assetspackages/core: transform engine + plugin registry (format orchestration)packages/storage: pluggable storage backends (filesystem, S3, database)packages/to-pie: QTI → PIE transform plugin (plus vendor extension hooks)packages/pie-to-qti2: PIE → QTI transform plugin (generator registry + packaging)
packages/item-player: QTI item player (core engine + extraction + web-component rendering + optional iframe host helper)packages/assessment-player: QTI assessment shell (sections, navigation, persistence, backend adapter)packages/default-components: default web components for QTI interactions (Svelte-authored, but web-component contract is framework-agnostic)
packages/i18n: Internationalization (i18n) system for player UI (type-safe translations, runtime locale switching)packages/typeset-katex: KaTeX typesetting adapter (host-providedtypeset()function)packages/qti-processing: response processing operators/templates used by the item playerpackages/player-elements,packages/web-component-loaders: web-component build/load helpers
apps/demo: example app + fixtures + reference iframe runtime page
Status: Production-ready for the supported QTI delivery scope
The item player keeps “QTI logic” separate from “UI rendering”:
- Core engine: parse XML, track state, execute response processing, apply QTI roles.
- Extraction: produce
InteractionDatafor each interaction (priority-based extractors). - Rendering: render interactions via web components selected by a registry (priority +
canHandle()).
This lets you:
- swap UI frameworks (web components are the contract),
- add vendor QTI support via plugins without forking the core,
- centralize typesetting and security policies.
Plugins can register:
- Extractors: interpret/validate vendor markup, override standard extraction.
- Components: map
InteractionData→ a custom element tag name (and optionally auto-register the element). - Lifecycle hooks: integrate with host telemetry or setup logic.
Key references:
packages/item-player/src/core/Plugin.tspackages/item-player/src/core/PluginManager.ts- Example plugin:
packages/acme-likert-plugin/
- ExtractionRegistry (data extraction, priority-based)
- ComponentRegistry (renderer selection, priority-based)
Key reference:
packages/item-player/src/core/ComponentRegistry.ts
The player intentionally does not bundle a math engine. Instead, the host can pass a typeset function, which is called after render (and can be re-invoked on DOM updates).
Default adapter:
packages/typeset-katexprovidestypesetMathInElementand KaTeX CSS.
Key references:
packages/typeset-katex/README.mdpackages/item-player/src/components/actions/typesetAction.tspackages/item-player/src/components/ItemBody.svelte(appliestypesetto the rendered wrapper)
If you need QTI <customOperator> support, the player can be configured with a registry of operator implementations keyed by operator class (preferred) or definition URI.
Key reference:
packages/item-player/src/types/index.ts(customOperators)
For untrusted QTI, consider iframe isolation. The repo provides:
- Host helper:
@pie-qti/item-player/iframe - Protocol types/validators for postMessage messages
You host your own runtime page/app (the repo’s example includes a reference runtime for demo/testing).
Key references:
packages/item-player/docs/iframe-mode.mdpackages/item-player/src/iframe/IFramePlayerHost.ts
When rendering into the host DOM, configure security guardrails explicitly:
- HTML sanitization (what tags/attrs survive, and how URLs are filtered)
- URL policy (which protocols/hosts are allowed for extracted URLs and HTML attributes)
- Parsing limits (optional DoS guardrails)
- Trusted Types policy name (host-controlled; effective only if your CSP enforces Trusted Types)
Key references:
packages/item-player/src/types/index.ts(PlayerSecurityConfig)packages/item-player/src/core/sanitizer.tspackages/item-player/src/core/urlPolicy.ts- Security model PRD:
docs/prds/architecture/security.md
The assessment player orchestrates a multi-item test experience:
- navigation modes (linear/nonlinear)
- sections/hierarchy
- selection & ordering rules (randomization + seed)
- time limits and events
- item session control (attempts, review/skip/validation)
- state persistence (local-first, optional backend save)
- optional backend adapter for secure scoring and role-based filtering
Key references:
packages/assessment-player/README.mdpackages/assessment-player/BACKEND-INTEGRATION.md
The default interaction components render via web components (Svelte custom elements) inside Shadow DOM. This provides encapsulation while remaining framework-agnostic for hosts.
The theming system balances three goals:
- Components work with zero host CSS — Layout and accessibility styles are built-in.
- Host controls visual theming — Colors, typography, and radii via CSS variables.
- Host can refine details — Stable
::part()hooks for targeted customization.
Components consume package-owned PIE QTI theme variables. DaisyUI hosts can import
@pie-qti/theme-daisyui/bridge.css to map their active --color-* theme into
these variables.
| Variable | Purpose |
|---|---|
--pie-qti-primary |
Primary color |
--pie-qti-accent |
Accent color |
--pie-qti-base-100, --pie-qti-base-200, --pie-qti-base-300 |
Base surface colors |
--pie-qti-base-content |
Base content (text) color |
--pie-qti-success |
Success color |
Usage in components: var(--pie-qti-primary, <fallback>),
var(--pie-qti-base-content, <fallback>), etc.
If the host doesn't provide these variables, components fall back to safe defaults via var(--token, fallback).
- Set theme variables on
:rootor any ancestor of the web component. - Optionally set
data-theme="..."for DaisyUI theme switching.
Each component exposes stable part names for host-side CSS refinement:
/* Example: customize order interaction items */
pie-qti-order::part(item) {
border-radius: 12px;
}
pie-qti-order::part(handle) {
opacity: 0.9;
}Components expose parts for all major structural elements (lists, items, handles, prompts, inputs, etc.).
The default components include a small baseline stylesheet (ShadowBaseStyles) that provides minimal styling for common DaisyUI classnames (btn, alert, badge, etc.) inside Shadow DOM when the host does not load DaisyUI/Tailwind.
Key references:
packages/default-components/STYLING.md(full part catalog and examples)packages/default-components/src/shared/components/ShadowBaseStyles.svelte
Status: Production-ready for the supported QTI delivery scope Package:
@pie-qti/i18n
The PIE-QTI player includes a lightweight, type-safe internationalization system for translating player UI strings (buttons, labels, error messages, ARIA text, etc.).
Note: This system translates the player interface, not QTI assessment content. Assessments are authored in the content creator's chosen language.
- Type-safe translations: TypeScript autocomplete for all message keys
- Runtime locale switching: Change language via provider (persisted to localStorage with
SvelteI18nProvider) - Web Component compatible: Works within Shadow DOM boundaries
- Small bundle size: <10 KB gzipped (core + default English locale)
- On-demand loading: Additional locales loaded asynchronously when needed
| Locale Code | Language |
|---|---|
en-US |
English (United States) |
es-ES |
Spanish (Spain) |
fr-FR |
French (France) |
nl-NL |
Dutch (Netherlands) |
ro-RO |
Romanian (Romania) |
ar-SA |
Arabic (Saudi Arabia) |
zh-CN |
Chinese (Simplified) |
th-TH |
Thai (Thailand) |
The i18n system consists of:
DefaultI18nProvider(packages/i18n/src/core/I18n.ts) - Core provider: message lookup, interpolation, pluralization, lazy locale loading via Vite glob importsSvelteI18nProvider(packages/i18n/src/providers/SvelteI18nProvider.ts) - Wraps anyI18nProvider; persists locale to localStorage and triggers page reload on locale change- Locale files (
packages/i18n/src/locales/*.ts) - TypeScript modules with structured translation messages LocaleSwitchercomponent (packages/i18n/src/components/LocaleSwitcher.svelte) - Dropdown UI for runtime locale selection
import { createDefaultSvelteI18nProvider } from '@pie-qti/i18n';
const provider = createDefaultSvelteI18nProvider('en-US');Translations are organized by feature:
common.*- Shared UI text (Submit, Cancel, Next, etc.)units.*- Unit formatting (bytes, KB, seconds, etc.)validation.*- Form validation messagesinteractions.*- QTI interaction-specific text (organized by interaction type)assessment.*- Assessment player UI (navigation, sections, timer, feedback)accessibility.*- ARIA labels and screen reader announcements
packages/i18n/README.md- Full API documentationpackages/i18n/src/locales/en-US.ts- Complete list of available translation keys
Status: Under active development
TransformEngine is the orchestrator:
- detects source format if not provided,
- selects a plugin by
(sourceFormat, targetFormat), - executes
plugin.transform()and optionallyplugin.validate().
Key references:
packages/core/src/engine/transform-engine.tspackages/core/src/registry/plugin-registry.ts
- Lossless path: if the QTI contains a PIE extension, the transformer extracts the embedded PIE model verbatim (perfect PIE reconstruction).
- Best-effort path: otherwise, it detects interaction/test type and transforms QTI semantics into the closest PIE model(s).
QTI → PIE supports a vendor extension system:
- detectors: identify vendor QTI patterns
- transformers: replace/override transformation logic
- asset resolvers / CSS class extractors / metadata extractors: preserve vendor details and keep round-trips stable
Key references:
packages/to-pie/src/plugin.ts(vendor registration + lossless extraction checks)
- Lossless path: if the PIE item contains embedded QTI source, reconstruction returns that source verbatim.
- Generated path: otherwise, a generator is selected from a registry to produce QTI XML (primary model today, with multi-model context).
You can register custom generators to handle new/variant PIE models.
Key references:
packages/pie-to-qti2/src/plugin.ts(registerGenerator, registry usage)packages/pie-to-qti2/CUSTOM-GENERATORS.md
When configured (and typically when external passages are generated), the PIE → QTI plugin can also emit:
imsmanifest.xml(IMS CP v1.1)- item XMLs, passage XMLs, and assessment XMLs (as applicable)
Key reference:
packages/pie-to-qti2/docs/MANIFEST-GENERATION.md
For stable references across systems:
- PIE
baseIdis preserved into QTI metadata - the reverse transform restores
baseIdso repeated round-trips remain stable
Key reference:
packages/to-pie/src/plugin.ts(extractBaseId)packages/pie-to-qti2/src/plugin.ts(addSearchMetadatastoressourceSystemIdandexternalId)
Status: Composer CMS is the intended home for product import workflows. This repository ships reusable packages; host applications compose them.
A product import surface can wire the reusable packages together along these lines:
- upload and extraction (pluggable storage backend with session management),
- analysis (discover items/tests, count interactions, record issues),
- transformation (batch convert to PIE),
- preview (QTI player preview and PIE player preview side-by-side).
Use the pluggable storage system (@pie-qti/storage) that abstracts storage backends:
- Default: Filesystem backend
- Optional: S3, database, or custom backends via configuration
Sessions are typically stored with separate metadata files:
metadata.json- Core session state (id, status, timestamps)analysis.json- Analysis results (packages, items, interactions)transformation.json- Transform results (items, assessments, errors)
This separation enables:
- Independent loading of analysis/transformation data
- Efficient storage backends (only fetch what's needed)
- Clear data ownership and versioning
Key references:
- Storage types:
packages/types/src/storage/index.ts - Storage package:
packages/storage/src/ - Source profile configuration:
docs/SOURCE-PROFILES.md
A sessioned host flow can follow this shape:
- Upload: store ZIP(s) in a new session
- Analyze: extract and analyze the session content
- Transform: run
TransformEngine+QtiToPiePluginover the session
- QTI preview: use the QTI players + typesetting adapter to render XML directly.
- PIE preview: use PIE web components (
pie-iife-player/pie-esm-player) to render the transformed PIE config.
Product import implementations should compose the package-level pieces (@pie-qti/to-pie, @pie-qti/transform-core, QTI players, PIE players) inside their own persistence, workflow, and authorization boundaries.
This section combines “deployment guidance” with “security boundary clarifications”. If you’re embedding this into a product, treat this as required reading.
- Same-DOM is not a sandbox: rendering attacker-controlled content into your application DOM is inherently risky. The player provides guardrails (sanitization, URL policy, optional parsing limits, Trusted Types support), but it does not provide a complete isolation boundary.
- Plugins/custom components are integrator-owned: any
QTIPluginand any custom web components run as host code. Treat them as trusted application code (review, test, pin). - Client-side scoring is not secure: for high-stakes assessment, you must do server-side scoring and avoid sending correct answers / scoring logic to the candidate.
- Best isolation is origin isolation: for untrusted QTI, prefer iframe mode and run the runtime on a separate origin with strict postMessage allowlists.
- LTI is a host integration concern: the player can run inside an LTI 1.3 / LTI Advantage tool, but launch validation, Deep Linking, AGS grade passback, NRPS roster lookup, and LMS policy enforcement belong to the host application.
Use when you control authoring and inputs.
- Keep URL policy + sanitizer enabled anyway.
- Keep embeds disabled unless you explicitly allow them.
- Deploy with a baseline CSP.
If you must render third-party QTI in the host DOM:
- enforce strict URL policy and limit allowed protocols/hosts,
- enable parsing limits to reduce DoS surface,
- keep
<object>/<embed>/<iframe>disabled, - deploy strong CSP and consider Trusted Types enforcement.
Residual risk remains architectural.
- host runtime on a separate origin,
- use sandboxing and strict origin allowlists,
- validate message types/versions, ignore unknown messages,
- keep runtime CSP separate from host CSP.
For candidate role:
- do not ship
<correctResponse>or<responseProcessing>to the browser, - submit only candidate responses to the backend,
- score on the server using privileged role,
- return only the minimum outcomes needed for UX,
- persist sessions server-side if integrity matters.
Reference:
packages/assessment-player/BACKEND-INTEGRATION.md
At minimum:
- restrict
script-src,connect-src,img-src,media-src,font-srcto what you actually need, - avoid
unsafe-inlineif possible, - if you can: enforce Trusted Types and set the player’s
trustedTypesPolicyNameto match your CSP policy.
You’ll likely want multiple layers:
- schema/XSD validation where feasible (pre-ingestion),
- transform-time validation (plugin-level warnings),
- render-time checks (player errors/warnings),
- optional external validators (e.g. QTIWorks) as a secondary signal for interoperability.
- Transformation overview:
docs/PIE-QTI-TRANSFORMATION-GUIDE.md - QTI technical reference:
docs/QTI_techguide.md - IMS Content Package notes:
docs/IMS_Content_Packages_techguide.md - QTI item player:
packages/item-player/README.md - Iframe mode reference:
packages/item-player/docs/iframe-mode.md - Security model PRD:
docs/prds/architecture/security.md - QTI assessment player:
packages/assessment-player/README.md - Backend integration:
packages/assessment-player/BACKEND-INTEGRATION.md - IMS Content Packages:
packages/pie-to-qti2/docs/MANIFEST-GENERATION.md



