tessera-learn 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/AGENTS.md +50 -21
  2. package/README.md +2 -2
  3. package/dist/{audit--fSWIOgK.js → audit-DsYqXbqm.js} +282 -197
  4. package/dist/audit-DsYqXbqm.js.map +1 -0
  5. package/dist/{build-commands-Qyrlsp3n.js → build-commands-BFuiAxaR.js} +4 -4
  6. package/dist/build-commands-BFuiAxaR.js.map +1 -0
  7. package/dist/{inline-config-DqAKsCNl.js → inline-config-DVvOCKht.js} +6 -6
  8. package/dist/inline-config-DVvOCKht.js.map +1 -0
  9. package/dist/plugin/cli.d.ts +5 -1
  10. package/dist/plugin/cli.d.ts.map +1 -1
  11. package/dist/plugin/cli.js +91 -49
  12. package/dist/plugin/cli.js.map +1 -1
  13. package/dist/plugin/index.d.ts +287 -2
  14. package/dist/plugin/index.d.ts.map +1 -1
  15. package/dist/plugin/index.js +3 -3
  16. package/dist/{plugin-B-aiL9-V.js → plugin-BuMiDTmU.js} +145 -111
  17. package/dist/plugin-BuMiDTmU.js.map +1 -0
  18. package/package.json +7 -7
  19. package/src/components/DefaultLayout.svelte +2 -5
  20. package/src/components/MultipleChoice.svelte +1 -2
  21. package/src/components/Quiz.svelte +18 -26
  22. package/src/plugin/ast.ts +9 -2
  23. package/src/plugin/build-commands.ts +7 -4
  24. package/src/plugin/cli.ts +96 -46
  25. package/src/plugin/csp.ts +59 -0
  26. package/src/plugin/duplicate-cli.ts +37 -1
  27. package/src/plugin/export.ts +56 -27
  28. package/src/plugin/index.ts +138 -93
  29. package/src/plugin/inline-config.ts +4 -2
  30. package/src/plugin/manifest.ts +24 -23
  31. package/src/plugin/new-cli.ts +2 -0
  32. package/src/plugin/validate-cli.ts +5 -2
  33. package/src/plugin/validation.ts +255 -238
  34. package/src/runtime/App.svelte +14 -9
  35. package/src/runtime/Sidebar.svelte +3 -1
  36. package/src/runtime/adapters/cmi5.ts +59 -402
  37. package/src/runtime/adapters/discovery.ts +11 -0
  38. package/src/runtime/adapters/index.ts +27 -60
  39. package/src/runtime/adapters/lms-error.ts +61 -0
  40. package/src/runtime/adapters/scorm-base.ts +15 -14
  41. package/src/runtime/adapters/scorm12.ts +6 -25
  42. package/src/runtime/adapters/scorm2004.ts +12 -54
  43. package/src/runtime/adapters/web.ts +11 -4
  44. package/src/runtime/adapters/xapi-launch-base.ts +346 -0
  45. package/src/runtime/adapters/xapi.ts +26 -0
  46. package/src/runtime/fingerprint.ts +28 -0
  47. package/src/runtime/interaction-format.ts +0 -1
  48. package/src/runtime/persistence.ts +4 -0
  49. package/src/runtime/types.ts +22 -1
  50. package/src/runtime/xapi/publisher.ts +16 -15
  51. package/src/runtime/xapi/setup.ts +24 -15
  52. package/src/virtual.d.ts +4 -1
  53. package/templates/course/course.config.js +1 -0
  54. package/dist/audit--fSWIOgK.js.map +0 -1
  55. package/dist/build-commands-Qyrlsp3n.js.map +0 -1
  56. package/dist/inline-config-DqAKsCNl.js.map +0 -1
  57. package/dist/plugin-B-aiL9-V.js.map +0 -1
package/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md: Tessera Course Authoring Guide
2
2
 
