From bda9aae40d1217943815d156c8cf02259763a90d Mon Sep 17 00:00:00 2001 From: konojunya Date: Fri, 4 Sep 2026 01:45:09 +0900 Subject: [PATCH] Integrate the core icon catalog --- Cargo.lock | 8 +-- Cargo.toml | 2 +- README.md | 2 +- THIRD_PARTY_LICENSES.md | 6 +-- crates/stack-engine-wasm/Cargo.toml | 2 +- crates/stack-engine/Cargo.toml | 2 +- crates/stack-engine/src/lib.rs | 52 ++++++++++++++++++- .../tests/fixtures/explicit-core-icon.stack | 11 ++++ crates/stack-engine/tests/render_snapshots.rs | 22 ++++++++ .../snapshots/render/complete-semantics.svg | 4 +- .../render/default-normalization.svg | 4 +- .../snapshots/render/explicit-core-icon.svg | 28 ++++++++++ docs/dependency-audit.md | 6 +-- package-lock.json | 6 +-- package.json | 2 +- packages/engine/README.md | 4 +- packages/engine/THIRD_PARTY_LICENSES.md | 2 +- packages/engine/package.json | 2 +- scripts/validate-svg.py | 7 +++ tests/fixtures/operation-cases.json | 7 +++ tests/wasm.test.mjs | 19 ++++++- 21 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 crates/stack-engine/tests/fixtures/explicit-core-icon.stack create mode 100644 crates/stack-engine/tests/snapshots/render/explicit-core-icon.svg diff --git a/Cargo.lock b/Cargo.lock index 5e628ff..3c8ef02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,7 +116,7 @@ source = "git+https://github.com/stack-sh/compiler.git?rev=3d2379483da1edaeb24a2 [[package]] name = "stack-engine" -version = "0.2.0" +version = "0.3.0" dependencies = [ "stack-compiler", "stack-formatter", @@ -125,7 +125,7 @@ dependencies = [ [[package]] name = "stack-engine-wasm" -version = "0.2.0" +version = "0.3.0" dependencies = [ "js-sys", "serde", @@ -144,8 +144,8 @@ dependencies = [ [[package]] name = "stack-theme" -version = "0.1.0" -source = "git+https://github.com/stack-sh/theme.git?rev=ed6c500762fc9ccffc8777172ac672a716dcd916#ed6c500762fc9ccffc8777172ac672a716dcd916" +version = "0.2.0" +source = "git+https://github.com/stack-sh/theme.git?rev=d25b883884420adcc124e4c9c786ad92925eae60#d25b883884420adcc124e4c9c786ad92925eae60" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index e9c3d41..629f880 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,4 @@ serde = { version = "=1.0.229", features = ["derive"] } serde_json = "=1.0.151" stack-compiler = { git = "https://github.com/stack-sh/compiler.git", rev = "3d2379483da1edaeb24a26d43743587a4f5bd645" } stack-formatter = { path = "crates/stack-formatter" } -stack-theme = { git = "https://github.com/stack-sh/theme.git", rev = "ed6c500762fc9ccffc8777172ac672a716dcd916" } +stack-theme = { git = "https://github.com/stack-sh/theme.git", rev = "d25b883884420adcc124e4c9c786ad92925eae60" } diff --git a/README.md b/README.md index 0096882..91d7642 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ cargo doc --workspace --no-deps `stack-engine` exposes byte-oriented `format`, `check`, and `render` methods through an engine bound to the embedded or a caller-provided validated catalog. Every normal output carries engine, authored language, theme catalog version, and theme catalog revision metadata. User-source failures stay in ordered portable diagnostics. Invalid provided catalogs and violated normalized pipeline invariants use a separate operational-error channel. Checks and renders resolve the requested theme, validate deterministic integer geometry, and route ordered edges outside node interiors. Missing themes and icons produce source-mapped `STK6001` and `STK5001` warnings while a fallback SVG remains available. An unsatisfied authored order hint produces `STK4001` at its source-map range; a satisfied hint does not. -The renderer emits fixed-dimension standalone SVG with embedded catalog icons, local marker references, escaped authored text, accessible title and description metadata, and no script, event handler, external URL, host font measurement, or runtime I/O. Canonical SVG snapshots are byte-stable and parsed by `scripts/validate-svg.py`; set `UPDATE_STACK_SNAPSHOTS=1` only when intentionally regenerating them. CI also executes one exact numeric geometry fixture in both the native suite and a WASI build. +The renderer emits fixed-dimension standalone SVG with embedded catalog icons, local marker references, escaped authored text, accessible title and description metadata, and no script, event handler, external URL, host font measurement, or runtime I/O. The bundled catalog provides the first-party explicit icon identifiers `api`, `web`, `mobile`, `desktop`, `server`, `container`, `cluster`, `cloud`, `scheduler`, `webhook`, `identity`, and `observability` in every core theme. Canonical SVG snapshots are byte-stable and parsed by `scripts/validate-svg.py`; set `UPDATE_STACK_SNAPSHOTS=1` only when intentionally regenerating them. CI also executes one exact numeric geometry fixture in both the native suite and a WASI build. The npm package exports synchronous `format`, `check`, and `render` functions after asynchronous module initialization. Each operation accepts `string | Uint8Array` and returns a specific typed result with camel-case metadata and portable diagnostics. Diagnostics preserve the compiler's primary range, ordered `expected` values, corrective help, and related source locations. Invalid UTF-8 remains a normal `STK1001` result. Unsupported JavaScript input types and internal operational failures throw at the adapter boundary. Shared fixtures compare complete native and WebAssembly results, including formatted source, diagnostics, SVG, and metadata. Artifact validation audits WebAssembly imports and package contents; browser consumers retain responsibility for loading the module and performing any DOM, filesystem, network, or clock work. diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index 39da578..1a9bbc4 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -4,8 +4,8 @@ | Component | Revision | License | Source | Notes | | --- | --- | --- | --- | --- | -| `stack-compiler` | `17a0abe9c35e641761ff08fdf59b29a42828d9fd` | Apache-2.0 | | Unmodified Rust dependency; its license and notice obligations apply to distributions that include it. | -| `stack-theme` | `ed6c500762fc9ccffc8777172ac672a716dcd916` | Apache-2.0 | | Unmodified Rust dependency with repository-authored core SVG assets and deterministic metrics; its license and notice obligations apply to distributions that include it. | +| `stack-compiler` | `3d2379483da1edaeb24a26d43743587a4f5bd645` | Apache-2.0 | | Unmodified Rust dependency; its license and notice obligations apply to distributions that include it. | +| `stack-theme` | `d25b883884420adcc124e4c9c786ad92925eae60` | Apache-2.0 | | Unmodified Rust dependency with repository-authored fallback and explicit core SVG assets and deterministic metrics; its license and notice obligations apply to distributions that include it. | | `serde` / `serde_core` | `1.0.229` | MIT OR Apache-2.0 | | Runtime catalog data types through `stack-theme`. | | `serde_json` | `1.0.151` | MIT OR Apache-2.0 | | Runtime embedded-catalog decoding through `stack-theme`; also used by formatter conformance tests. | | `itoa` | `1.0.18` | MIT OR Apache-2.0 | | Transitive runtime dependency of `serde_json`. | @@ -32,6 +32,6 @@ | `wasm-bindgen-cli` | `0.2.127` | MIT OR Apache-2.0 | | Version-matched build tool; not shipped in the npm package. | | `typescript` | `7.0.2` | Apache-2.0 | | Type-check tool; not shipped in the npm package. | -No third-party visual asset is bundled in a Stack Engine distribution. The npm package includes this inventory and the Apache-2.0, MIT, and Unicode-3.0 license texts required by its compiled dependency choices. +No third-party visual asset is bundled in a Stack Engine distribution. The bundled fallback and 12 explicit icons are Stack-authored Apache-2.0 assets from `stack-theme`. The npm package includes this inventory and the Apache-2.0, MIT, and Unicode-3.0 license texts required by its compiled dependency choices. Before publishing a native library, binary-derived artifact, or WASM package, this inventory must list the shipped dependencies and assets, their pinned versions, exact licenses, required license texts, attribution, modifications, and redistribution conditions. Build-only dependencies that are not shipped should be distinguished from distributed code. diff --git a/crates/stack-engine-wasm/Cargo.toml b/crates/stack-engine-wasm/Cargo.toml index 135bd04..cd00609 100644 --- a/crates/stack-engine-wasm/Cargo.toml +++ b/crates/stack-engine-wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stack-engine-wasm" -version = "0.2.0" +version = "0.3.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/stack-engine/Cargo.toml b/crates/stack-engine/Cargo.toml index e37c918..30e7627 100644 --- a/crates/stack-engine/Cargo.toml +++ b/crates/stack-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stack-engine" -version = "0.2.0" +version = "0.3.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/stack-engine/src/lib.rs b/crates/stack-engine/src/lib.rs index 7ec6a24..c8777dd 100644 --- a/crates/stack-engine/src/lib.rs +++ b/crates/stack-engine/src/lib.rs @@ -521,7 +521,7 @@ mod tests { output.metadata.language_version, Some(LanguageVersion { major: 1, minor: 0 }) ); - assert_eq!(output.metadata.theme_catalog_version, "0.1.0"); + assert_eq!(output.metadata.theme_catalog_version, "0.2.0"); assert_eq!( output.metadata.theme_catalog_revision, stack_theme::CATALOG_REVISION @@ -530,6 +530,56 @@ mod tests { } } + #[test] + fn bundled_catalog_resolves_explicit_core_icons() -> Result<(), Box> { + let expected_icons = [ + ("api", "Application programming interface"), + ("web", "Web application"), + ("mobile", "Mobile application"), + ("desktop", "Desktop application"), + ("server", "Server host"), + ("container", "Application container"), + ("cluster", "Compute cluster"), + ("cloud", "Cloud environment"), + ("scheduler", "Scheduled execution"), + ("webhook", "Webhook endpoint"), + ("identity", "Identity and access"), + ("observability", "Observability system"), + ]; + let catalog = stack_theme::catalog(); + assert_eq!(catalog.catalog_version, "0.2.0"); + assert_eq!( + stack_theme::CATALOG_REVISION, + "sha256:d3a8a5a9d2100e496af3fd7adf389788f4a77508bf749a108183a2abf8f681e1" + ); + for theme in &catalog.themes { + for (identifier, subject) in expected_icons { + let icon = theme + .icons + .iter() + .find(|icon| icon.id == identifier) + .ok_or("core icon is unavailable in a bundled theme")?; + assert_eq!(icon.subject, subject); + assert_eq!(icon.asset.path, format!("assets/core/{identifier}.svg")); + } + } + + let source = b"stack 1.0 diagram \"Core icon\" { theme dark node gateway \"Gateway\" { kind service detail \"Public API\" icon \"api\" } }"; + let checked = Engine::bundled().check(source)?; + let rendered = Engine::bundled().render(source)?; + assert!(checked.diagnostics.is_empty()); + assert!(rendered.diagnostics.is_empty()); + assert_eq!(rendered.metadata.theme_catalog_version, "0.2.0"); + assert_eq!( + rendered.metadata.theme_catalog_revision, + stack_theme::CATALOG_REVISION + ); + let svg = rendered.svg.ok_or("explicit icon render produced no SVG")?; + assert!(svg.contains("data-icon-id=\"api\"")); + assert!(!svg.contains("data-icon-id=\"kind-external\"")); + Ok(()) + } + #[test] fn format_preserves_semantic_diagnostics_but_not_syntax_failures() { let engine = Engine::bundled(); diff --git a/crates/stack-engine/tests/fixtures/explicit-core-icon.stack b/crates/stack-engine/tests/fixtures/explicit-core-icon.stack new file mode 100644 index 0000000..4eb9c9e --- /dev/null +++ b/crates/stack-engine/tests/fixtures/explicit-core-icon.stack @@ -0,0 +1,11 @@ +stack 1.0 + +diagram "Core icon" { + theme dark + + node gateway "Gateway" { + kind service + detail "Public API" + icon "api" + } +} diff --git a/crates/stack-engine/tests/render_snapshots.rs b/crates/stack-engine/tests/render_snapshots.rs index 4b97b3b..a0489d3 100644 --- a/crates/stack-engine/tests/render_snapshots.rs +++ b/crates/stack-engine/tests/render_snapshots.rs @@ -48,6 +48,28 @@ fn canonical_valid_fixtures_match_standalone_svg_snapshots() -> Result<(), Box Result<(), Box> { + let source = include_bytes!("fixtures/explicit-core-icon.stack"); + let output = Engine::bundled().render(source)?; + assert!(output.diagnostics.is_empty()); + assert_eq!(output.metadata.theme_catalog_version, "0.2.0"); + assert_eq!( + output.metadata.theme_catalog_revision, + stack_theme::CATALOG_REVISION + ); + let svg = output.svg.ok_or("explicit icon fixture produced no SVG")?; + assert!(svg.contains("data-icon-id=\"api\"")); + let snapshot = + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/snapshots/render/explicit-core-icon.svg"); + if std::env::var_os("UPDATE_STACK_SNAPSHOTS").is_some() { + std::fs::write(&snapshot, &svg)?; + } else { + assert_eq!(svg, std::fs::read_to_string(&snapshot)?); + } + Ok(()) +} + fn specification_root() -> Result> { let configured = std::env::var_os("STACK_SPECIFICATION_DIR") .ok_or("STACK_SPECIFICATION_DIR must point to stack-sh/specification")?; diff --git a/crates/stack-engine/tests/snapshots/render/complete-semantics.svg b/crates/stack-engine/tests/snapshots/render/complete-semantics.svg index 5c0cc80..6cde184 100644 --- a/crates/stack-engine/tests/snapshots/render/complete-semantics.svg +++ b/crates/stack-engine/tests/snapshots/render/complete-semantics.svg @@ -1,8 +1,8 @@ - + Complete semantics Architecture diagram with 10 nodes, 3 groups, and 8 relationships. - stack-engine 0.2.0; language 1.0; theme 0.1.0 at sha256:b54ca3435931675d36d6a69b5154a0bd6ce2cbae1cfe14a20a095a8e24f8bae4 + stack-engine 0.3.0; language 1.0; theme 0.2.0 at sha256:d3a8a5a9d2100e496af3fd7adf389788f4a77508bf749a108183a2abf8f681e1 diff --git a/crates/stack-engine/tests/snapshots/render/default-normalization.svg b/crates/stack-engine/tests/snapshots/render/default-normalization.svg index 0f8de12..965d8f1 100644 --- a/crates/stack-engine/tests/snapshots/render/default-normalization.svg +++ b/crates/stack-engine/tests/snapshots/render/default-normalization.svg @@ -1,8 +1,8 @@ - + Default normalization Architecture diagram with 2 nodes, 0 groups, and 1 relationship. - stack-engine 0.2.0; language 1.0; theme 0.1.0 at sha256:b54ca3435931675d36d6a69b5154a0bd6ce2cbae1cfe14a20a095a8e24f8bae4 + stack-engine 0.3.0; language 1.0; theme 0.2.0 at sha256:d3a8a5a9d2100e496af3fd7adf389788f4a77508bf749a108183a2abf8f681e1 diff --git a/crates/stack-engine/tests/snapshots/render/explicit-core-icon.svg b/crates/stack-engine/tests/snapshots/render/explicit-core-icon.svg new file mode 100644 index 0000000..4c74809 --- /dev/null +++ b/crates/stack-engine/tests/snapshots/render/explicit-core-icon.svg @@ -0,0 +1,28 @@ + + + Core icon + Architecture diagram with 1 node, 0 groups, and 0 relationships. + stack-engine 0.3.0; language 1.0; theme 0.2.0 at sha256:d3a8a5a9d2100e496af3fd7adf389788f4a77508bf749a108183a2abf8f681e1 + + + + + + + Core icon + + + + + + + Gateway: Public API + + + Gateway + Public API + + + + diff --git a/docs/dependency-audit.md b/docs/dependency-audit.md index 170d6b4..5347ab2 100644 --- a/docs/dependency-audit.md +++ b/docs/dependency-audit.md @@ -1,14 +1,14 @@ # Dependency and host-I/O audit -Audit date: 2026-09-03 +Audit date: 2026-09-04 ## Runtime graph `stack-engine` has three direct dependencies: -- `stack-compiler` at `17a0abe9c35e641761ff08fdf59b29a42828d9fd` for byte decoding, parsing, validation, normalized IR, source maps, and compiler diagnostics; +- `stack-compiler` at `3d2379483da1edaeb24a26d43743587a4f5bd645` for byte decoding, parsing, validation, normalized IR, source maps, and compiler diagnostics; - the workspace-local `stack-formatter` for canonical source output; -- `stack-theme` at `ed6c500762fc9ccffc8777172ac672a716dcd916` for the embedded core catalog, SVG bytes, deterministic font metrics, catalog version, and catalog revision. +- `stack-theme` at `d25b883884420adcc124e4c9c786ad92925eae60` for the `0.2.0` embedded core catalog, 12 provider-neutral explicit icons, SVG bytes, deterministic font metrics, catalog version, and catalog revision. `stack-engine-wasm` adds `serde` for its serializable native parity model and, only on `wasm32`, version-matched `wasm-bindgen` and `js-sys` for the JavaScript ABI, typed-array input, and plain object construction. It does not use `web-sys` or a WASI target. diff --git a/package-lock.json b/package-lock.json index 0ea53f4..d0697ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "stack-engine-workspace", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "stack-engine-workspace", - "version": "0.2.0", + "version": "0.3.0", "workspaces": [ "packages/engine" ], @@ -395,7 +395,7 @@ }, "packages/engine": { "name": "@stack-sh/engine", - "version": "0.2.0", + "version": "0.3.0", "license": "Apache-2.0" } } diff --git a/package.json b/package.json index cc74ea8..3163167 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stack-engine-workspace", "private": true, - "version": "0.2.0", + "version": "0.3.0", "workspaces": [ "packages/engine" ], diff --git a/packages/engine/README.md b/packages/engine/README.md index d4327bf..96789f6 100644 --- a/packages/engine/README.md +++ b/packages/engine/README.md @@ -9,9 +9,11 @@ await init(); const formatted = format('stack 1.0 diagram "API" { node api "API" }'); const checked = check(new TextEncoder().encode('stack 1.0 diagram "API" { node api "API" }')); -const rendered = render('stack 1.0 diagram "API" { node api "API" }'); +const rendered = render('stack 1.0 diagram "API" { node api "API" { icon "api" } }'); ``` Each operation is synchronous after module initialization and accepts either a JavaScript string or `Uint8Array`. Invalid Stack source, including invalid UTF-8 bytes, returns normal portable diagnostics. Diagnostics include the primary range, ordered `expected` values, corrective help, and related source locations. A JavaScript value of any other type throws `TypeError` at the package boundary. The package does not read files, contact a network service, inspect the DOM, observe a clock, or measure host fonts. Consumers own module loading and all host I/O. + +The bundled catalog resolves `api`, `web`, `mobile`, `desktop`, `server`, `container`, `cluster`, `cloud`, `scheduler`, `webhook`, `identity`, and `observability` as first-party explicit icons in the `default`, `light`, and `dark` themes. Missing authored icon identifiers still produce `STK5001` and render the theme's fallback icon. diff --git a/packages/engine/THIRD_PARTY_LICENSES.md b/packages/engine/THIRD_PARTY_LICENSES.md index 2e2192f..6c94df4 100644 --- a/packages/engine/THIRD_PARTY_LICENSES.md +++ b/packages/engine/THIRD_PARTY_LICENSES.md @@ -5,7 +5,7 @@ | Component | Version / revision | Selected license | Source | | --- | --- | --- | --- | | `stack-compiler` | `3d2379483da1edaeb24a26d43743587a4f5bd645` | Apache-2.0 | | -| `stack-theme` | `ed6c500762fc9ccffc8777172ac672a716dcd916` | Apache-2.0 | | +| `stack-theme` | `d25b883884420adcc124e4c9c786ad92925eae60` | Apache-2.0 | | | `serde` / `serde_core` | `1.0.229` | Apache-2.0 | | | `serde_json` | `1.0.151` | Apache-2.0 | | | `itoa` | `1.0.18` | Apache-2.0 | | diff --git a/packages/engine/package.json b/packages/engine/package.json index 3d41e55..ed82e7b 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -1,6 +1,6 @@ { "name": "@stack-sh/engine", - "version": "0.2.0", + "version": "0.3.0", "description": "Browser WebAssembly adapter for Stack diagram operations", "type": "module", "license": "Apache-2.0", diff --git a/scripts/validate-svg.py b/scripts/validate-svg.py index dc0fc5d..1eed140 100644 --- a/scripts/validate-svg.py +++ b/scripts/validate-svg.py @@ -62,6 +62,7 @@ def main() -> None: assert snapshots, "no render snapshots found" documents = {path.stem: validate(path) for path in snapshots} complete = documents["complete-semantics"] + explicit_icon = documents["explicit-core-icon"] assert values(complete, "data-node-kind") == { "actor", "client", @@ -86,6 +87,12 @@ def main() -> None: "bidirectional", "association", } + assert values(explicit_icon, "data-icon-id") == {"api"} + assert explicit_icon.attrib.get("data-theme-version") == "0.2.0" + assert ( + explicit_icon.attrib.get("data-theme-revision") + == "sha256:d3a8a5a9d2100e496af3fd7adf389788f4a77508bf749a108183a2abf8f681e1" + ) if __name__ == "__main__": diff --git a/tests/fixtures/operation-cases.json b/tests/fixtures/operation-cases.json index 8be7cbd..887a0d2 100644 --- a/tests/fixtures/operation-cases.json +++ b/tests/fixtures/operation-cases.json @@ -13,6 +13,13 @@ "value": "stack 1.0 diagram \"Fallback\" { theme neon node api \"API\" { icon \"missing\" } }" } }, + { + "name": "explicit-core-icon-string", + "input": { + "kind": "string", + "value": "stack 1.0\ndiagram \"Core icon\" {\n theme dark\n node gateway \"Gateway\" {\n kind service\n detail \"Public API\"\n icon \"api\"\n }\n}\n" + } + }, { "name": "actionable-error-string", "input": { diff --git a/tests/wasm.test.mjs b/tests/wasm.test.mjs index c097b02..8802040 100644 --- a/tests/wasm.test.mjs +++ b/tests/wasm.test.mjs @@ -72,7 +72,7 @@ test("browser diagnostics preserve actionable compiler guidance", () => { ); assert.ok(actionable); assert.equal(actionable.render.svg, null); - assert.equal(actionable.check.metadata.engineVersion, "0.2.0"); + assert.equal(actionable.check.metadata.engineVersion, "0.3.0"); assert.deepEqual(actionable.check.diagnostics[0], { code: "STK2002", severity: "error", @@ -87,6 +87,23 @@ test("browser diagnostics preserve actionable compiler guidance", () => { }); }); +test("browser rendering resolves the bundled explicit core icon", () => { + const explicitIcon = wasmOutputs().find( + ({ name }) => name === "explicit-core-icon-string", + ); + assert.ok(explicitIcon); + assert.deepEqual(explicitIcon.check.diagnostics, []); + assert.deepEqual(explicitIcon.render.diagnostics, []); + assert.equal(explicitIcon.render.metadata.engineVersion, "0.3.0"); + assert.equal(explicitIcon.render.metadata.themeCatalogVersion, "0.2.0"); + assert.equal( + explicitIcon.render.metadata.themeCatalogRevision, + "sha256:d3a8a5a9d2100e496af3fd7adf389788f4a77508bf749a108183a2abf8f681e1", + ); + assert.match(explicitIcon.render.svg, /data-icon-id="api"/); + assert.doesNotMatch(explicitIcon.render.svg, /data-icon-id="kind-external"/); +}); + test("the JavaScript boundary rejects unsupported source values consistently", () => { for (const operation of [format, check, render]) { assert.throws(