Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d8402c4
docs(three-d-viewer): add SDD-0001 and ADR-0001 for interaction layer
erseco Jul 10, 2026
81dc650
feat(three-d-viewer): add versioned interaction schema and migration
erseco Jul 10, 2026
b7f4fec
feat(three-d-viewer): shared interaction runtime with renderer adapters
erseco Jul 10, 2026
fa87590
feat(three-d-viewer): export interaction markup and runtime boot
erseco Jul 10, 2026
5dcf8c9
feat(three-d-viewer): interaction authoring UI and styles
erseco Jul 10, 2026
c404e48
test(three-d-viewer): E2E for interactions; fix marker wipe on form c…
erseco Jul 10, 2026
35af2a5
fix(three-d-viewer): harden interaction layer after adversarial review
erseco Jul 10, 2026
166e382
test(three-d-viewer): make interaction E2E robust to headless WebGL
erseco Jul 10, 2026
cc345f9
feat(three-d-viewer): make question markers SCORM-gradable
erseco Jul 10, 2026
617e999
Merge branch 'main' into 2153-3d-viewer-edevice
erseco Jul 21, 2026
1e4b5a7
build(idevices): reuse centralized TypeScript iDevice pipeline
claude Jul 30, 2026
d9866ce
refactor(three-d-viewer): migrate the iDevice to TypeScript
claude Jul 30, 2026
1865cda
test(three-d-viewer): port the suite to TypeScript and extend E2E cov…
claude Jul 30, 2026
dbf323a
docs(three-d-viewer): describe the TypeScript architecture
claude Jul 30, 2026
9754378
fix(export): keep generated source maps out of server-side export pac…
claude Jul 30, 2026
0db489a
fix(three-d-viewer): restore editor animation playback and canvas tea…
claude Jul 30, 2026
7204b7d
fix(build): stop gitignore from swallowing iDevice src/runtime sources
claude Jul 30, 2026
a52adaf
fix(build): build assets before the CI unit-test run
claude Jul 30, 2026
ab5dcc4
Merge branch 'main' into refactor/three-d-viewer-typescript
erseco Aug 4, 2026
b80e704
ci: re-run checks after cancelled workflow runs
erseco Aug 4, 2026
452791e
Merge main into refactor/three-d-viewer-typescript
erseco Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .agents/skills/idevice/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ Creating or modifying interactive devices (iDevices) in `public/files/perm/idevi

**Reference iDevices** (well-tested, good to study): `checklist`, `rubric`, `geogebra-activity`

## TypeScript iDevices (`src/`)

An iDevice with a `src/` directory is a **TypeScript iDevice**: its
`edition/<name>.js` and `export/<name>.js` are GENERATED bundles (gitignored)
— never edit them; edit `src/` and rebuild. Convention and commands:

- `src/edition/index.ts` → `edition/<name>.js` (assigns `window.$exeDevice`);
`src/export/index.ts` → `export/<name>.js` (assigns the runtime global).
- Build/typecheck: `bun run bundle:idevices` / `bun run typecheck:idevices`
(central runner `scripts/build-idevices.ts`; `--only <name>`, `--watch`).
Run `make bundle` after src/ edits and BEFORE E2E, or the preview serves the
stale bundle from `public/bundles/idevices.zip`.
- Tests are colocated `*.spec.ts` (Vitest — `bun test` ignores `public/**`),
plus bundle-contract smoke tests over the compiled IIFEs.
- Deviations (custom bundle name, externals, minify) go in an optional
`build.config.json` — see `doc/development/idevices-typescript.md` and
ADR-2147-01. Reference implementations: `three-d-viewer` (full convention),
`slide` (manifest).

## Structure

```
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
- name: Build all assets once
run: bun run build:static

# The path list must include every GENERATED (gitignored) file the
# workarea serves — the test runners get a fresh checkout, so anything
# missing here 404s at runtime (e.g. TypeScript-iDevice bundles, ADR-2147-01).
- name: Upload dynamic bundles (chromium/firefox)
uses: actions/upload-artifact@v7
with:
Expand All @@ -64,6 +67,8 @@ jobs:
public/bundles/**
public/style/workarea/main.css
public/files/perm/idevices/base/slide/edition/slide-editor.bundle.js
public/files/perm/idevices/base/three-d-viewer/edition/three-d-viewer.js
public/files/perm/idevices/base/three-d-viewer/export/three-d-viewer.js

- name: Upload static distribution (static project)
uses: actions/upload-artifact@v7
Expand Down
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ symfony.lock
.phpunit.cache
.idea/

runtime/
# Anchored to the repository root: an unanchored `runtime/` also matches source
# directories named `runtime` at any depth (e.g. a TypeScript iDevice's
# `src/runtime/`) and silently drops them from commits.
/runtime/
symfony_legacy
nestjs_legacy
test-results/
Expand All @@ -128,6 +131,10 @@ public/app/dist/
/app/dist/
/app/node_modules/

# Slide iDevice — pre-built editor bundle (regenerated by package.json postinstall)
# TypeScript iDevice bundles — generated from each iDevice's src/ by scripts/build-idevices.ts
/public/files/perm/idevices/base/slide/edition/slide-editor.bundle.js
/public/files/perm/idevices/base/three-d-viewer/edition/three-d-viewer.js
/public/files/perm/idevices/base/three-d-viewer/edition/three-d-viewer.js.map
/public/files/perm/idevices/base/three-d-viewer/export/three-d-viewer.js
/public/files/perm/idevices/base/three-d-viewer/export/three-d-viewer.js.map
.omc/
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,10 @@ test-frontend: check-bun check-env bundle ## Run frontend tests (with Vitest + h
bun test:frontend

.PHONY: test-unit-ci
test-unit-ci: check-bun check-tests check-env ## Run unit tests with lcov coverage for CI/Codecov
# Depends on `bundle` for the same reason test-unit/test-integration/test-frontend
# do: backend tests assert against generated assets (e.g. the TypeScript iDevice
# bundles), which are not committed.
test-unit-ci: check-bun check-tests check-env bundle ## Run unit tests with lcov coverage for CI/Codecov
@echo "Running unit tests with lcov coverage..."
@mkdir -p coverage/bun
$(TEST_ENV) bun test:unit:ci
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
id: ADR-2147-01
title: "TypeScript iDevices: src/ sources compiled by one convention-based build"
status: Proposed
date: 2026-07-30
tracking_issue: 2147
deciders:
- "@erseco"
reviewers:
- "@mnunezcedec"
- "@cristinavaldera"
related:
prs: [2147]
changes:
- "2153-three-d-viewer-interactions"
adrs: []
supersedes: []
superseded_by: []
ai_assistance:
tool: "Claude Code"
model: "claude-fable-5"
---

# ADR-2147-01: TypeScript iDevices: src/ sources compiled by one convention-based build

## Context

iDevices are classic-script objects loaded by the workarea and the exporters.
Historically each one is hand-written vanilla JavaScript committed directly
under `edition/` and `export/`. Two iDevices now keep their maintained source
in TypeScript instead — Slide (`src/` + a bespoke `scripts/build-slide-editor.ts`)
and Interactive Video (`src/` + a bespoke `scripts/build-interactive-video.ts`).
Two per-iDevice build scripts with duplicated Bun plumbing were already
diverging in flags and behaviour, and every future TypeScript iDevice would
have added another copy plus more package.json entries.

## Problem

How does the repository recognise, build, type-check and test an iDevice whose
maintained source is TypeScript, without a new build pipeline per iDevice?

## Decision drivers

- One obvious convention for the next TypeScript iDevice (zero new scripts).
- The shipped output must remain plain classic-script IIFEs; the language and
compile step are not a framework.
- Generated artifacts must never be committed; a clean checkout must
regenerate them through the existing pipeline (`build:all` / `make bundle`).
- Existing iDevices with special needs (Slide) must fit without renaming their
shipped bundles.

## Decision

**An iDevice that keeps a `src/` directory is a TypeScript iDevice**, built by
the centralized `scripts/build-idevices.ts`:

- **Convention:** `src/edition/index.ts` → `edition/<name>.js` and
`src/export/index.ts` → `export/<name>.js` — self-contained IIFEs
(`target: browser`, linked source maps, unminified), whose entry points
explicitly assign their window globals (`$exeDevice`, `$<name>`).
- **Escape hatch:** an optional `build.config.json` next to `config.xml`
replaces the convention for that iDevice (custom entries/naming/globalName/
minify/sourcemap, plus `externals` mapping bare imports to page-provided
globals so vendored libraries are never inlined). Slide uses it.
- **Type checking:** each TypeScript iDevice ships its own `tsconfig.json`
(strict for new code); the runner executes `tsc -p` for every one it finds.
- **Tests:** colocated `*.spec.ts` next to each module, run by **Vitest**
(`bun test` ignores `public/**`), plus bundle-contract smoke tests that
evaluate the compiled IIFEs.
- **Artifacts:** generated bundles and source maps are gitignored;
`build:all` runs `typecheck:idevices` + `bundle:idevices` before
`bundle:resources` (export bundles ship inside `idevices.zip`).

Package scripts: `typecheck:idevices`, `bundle:idevices`,
`bundle:idevices:watch`; the runner accepts `--only <names>` and `--watch`.

## Options considered

### Option 1: One bespoke build script per TypeScript iDevice (status quo)

Pros: each script is trivially readable. Cons: duplicated plumbing, per-iDevice
package.json entries, drift between scripts (they already differed in
sourcemaps, watch support and failure reporting).

### Option 2: Convention-based central runner + per-iDevice manifest (chosen)

Pros: the next TypeScript iDevice needs no build changes at all; one place to
fix bundler behaviour; deviations are declared, not programmed. Cons: one more
convention to know; the manifest is a small new format (documented in the
runner header and `doc/development/idevices-typescript.md`).

## Consequences

### Positive

- Adding a TypeScript iDevice = create `src/edition|export/index.ts` (+ a
strict `tsconfig.json`); building, type-checking and watching come for free.
- Slide and Interactive Video share one build path; Slide's output stayed
byte-identical apart from the generic externals shim's message strings.

### Negative

- A hidden convention: `src/` now has meaning. Mitigated by this ADR,
`doc/development/idevices-typescript.md` and the idevice skill.

### Neutral

- Classic-script iDevices are untouched; nothing forces a migration.

## Validation

- `scripts/build-idevices.spec.ts` covers discovery, the convention, the
manifest and its validation against the real repository state.
- `bun run build:all` exercises typecheck + build for every TypeScript
iDevice on every bundle/test target.

## References

- `scripts/build-idevices.ts` (runner; manifest schema in its header).
- `doc/development/idevices-typescript.md` (developer guide).
- PR [#2147](https://github.com/exelearning/exelearning/pull/2147).
Loading
Loading