3
- Tessera is an LMS-tracking runtime for interactive learning content (SCORM 1.2 / SCORM 2004 4e / cmi5 / static web). It owns tracking, progress, completion/success rollup, persistence, and navigation gating. You own the presentation layer.
3
+ Tessera is an LMS-tracking runtime for interactive learning content (SCORM 1.2 / SCORM 2004 4e / cmi5 / xAPI 1.0.3 / static web). It owns tracking, progress, completion/success rollup, persistence, and navigation gating. You own the presentation layer.
4
4
 
5
5
  This is the canonical reference for authoring a Tessera course. Read it before generating or editing course code. You are reading `node_modules/tessera-learn/AGENTS.md`; it updates when you bump `tessera-learn`.
6
6
 
@@ -54,7 +54,8 @@ From the workspace root (`pnpm`; corepack provisions it). Each command takes the
54
54
  pnpm install # first time only
55
55
  pnpm dev <course> # dev server at http://localhost:5173 (Ctrl+C to stop)
56
56
  pnpm export <course> # build + package for the LMS standard in course.config.js
57
- pnpm validate <course> # run validation only no server, no bundle
57
+ pnpm export <course> --standard <web|scorm12|scorm2004|cmi5|xapi> # override that standard for this build
58
+ pnpm validate <course> # run validation only — no server, no bundle (also takes --standard)
58
59
  pnpm a11y <course> # runtime a11y audit on its own (the audit half of check)
59
60
  pnpm check <course> # validate, then the runtime a11y audit (axe) over the built course
60
61
  ```
@@ -430,6 +431,7 @@ By default `successStatus` stays `"unknown"`. Set `requireSuccessStatus: "passed
430
431
  | SCORM 1.2 | `lesson_status = "completed"` | `lesson_status = "passed"` |
431
432
  | SCORM 2004 4th | `completion_status = "completed"`, `success_status = "unknown"` | `success_status = "passed"` |
432
433
  | cmi5 | **Completed** (no Passed/Failed) | **Passed** alongside **Completed** |
434
+ | xapi | **Completed** (no Passed/Failed) | **Passed** alongside **Completed** |
433
435
  | web | `localStorage` only | `localStorage` only |
434
436
 
435
437
  ### Rules and non-goals
@@ -512,10 +514,12 @@ For the common case, set `branding.primaryColor` and `branding.fontFamily` in `c
512
514
  ```js
