tessera-learn 0.2.0 → 0.2.1
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/AGENTS.md +11 -11
- package/dist/{audit-BA5o0ick.js → audit-BNrvFHq_.js} +49 -22
- package/dist/{audit-BA5o0ick.js.map → audit-BNrvFHq_.js.map} +1 -1
- package/dist/{build-commands-C0OnV-Vg.js → build-commands-BWnATKat.js} +2 -2
- package/dist/{build-commands-C0OnV-Vg.js.map → build-commands-BWnATKat.js.map} +1 -1
- package/dist/{inline-config-CroQ-_2Y.js → inline-config-Dudu5r8w.js} +2 -2
- package/dist/{inline-config-CroQ-_2Y.js.map → inline-config-Dudu5r8w.js.map} +1 -1
- package/dist/plugin/cli.js +3 -3
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -2
- package/dist/{plugin-W_rk3Pit.js → plugin-diNZaDJK.js} +2 -2
- package/dist/{plugin-W_rk3Pit.js.map → plugin-diNZaDJK.js.map} +1 -1
- package/package.json +1 -1
- package/src/plugin/a11y/audit.ts +74 -18
- package/src/runtime/App.svelte +20 -1
- package/src/virtual.d.ts +6 -0
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ pnpm tessera export <name> # each course exports independently to its own LMS p
|
|
|
37
37
|
cd courses/<name> && pnpm exec tessera export # …this works for every command, not just dev
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
A **bare command at the workspace root errors** and lists the available courses — it never silently picks one, so its meaning can't change as you add courses. Name the course, or `cd` into its folder. (The scaffolded root scripts — `pnpm dev`, `pnpm export`, … —
|
|
40
|
+
A **bare command at the workspace root errors** and lists the available courses — it never silently picks one, so its meaning can't change as you add courses. Name the course, or `cd` into its folder. (The scaffolded root scripts — `pnpm dev`, `pnpm export`, … — pass straight through, so `pnpm dev <course>` runs that course and a bare `pnpm dev` errors just the same.)
|
|
41
41
|
|
|
42
42
|
### Sharing across courses with `$shared`
|
|
43
43
|
|
|
@@ -58,21 +58,21 @@ A **bare command at the workspace root errors** and lists the available courses
|
|
|
58
58
|
|
|
59
59
|
## Running the project
|
|
60
60
|
|
|
61
|
-
From the
|
|
61
|
+
From the workspace root (set up for `pnpm` — Node's corepack provisions it automatically). The `dev`/`export`/`validate`/`check` scripts take the course to run; a bare command lists the workspace's courses rather than picking one:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
pnpm install # first time only
|
|
65
|
-
pnpm dev
|
|
66
|
-
pnpm export
|
|
67
|
-
pnpm validate
|
|
68
|
-
pnpm check
|
|
65
|
+
pnpm dev <course> # dev server at http://localhost:5173 (Ctrl+C to stop)
|
|
66
|
+
pnpm export <course> # build + package for the LMS standard configured in course.config.js
|
|
67
|
+
pnpm validate <course> # run project validation only — no server, no bundle
|
|
68
|
+
pnpm check <course> # validate, then the runtime accessibility audit (axe) over the built course
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
The dev server hot-reloads as you edit pages, layouts, components, and `course.config.js`. The `export` command produces a SCORM 1.2, SCORM 2004, cmi5, or static-web bundle depending on `course.config.js`.
|
|
72
72
|
|
|
73
|
-
`pnpm validate
|
|
73
|
+
`pnpm validate <course>` runs the same checks as `dev` and `export` (page syntax, manifest shape, `pageConfig`, question components, asset references, LMS data-contract bypass, and the static accessibility rules) and exits non-zero if any fail. Use it as a fast feedback loop after editing — it's the quickest way to confirm a change is structurally sound.
|
|
74
74
|
|
|
75
|
-
`pnpm check
|
|
75
|
+
`pnpm check <course>` runs `validate` and then the deeper, opt-in pass (`tessera a11y`): it builds the course, renders every page in a headless browser, and runs [axe-core](https://github.com/dequelabs/axe-core) to catch issues a static scan can't see (computed ARIA, real rendered contrast). The runtime audit drives Playwright, which needs a browser binary once per machine:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
pnpm exec playwright install chromium
|
|
@@ -892,7 +892,7 @@ export default {
|
|
|
892
892
|
|
|
893
893
|
### Build output
|
|
894
894
|
|
|
895
|
-
`pnpm export
|
|
895
|
+
`pnpm export <course>` (which wraps `vite build`) writes:
|
|
896
896
|
|
|
897
897
|
| `export.standard` | What ships | Where |
|
|
898
898
|
| ----------------- | ------------------------------------- | ---------------------------------------- |
|
|
@@ -905,7 +905,7 @@ For LMS exports, upload the zip via your LMS's import flow. For web export, the
|
|
|
905
905
|
|
|
906
906
|
### Validation
|
|
907
907
|
|
|
908
|
-
The Vite plugin runs project validation on every dev start and build (page syntax, manifest shape, `pageConfig` parseability, question components, asset references, LMS data-contract bypass, etc.). Errors abort the build and print as `[tessera error] ...`; warnings print as `[tessera warning] ...` and don't block. Run `pnpm validate
|
|
908
|
+
The Vite plugin runs project validation on every dev start and build (page syntax, manifest shape, `pageConfig` parseability, question components, asset references, LMS data-contract bypass, etc.). Errors abort the build and print as `[tessera error] ...`; warnings print as `[tessera warning] ...` and don't block. Run `pnpm validate <course>` to check without building.
|
|
909
909
|
|
|
910
910
|
---
|
|
911
911
|
|
|
@@ -915,7 +915,7 @@ Tessera checks accessibility in two passes, plus components that are accessible
|
|
|
915
915
|
|
|
916
916
|
**Static checks** run inside `validate`, `dev`, and `export` — no extra setup. They cover what's visible in your source: `<Image>` alt-or-`decorative`, `<Video>`/`<Audio>` `title` + captions/transcript, empty question option/answer labels, skipped heading levels (e.g. `h2` → `h4`), `branding.primaryColor` contrast against white, and a well-formed `language` tag. They also route the Svelte compiler's own `a11y_*` warnings through the reporter. Each diagnostic carries a rule ID in brackets (e.g. `[tessera/image-alt]`, `[a11y_missing_attribute]`) — that ID is what `a11y.ignore` and `a11y.level` match.
|
|
917
917
|
|
|
918
|
-
**Runtime audit** is the opt-in deep pass: `pnpm a11y
|
|
918
|
+
**Runtime audit** is the opt-in deep pass: `pnpm a11y <course>` (run it directly, or via `pnpm check <course>`, which runs `validate` first) builds the course, renders **every** page in a headless browser (including pages gated behind a quiz), runs [axe-core](https://github.com/dequelabs/axe-core), writes `a11y-report.json`, and exits non-zero on any violation at or above an impact threshold (default `serious`). It catches what a static scan can't — computed ARIA, focus order, real rendered contrast.
|
|
919
919
|
|
|
920
920
|
The runtime audit drives Playwright, which needs a browser binary once per machine:
|
|
921
921
|
|
|
@@ -1446,7 +1446,7 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1446
1446
|
const disableRules = axeIgnoreRules(settings.ignore);
|
|
1447
1447
|
const manifest = generateManifest(resolve(projectRoot, "pages"));
|
|
1448
1448
|
const vite = await import("vite");
|
|
1449
|
-
const { resolveTesseraConfig } = await import("./inline-config-
|
|
1449
|
+
const { resolveTesseraConfig } = await import("./inline-config-Dudu5r8w.js");
|
|
1450
1450
|
const auditBaseConfig = await resolveTesseraConfig(projectRoot, workspaceRoot, {
|
|
1451
1451
|
command: "build",
|
|
1452
1452
|
mode: "production"
|
|
@@ -1510,23 +1510,29 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1510
1510
|
nodes: v.nodes.length
|
|
1511
1511
|
}));
|
|
1512
1512
|
};
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
await page.waitForFunction((idx) => document.querySelectorAll("button.tessera-nav-page")[idx]?.getAttribute("aria-current") === "page", i, { timeout: 2e4 });
|
|
1524
|
-
await page.waitForLoadState("networkidle");
|
|
1525
|
-
pages.push({
|
|
1526
|
-
index: i,
|
|
1513
|
+
const recordPage = async (index, title) => {
|
|
1514
|
+
const loadFailed = await page.evaluate(() => document.getElementById("tessera-app")?.dataset.tesseraPageError === "true");
|
|
1515
|
+
if (loadFailed) return {
|
|
1516
|
+
index,
|
|
1517
|
+
title,
|
|
1518
|
+
violations: [],
|
|
1519
|
+
loadFailed
|
|
1520
|
+
};
|
|
1521
|
+
return {
|
|
1522
|
+
index,
|
|
1527
1523
|
title,
|
|
1528
1524
|
violations: await scan()
|
|
1529
|
-
}
|
|
1525
|
+
};
|
|
1526
|
+
};
|
|
1527
|
+
const totalPages = manifest.pages.length;
|
|
1528
|
+
if (!await page.evaluate(() => typeof window.__tesseraAudit?.goToIndex === "function")) {
|
|
1529
|
+
if (totalPages > 1) console.warn(`\x1b[33m[tessera a11y]\x1b[0m Could not enumerate pages; auditing the entry page only (1 of ${totalPages}). The report records the reduced scope.`);
|
|
1530
|
+
pages.push(await recordPage(0, manifest.pages[0]?.title ?? "(entry)"));
|
|
1531
|
+
} else for (let i = 0; i < totalPages; i++) {
|
|
1532
|
+
await page.evaluate((idx) => window.__tesseraAudit.goToIndex(idx), i);
|
|
1533
|
+
await page.waitForFunction((idx) => document.getElementById("tessera-app")?.dataset.tesseraPageIndex === String(idx), i, { timeout: 2e4 });
|
|
1534
|
+
await page.waitForLoadState("networkidle");
|
|
1535
|
+
pages.push(await recordPage(i, manifest.pages[i]?.title ?? `Page ${i + 1}`));
|
|
1530
1536
|
}
|
|
1531
1537
|
} finally {
|
|
1532
1538
|
await browser.close();
|
|
@@ -1534,17 +1540,24 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1534
1540
|
const thresholdRank = IMPACT_RANK[threshold];
|
|
1535
1541
|
let totalViolations = 0;
|
|
1536
1542
|
let failingViolations = 0;
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
if (
|
|
1543
|
+
let pagesFailedToLoad = 0;
|
|
1544
|
+
for (const p of pages) {
|
|
1545
|
+
if (p.loadFailed) pagesFailedToLoad++;
|
|
1546
|
+
for (const v of p.violations) {
|
|
1547
|
+
totalViolations++;
|
|
1548
|
+
if (isFailing(v, thresholdRank)) failingViolations++;
|
|
1549
|
+
}
|
|
1540
1550
|
}
|
|
1541
1551
|
const report = {
|
|
1542
1552
|
standard: settings.standard,
|
|
1543
1553
|
threshold,
|
|
1544
1554
|
pages,
|
|
1555
|
+
pagesAudited: pages.length,
|
|
1556
|
+
totalPages: manifest.pages.length,
|
|
1557
|
+
pagesFailedToLoad,
|
|
1545
1558
|
totalViolations,
|
|
1546
1559
|
failingViolations,
|
|
1547
|
-
passed: failingViolations === 0
|
|
1560
|
+
passed: failingViolations === 0 && pagesFailedToLoad === 0
|
|
1548
1561
|
};
|
|
1549
1562
|
const reportPath = resolve(projectRoot, "a11y-report.json");
|
|
1550
1563
|
writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
@@ -1562,6 +1575,10 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1562
1575
|
function printSummary(report, reportPath) {
|
|
1563
1576
|
const thresholdRank = IMPACT_RANK[report.threshold];
|
|
1564
1577
|
for (const p of report.pages) {
|
|
1578
|
+
if (p.loadFailed) {
|
|
1579
|
+
console.log(`\x1b[31m ✗\x1b[0m ${p.title} — failed to load`);
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1565
1582
|
if (p.violations.length === 0) {
|
|
1566
1583
|
console.log(`\x1b[32m ✓\x1b[0m ${p.title}`);
|
|
1567
1584
|
continue;
|
|
@@ -1571,10 +1588,20 @@ function printSummary(report, reportPath) {
|
|
|
1571
1588
|
for (const v of p.violations) console.log(` [${v.impact ?? "n/a"}] ${v.id} — ${v.help} (${v.nodes} node${v.nodes === 1 ? "" : "s"})`);
|
|
1572
1589
|
}
|
|
1573
1590
|
console.log(`\n[tessera a11y] Report written to ${reportPath}`);
|
|
1591
|
+
if (report.pagesAudited < report.totalPages) console.log(`\x1b[33m[tessera a11y] Covered ${report.pagesAudited} of ${report.totalPages} page(s)\x1b[0m — reduced scope, the rest were not audited.`);
|
|
1592
|
+
else if (report.pagesFailedToLoad > 0) {
|
|
1593
|
+
const scanned = report.pagesAudited - report.pagesFailedToLoad;
|
|
1594
|
+
console.log(`[tessera a11y] Reached all ${report.totalPages} page(s); scanned ${scanned}, ${report.pagesFailedToLoad} failed to load.`);
|
|
1595
|
+
} else console.log(`[tessera a11y] Covered all ${report.totalPages} page(s).`);
|
|
1574
1596
|
if (report.passed) console.log(`\x1b[32m[tessera a11y] Passed\x1b[0m — ${report.totalViolations} total finding(s), none at/above "${report.threshold}".`);
|
|
1575
|
-
else
|
|
1597
|
+
else {
|
|
1598
|
+
const reasons = [];
|
|
1599
|
+
if (report.failingViolations > 0) reasons.push(`${report.failingViolations} finding(s) at/above "${report.threshold}" (of ${report.totalViolations} total)`);
|
|
1600
|
+
if (report.pagesFailedToLoad > 0) reasons.push(`${report.pagesFailedToLoad} page(s) failed to load`);
|
|
1601
|
+
console.log(`\x1b[31m[tessera a11y] Failed\x1b[0m — ${reasons.join("; ")}.`);
|
|
1602
|
+
}
|
|
1576
1603
|
}
|
|
1577
1604
|
//#endregion
|
|
1578
1605
|
export { isPlausibleLanguageTag as a, validateProject as c, isIgnored as i, generateManifest as l, runAudit as n, normalizeA11y as o, resolvePackageRoot as r, reportValidationIssues as s, AUDIT_ENV_FLAG as t, readCourseConfig as u };
|
|
1579
1606
|
|
|
1580
|
-
//# sourceMappingURL=audit-
|
|
1607
|
+
//# sourceMappingURL=audit-BNrvFHq_.js.map
|