513
515
  export default {
514
516
  title: 'My Course', // required — the only field with no default
517
+ id: 'urn:uuid:…', // unique course identity; scaffolders generate one — keep it
515
518
  description: '',
516
519
  author: '',
517
520
  version: '1.0.0',
518
521
  language: 'en', // BCP-47 tag for <html lang>; defaults to "en"
522
+ resume: 'auto', // "auto" (default) restores progress unless page structure changed | "never"
519
523
 
520
524
  branding: {
521
525
  logo: '', // e.g. "$assets/logo.png"
@@ -538,7 +542,7 @@ export default {
538
542
  },
539
543
 
540
544
  export: {
541
- standard: 'web', // "web" | "scorm12" | "scorm2004" | "cmi5"
545
+ standard: 'web', // "web" | "scorm12" | "scorm2004" | "cmi5" | "xapi"
542
546
  },
543
547
 
544
548
  a11y: {
@@ -551,16 +555,18 @@ export default {
551
555
 
552
556
  ### Field behaviour
553
557
 
554
- | Field | Behaviour |
555
- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
556
- | `language` | Sets `<html lang>` (WCAG 3.1.1). Missing/implausible value warns and falls back to `"en"` |
557
- | `navigation.mode: "free"` | All pages accessible except those blocked by gating quizzes |
558
- | `navigation.mode: "sequential"` | Pages unlock one at a time as each completes |
559
- | `completion.mode: "percentage"` | Completes when `visitedPages / totalPages * 100 >= percentageThreshold` |
560
- | `completion.mode: "quiz"` | Completes when graded quiz average >= `scoring.passingScore` |
561
- | `completion.mode: "manual"` | Completes when an author trigger fires. See [Manual completion](#manual-completion) |
562
- | `a11y.level: "error"` | Promotes captions/transcript, heading order, contrast, language, Svelte a11y warnings to errors. Hard errors (missing `alt`, missing media `title`) always block regardless |
563
- | `a11y.ignore` | Flat list matched literally against every diagnostic rule ID across all tiers (`tessera/…`, `a11y_…`, bare axe IDs) |
558
+ | Field | Behaviour |
559
+ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
560
+ | `id` | Unique course identity; seeds the web localStorage key and the cmi5/xAPI activity id. Scaffolders mint a `urn:uuid:…`. Missing → falls back to a fixed value (collides across courses) and the build warns. `tessera duplicate` regenerates it. |
561
+ | `language` | Sets `<html lang>` (WCAG 3.1.1). Missing/implausible value warns and falls back to `"en"` |
562
+ | `resume` | `"auto"` (default) restores saved progress, but **discards it when the page structure changed** (pages added/removed/reordered/renamed) since it was saved. `"never"` always starts fresh. Re-uploading a changed course to an LMS: upload as a _new version_/registration, not overwrite-in-place, so learners get a clean attempt — `"auto"` still protects learners if your LMS overwrites in place, but old-structure scores/progress are dropped by design (they can't be safely remapped) |
563
+ | `navigation.mode: "free"` | All pages accessible except those blocked by gating quizzes |
564
+ | `navigation.mode: "sequential"` | Pages unlock one at a time as each completes |
565
+ | `completion.mode: "percentage"` | Completes when `visitedPages / totalPages * 100 >= percentageThreshold` |
566
+ | `completion.mode: "quiz"` | Completes when graded quiz average >= `scoring.passingScore` |
567
+ | `completion.mode: "manual"` | Completes when an author trigger fires. See [Manual completion](#manual-completion) |
568
+ | `a11y.level: "error"` | Promotes captions/transcript, heading order, contrast, language, Svelte a11y warnings to errors. Hard errors (missing `alt`, missing media `title`) always block regardless |
569
+ | `a11y.ignore` | Flat list matched literally against every diagnostic rule ID across all tiers (`tessera/…`, `a11y_…`, bare axe IDs) |
564
570
 
565
571
  ### Minimum config
566
572
 
@@ -589,15 +595,36 @@ canAccess: (ctx) => {
589
595
 
590
596
  `pnpm export <course>` writes:
591
597
 
592
- | `export.standard` | What ships | Where |
593
- | ----------------- | ------------------------------------- | ----------------------------- |
594
- | `web` | Static site (HTML/CSS/JS + `assets/`) | `dist/` (any static host) |
595
- | `scorm12` | SCORM 1.2 package | `dist/<course>-scorm12.zip` |
596
- | `scorm2004` | SCORM 2004 4th Edition package | `dist/<course>-scorm2004.zip` |
597
- | `cmi5` | cmi5 package (AU + manifest) | `dist/<course>-cmi5.zip` |
598
+ | `export.standard` | What ships | Where |
599
+ | ----------------- | ------------------------------------------- | ----------------------------- |
600
+ | `web` | Static site (HTML/CSS/JS + `assets/`) | `dist/` (any static host) |
601
+ | `scorm12` | SCORM 1.2 package | `dist/<course>-scorm12.zip` |
602
+ | `scorm2004` | SCORM 2004 4th Edition package | `dist/<course>-scorm2004.zip` |
603
+ | `cmi5` | cmi5 package (AU + manifest) | `dist/<course>-cmi5.zip` |
604
+ | `xapi` | xAPI 1.0.3 "Tin Can" package (`tincan.xml`) | `dist/<course>-xapi.zip` |
598
605
 
599
606
  Upload the LMS zips via your LMS's import flow; drop `dist/` (web) on any static host.
600
607
 
608
+ `--standard <web|scorm12|scorm2004|cmi5|xapi>` overrides `export.standard` for one build without editing `course.config.js` — e.g. `pnpm export <course> --standard scorm2004`. Useful for packaging the same course for multiple LMSs from one config. `pnpm validate <course> --standard <value>` takes the same flag to preview validation against the overridden standard. An unknown value fails before the build runs.
609
+
610
+ ### Web export Content-Security-Policy
611
+
612
+ Web builds emit a baseline CSP `<meta>` (LMS packages and the dev server don't). It allows any `https:` for images/media/frames/network, but **not** for scripts, styles, or fonts — so a CDN script/stylesheet/font is blocked until you allow its origin. Extend per-directive via `export.csp` (sources are **appended** to the baseline, never replaced):
613
+
614
+ ```js
615
+ export: {
616
+ standard: 'web',
617
+ csp: {
618
+ 'style-src': ['https://fonts.googleapis.com'],
619
+ 'font-src': ['https://fonts.gstatic.com'],
620
+ },
621
+ }
622
+ ```
623
+
624
+ - `export.csp: false` drops the meta entirely (use when your host sets a CSP header).
625
+ - To **tighten** or replace a directive (not just add), use a `transformIndexHtml` hook — `export.csp` only adds.
626
+ - Ignored unless `standard` is `'web'`.
627
+
601
628
  ### Validation
602
629
 
603
630
  The plugin validates on every dev start and build (page syntax, manifest shape, `pageConfig`, question components, asset references, data-contract bypass). Errors abort the build and print `[tessera error] ...`; warnings print `[tessera warning] ...` and don't block. Run `pnpm validate <course>` to check without building.
@@ -823,7 +850,7 @@ xapi: {
823
850
  activityId: 'https://example.com/courses/intro-to-x',
824
851
  }
825
852
 
826
- // cmi5 only: inherit the LMS launch LRS:
853
+ // cmi5 / xapi only: inherit the LMS launch LRS:
827
854
  xapi: { endpoint: 'lms' }
828
855
 
829
856
  // Fan out (at most one 'lms' entry):
@@ -840,6 +867,7 @@ Each destination has its own queue, auth resolver, and retry loop. One UUID per
840
867
  | Mode | `xapi` not set | `xapi.endpoint: 'lms'` | `xapi: {endpoint, ...}` (explicit) |
841
868
  | ------------- | ------------------ | ---------------------- | ------------------------------------------------------- |
842
869
  | **cmi5** | `useXAPI()` → null | Inherits launch LRS | Independent publisher; `actor` defaults to launch actor |
870
+ | **xapi** | `useXAPI()` → null | Inherits launch LRS | Independent publisher; `actor` defaults to launch actor |
843
871
  | **scorm12** | `useXAPI()` → null | **Config error** | Independent; `actor` derived from `cmi.core.student_id` |
844
872
  | **scorm2004** | `useXAPI()` → null | **Config error** | Independent; `actor` derived from `cmi.learner_id` |
845
873
  | **web** | `useXAPI()` → null | **Config error** | Independent; `actor` **required** in config |
@@ -848,7 +876,7 @@ Each destination has its own queue, auth resolver, and retry loop. One UUID per
848
876
 
849
877
  - **Actor priority:** author-supplied `xapi.actor` always wins; else cmi5 launch actor; else SCORM-derived from the LMS data model; else error. Override the SCORM-derived `homePage` via `actorAccountHomePage` (required if `activityId` is a non-URL IRI).
850
878
  - **Auth is Basic-only.** Pass the credential value, not the full header (the publisher prepends `Basic `). For OAuth, return a Basic credential from your `auth` function or run a proxy.
851
- - **Never ship a static `auth` string on web** the bundle is public. Use a function that fetches a server-brokered short-lived token. CORS must allow the served origin.
879
+ - **`course.config.js` is serialized verbatim into the client bundle** — every field is public, not just `auth`. Never put a static `auth` string, API key, or any secret in it; use a function that fetches a server-brokered short-lived token. CORS must allow the served origin.
852
880
  - **`actor` is required on web export** and resolved once per page-load (no mid-session identity change in v1 — reload to switch).
853
881
  - **Page unload rejects sends.** Once unload begins, `sendStatement` rejects (keeps cmi5 Terminated last). Do end-of-session work in a child component's `onDestroy`, not `beforeunload`.
854
882
  - **Retry:** 3 attempts, exponential backoff; 5xx/network retry, 4xx short-circuits, 409 treated as success. Opt out per call with `sendStatement(stmt, { retry: false })`.
@@ -893,6 +921,7 @@ Author-facing consequences:
893
921
  | scorm12 | Upload `dist/*-scorm12.zip` to [SCORM Cloud](https://cloud.scorm.com) (free) or Reload Player |
894
922
  | scorm2004 | SCORM Cloud (easiest); also Moodle, Cornerstone, SuccessFactors, Canvas |
895
923
  | cmi5 | Upload `dist/*-cmi5.zip` to SCORM Cloud and use its generated cmi5 dispatch URL |
924
+ | xapi | Upload `dist/*-xapi.zip` to SCORM Cloud (imports `tincan.xml`) and launch the generated URL |
896
925
  | web | Serve `dist/` from any static host |
897
926
 
898
927
  Inspect the LMS API call log to confirm `lesson_status` / `completion_status` / interactions look right.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tessera-learn
2
2
 
3
- LMS tracking runtime for interactive learning content. One adapter layer (SCORM 1.2, SCORM 2004 4th Edition, cmi5, static Web), your choice of components.
3
+ LMS tracking runtime for interactive learning content. One adapter layer (SCORM 1.2, SCORM 2004 4th Edition, cmi5, xAPI 1.0.3, static Web), your choice of components.
4
4
 
5
5
  Tessera is a toolkit for building interactive online courses, designed for AI-assisted authoring: pages are `.svelte` files, the runtime locks the LMS data contract (tracking, completion, scoring, persistence), and an AI agent working in the workspace follows the conventions in `AGENTS.md` to write pages and components. This package is the runtime; you typically don't depend on it directly — `create-tessera` scaffolds a workspace that pins it for you. A workspace is one package that holds many courses under `courses/<name>/` and a `shared/` design system imported as `$shared`.
6
6
 
@@ -19,7 +19,7 @@ That creates a workspace with Tessera wired up, a seed course, and a root `AGENT
19
19
  - **Hooks** (`tessera-learn`): `useQuestion`, `useQuiz`, `useNavigation`, `useProgress`, `useCompletion`, `usePersistence`, `useXAPI`.
20
20
  - **Vite plugin** (`tessera-learn/plugin`): `tesseraPlugin()` — wires page/layout discovery, the LMS adapter, the `$shared` alias, and the export pipeline. The `tessera` CLI (`new`/`dev`/`export`/`validate`/`a11y`/`check`) runs Vite with this plugin for you, so scaffolded workspaces need no `vite.config.js`.
21
21
  - **Built-in components** (`tessera-learn`): `Callout`, `Image`, `Audio`, `Video`, `Accordion` / `AccordionItem`, `Carousel` / `CarouselSlide`, `RevealModal`, `Quiz`, `MultipleChoice`, `FillInTheBlank`, `Matching`, `Sorting`, `DefaultLayout`.
22
- - **LMS adapters**: SCORM 1.2, SCORM 2004 4th Edition, cmi5, static Web — selected via `course.config.js` `export.standard`.
22
+ - **LMS adapters**: SCORM 1.2, SCORM 2004 4th Edition, cmi5, xAPI 1.0.3 ("Tin Can"), static Web — selected via `course.config.js` `export.standard`, or overridden per build with `tessera export --standard <value>`.
23
23
  - **Accessibility checks**: static rules (alt text, media titles/captions, heading order, contrast, `<html lang>`) run inside validation and the build with zero extra dependencies; an opt-in runtime audit (`tessera a11y`, with `playwright` + `@axe-core/playwright` as optional peers) renders every page and gates on axe-core violations.
24
24
 
25
25
  See `AGENTS.md` for usage, signatures, and authoring conventions.