diff --git a/CONTRACT.md b/CONTRACT.md
index 33cca4b..61d0025 100644
--- a/CONTRACT.md
+++ b/CONTRACT.md
@@ -16,11 +16,14 @@ Every catalog has these required top-level fields:
| `schemaVersion` | Catalog document shape; currently `1.0` |
| `catalogVersion` | Semantic version shared by the source catalog, Cargo crate, and npm package |
| `reservedThemeIds` | Previously registered theme identifiers that cannot be reused |
+| `fallbacks` | Core theme and logical icon identifiers used after missing-resource diagnostics |
| `fontMetrics` | Deterministic font measurement records |
| `themes` | Themes in canonical catalog order |
Theme identifiers use the Stack language identifier form. Active identifiers are unique and cannot also be reserved. When a registered theme is removed, its identifier moves to `reservedThemeIds`; it is never reassigned.
+`fallbacks.missingThemeId` must identify an active core theme. `fallbacks.missingIconId` must identify an icon present in every active theme with one stable subject. A renderer emits the applicable missing-resource diagnostic before selecting these records; fallback data does not hide the missing request.
+
`catalogRevision` is generated metadata rather than a source field. It is a `sha256:` digest over the normalized source catalog followed by every referenced SVG path and byte sequence in path order. The revision therefore changes when semantic catalog data, provenance, or icon bytes change, without requiring a Git commit to contain its own commit identifier.
## Theme records
@@ -44,9 +47,9 @@ Integer units keep Rust and JavaScript consumers from introducing representation
## Font metrics
-A font metric record requires `id`, `family`, `version`, `unitsPerEm`, `ascent`, `descent`, `lineGap`, `defaultAdvance`, `glyphAdvances`, and the same source, license, and distribution `provenance` required for icon assets.
+A font metric record requires `id`, `family`, `version`, `unitsPerEm`, `ascent`, `descent`, `lineGap`, `defaultAdvance`, `wideAdvance`, ordered non-overlapping `wideRanges`, `glyphAdvances`, and the same source, license, and distribution `provenance` required for icon assets.
-Advances use integer font design units. `glyphAdvances` keys are uppercase Unicode scalar labels such as `U+0041`. A consumer uses `defaultAdvance` for a scalar without an explicit entry. This makes measurement deterministic and removes any need for a host font API. The catalog version and revision identify the exact measurement table used by a render.
+Advances use integer font design units. `glyphAdvances` keys are uppercase Unicode scalar labels such as `U+0041`. A consumer first uses an explicit glyph advance, then `wideAdvance` for a scalar in an inclusive `wideRanges` entry, and finally `defaultAdvance`. This makes Latin and wide-script measurement deterministic without a host Unicode-width or font API. The metrics version, catalog version, and revision identify the exact table and ranges used by a render.
## Icon metadata and provenance
@@ -87,4 +90,4 @@ cargo clippy --workspace --all-targets --locked -- -D warnings
cargo doc --workspace --no-deps --locked
```
-The complete fixture exercises every required field. Security fixtures prove rejection of script, event-handler, and external-reference SVGs. The source catalog intentionally contains no themes until the core `default`, `light`, and `dark` data lands as the next provider change.
+The complete fixture exercises every required field. Security fixtures prove rejection of script, event-handler, and external-reference SVGs. Catalog completeness, palette contrast, distinct node-kind fallback icons, deterministic wide-character metrics, and embedded Cargo/npm asset parity are also enforced.
diff --git a/README.md b/README.md
index 39ddbf3..750093d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
The draft catalog contract is defined by [`CONTRACT.md`](./CONTRACT.md) and [`schemas/catalog.schema.json`](./schemas/catalog.schema.json). The canonical source is [`catalog/catalog.json`](./catalog/catalog.json); Cargo and npm artifacts are generated from that source with one content revision.
-The current `0.1.0` catalog is intentionally empty. The core `default`, `light`, and `dark` themes will be added as the next reviewed catalog change before either package is released to a registry.
+The current `0.1.0` catalog contains the core `default`, `light`, and `dark` themes, repository-authored fallbacks for every Stack 1.0 node kind, and versioned host-independent font metrics. Neither package has been released to a registry yet.
## Scope
@@ -36,6 +36,8 @@ cargo doc --workspace --no-deps --locked
`npm run generate` is the only supported way to update checked-in package copies and revision metadata. CI validates the schema and asset boundary, rejects unsafe SVG fixtures, compares Cargo and npm catalog semantics, builds both packages, and verifies the minimum Rust version.
+The local [core theme review](./review/index.html) renders every fallback without a build step. Serve the repository root over HTTP, open `/review/`, and compare all three themes before changing palettes, fallback shapes, icons, or typography metrics. The current review record is in [`docs/core-theme-review.md`](./docs/core-theme-review.md).
+
## Licensing
Repository-authored source code, catalog data, and assets are licensed under the [Apache License 2.0](./LICENSE).
diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md
index c8c57eb..076a353 100644
--- a/THIRD_PARTY_LICENSES.md
+++ b/THIRD_PARTY_LICENSES.md
@@ -1,6 +1,6 @@
# Third-party licenses
-No third-party fonts, icons, or other runtime assets are bundled in this repository yet.
+No third-party fonts, icons, or other runtime assets are bundled in this repository. The 30 core SVG assets and `stack_sans` metrics are repository-authored Apache-2.0 work with provenance recorded in the canonical catalog.
The repository uses the following third-party libraries:
diff --git a/assets/dark/kind-actor.svg b/assets/dark/kind-actor.svg
new file mode 100644
index 0000000..c116cd3
--- /dev/null
+++ b/assets/dark/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/dark/kind-cache.svg b/assets/dark/kind-cache.svg
new file mode 100644
index 0000000..4d59b2b
--- /dev/null
+++ b/assets/dark/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/dark/kind-client.svg b/assets/dark/kind-client.svg
new file mode 100644
index 0000000..c9a57cf
--- /dev/null
+++ b/assets/dark/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/dark/kind-database.svg b/assets/dark/kind-database.svg
new file mode 100644
index 0000000..69f2b79
--- /dev/null
+++ b/assets/dark/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/dark/kind-external.svg b/assets/dark/kind-external.svg
new file mode 100644
index 0000000..56906b8
--- /dev/null
+++ b/assets/dark/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/dark/kind-function.svg b/assets/dark/kind-function.svg
new file mode 100644
index 0000000..de7aa41
--- /dev/null
+++ b/assets/dark/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/dark/kind-queue.svg b/assets/dark/kind-queue.svg
new file mode 100644
index 0000000..524df6c
--- /dev/null
+++ b/assets/dark/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/dark/kind-service.svg b/assets/dark/kind-service.svg
new file mode 100644
index 0000000..9490694
--- /dev/null
+++ b/assets/dark/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/dark/kind-storage.svg b/assets/dark/kind-storage.svg
new file mode 100644
index 0000000..674bede
--- /dev/null
+++ b/assets/dark/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/dark/kind-worker.svg b/assets/dark/kind-worker.svg
new file mode 100644
index 0000000..bc4d009
--- /dev/null
+++ b/assets/dark/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/default/kind-actor.svg b/assets/default/kind-actor.svg
new file mode 100644
index 0000000..53c45b4
--- /dev/null
+++ b/assets/default/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/default/kind-cache.svg b/assets/default/kind-cache.svg
new file mode 100644
index 0000000..3b63bd2
--- /dev/null
+++ b/assets/default/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/default/kind-client.svg b/assets/default/kind-client.svg
new file mode 100644
index 0000000..40d3f7b
--- /dev/null
+++ b/assets/default/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/default/kind-database.svg b/assets/default/kind-database.svg
new file mode 100644
index 0000000..5637cd5
--- /dev/null
+++ b/assets/default/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/default/kind-external.svg b/assets/default/kind-external.svg
new file mode 100644
index 0000000..5db6001
--- /dev/null
+++ b/assets/default/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/default/kind-function.svg b/assets/default/kind-function.svg
new file mode 100644
index 0000000..7b377b8
--- /dev/null
+++ b/assets/default/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/default/kind-queue.svg b/assets/default/kind-queue.svg
new file mode 100644
index 0000000..0d5cdf7
--- /dev/null
+++ b/assets/default/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/default/kind-service.svg b/assets/default/kind-service.svg
new file mode 100644
index 0000000..6317048
--- /dev/null
+++ b/assets/default/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/default/kind-storage.svg b/assets/default/kind-storage.svg
new file mode 100644
index 0000000..fb0cff6
--- /dev/null
+++ b/assets/default/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/default/kind-worker.svg b/assets/default/kind-worker.svg
new file mode 100644
index 0000000..a8d5004
--- /dev/null
+++ b/assets/default/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/light/kind-actor.svg b/assets/light/kind-actor.svg
new file mode 100644
index 0000000..f917d5c
--- /dev/null
+++ b/assets/light/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/light/kind-cache.svg b/assets/light/kind-cache.svg
new file mode 100644
index 0000000..fd65755
--- /dev/null
+++ b/assets/light/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/light/kind-client.svg b/assets/light/kind-client.svg
new file mode 100644
index 0000000..7342504
--- /dev/null
+++ b/assets/light/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/light/kind-database.svg b/assets/light/kind-database.svg
new file mode 100644
index 0000000..6e63d4c
--- /dev/null
+++ b/assets/light/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/light/kind-external.svg b/assets/light/kind-external.svg
new file mode 100644
index 0000000..f3de7af
--- /dev/null
+++ b/assets/light/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/light/kind-function.svg b/assets/light/kind-function.svg
new file mode 100644
index 0000000..27dfeae
--- /dev/null
+++ b/assets/light/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/light/kind-queue.svg b/assets/light/kind-queue.svg
new file mode 100644
index 0000000..354811e
--- /dev/null
+++ b/assets/light/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/light/kind-service.svg b/assets/light/kind-service.svg
new file mode 100644
index 0000000..58debf8
--- /dev/null
+++ b/assets/light/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/light/kind-storage.svg b/assets/light/kind-storage.svg
new file mode 100644
index 0000000..0c879b8
--- /dev/null
+++ b/assets/light/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/light/kind-worker.svg b/assets/light/kind-worker.svg
new file mode 100644
index 0000000..be420e3
--- /dev/null
+++ b/assets/light/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/catalog/catalog.json b/catalog/catalog.json
index 3f883f3..b2cefc9 100644
--- a/catalog/catalog.json
+++ b/catalog/catalog.json
@@ -3,6 +3,167 @@
"schemaVersion": "1.0",
"catalogVersion": "0.1.0",
"reservedThemeIds": [],
- "fontMetrics": [],
- "themes": []
+ "fallbacks": {
+ "missingThemeId": "default",
+ "missingIconId": "kind-external"
+ },
+ "fontMetrics": [
+ {
+ "id": "stack_sans",
+ "family": "system-ui",
+ "version": "stack-metrics-1",
+ "unitsPerEm": 1000,
+ "ascent": 800,
+ "descent": -200,
+ "lineGap": 200,
+ "defaultAdvance": 600,
+ "wideAdvance": 1000,
+ "wideRanges": [
+ { "start": "U+1100", "end": "U+115F" },
+ { "start": "U+2329", "end": "U+232A" },
+ { "start": "U+2E80", "end": "U+A4CF" },
+ { "start": "U+AC00", "end": "U+D7A3" },
+ { "start": "U+F900", "end": "U+FAFF" },
+ { "start": "U+FE10", "end": "U+FE19" },
+ { "start": "U+FE30", "end": "U+FE6F" },
+ { "start": "U+FF00", "end": "U+FF60" },
+ { "start": "U+FFE0", "end": "U+FFE6" },
+ { "start": "U+1F300", "end": "U+1FAFF" },
+ { "start": "U+20000", "end": "U+3FFFD" }
+ ],
+ "glyphAdvances": {
+ "U+0020": 250,
+ "U+0021": 300,
+ "U+0022": 400,
+ "U+0027": 250,
+ "U+0028": 350,
+ "U+0029": 350,
+ "U+002C": 300,
+ "U+002D": 400,
+ "U+002E": 300,
+ "U+003A": 300,
+ "U+003B": 300,
+ "U+0049": 300,
+ "U+004D": 850,
+ "U+0057": 900,
+ "U+005B": 350,
+ "U+005D": 350,
+ "U+0066": 350,
+ "U+0069": 250,
+ "U+006A": 300,
+ "U+006C": 250,
+ "U+006D": 900,
+ "U+0072": 400,
+ "U+0074": 350,
+ "U+0077": 800
+ },
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/blob/main/docs/font-metrics.md",
+ "sourceRevision": "stack-sans-metrics-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true }
+ }
+ }
+ ],
+ "themes": [
+ {
+ "id": "default",
+ "name": "Stack Default",
+ "description": "Balanced blue-gray surfaces for general architecture diagrams.",
+ "palette": { "canvas": "#F3F6FA", "surface": "#FFFFFF", "surfaceMuted": "#E5EBF3", "text": "#142033", "textMuted": "#475569", "border": "#64748B", "accent": "#1D63D8", "danger": "#B4233A", "connector": "#475569" },
+ "typography": { "fontMetricsId": "stack_sans", "nodeLabelSizeMilliPx": 14000, "nodeDetailSizeMilliPx": 12000, "groupLabelSizeMilliPx": 13000, "edgeLabelSizeMilliPx": 11000, "lineHeightPermille": 1400, "labelWeight": 600, "detailWeight": 400 },
+ "nodeKindFallbacks": {
+ "actor": { "shape": "circle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-actor" },
+ "client": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 8000, "fallbackIconId": "kind-client" },
+ "service": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 8000, "fallbackIconId": "kind-service" },
+ "function": { "shape": "hexagon", "fill": "surfaceMuted", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-function" },
+ "worker": { "shape": "capsule", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 16000, "fallbackIconId": "kind-worker" },
+ "database": { "shape": "cylinder", "fill": "surface", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-database" },
+ "cache": { "shape": "cylinder", "fill": "surfaceMuted", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-cache" },
+ "queue": { "shape": "capsule", "fill": "surfaceMuted", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 16000, "fallbackIconId": "kind-queue" },
+ "storage": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 4000, "fallbackIconId": "kind-storage" },
+ "external": { "shape": "hexagon", "fill": "surfaceMuted", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-external" }
+ },
+ "connector": { "stroke": "connector", "text": "textMuted", "labelBackground": "canvas", "widthMilliPx": 1500, "arrowSizeMilliPx": 7000 },
+ "icons": [
+ { "id": "kind-actor", "subject": "Human actor", "asset": { "path": "assets/default/kind-actor.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-client", "subject": "Client application or device", "asset": { "path": "assets/default/kind-client.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-service", "subject": "Long-running service", "asset": { "path": "assets/default/kind-service.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-function", "subject": "On-demand function", "asset": { "path": "assets/default/kind-function.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-worker", "subject": "Background worker", "asset": { "path": "assets/default/kind-worker.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-database", "subject": "Durable database", "asset": { "path": "assets/default/kind-database.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-cache", "subject": "Disposable cache", "asset": { "path": "assets/default/kind-cache.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-queue", "subject": "Asynchronous queue or stream", "asset": { "path": "assets/default/kind-queue.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-storage", "subject": "Blob or file storage", "asset": { "path": "assets/default/kind-storage.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-external", "subject": "External system", "asset": { "path": "assets/default/kind-external.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } }
+ ]
+ },
+ {
+ "id": "light",
+ "name": "Stack Light",
+ "description": "Warm neutral surfaces for documents with light backgrounds.",
+ "palette": { "canvas": "#FAF9F6", "surface": "#FFFFFF", "surfaceMuted": "#EEEAE3", "text": "#1C1917", "textMuted": "#57534E", "border": "#78716C", "accent": "#005DBB", "danger": "#A52A1A", "connector": "#57534E" },
+ "typography": { "fontMetricsId": "stack_sans", "nodeLabelSizeMilliPx": 14000, "nodeDetailSizeMilliPx": 12000, "groupLabelSizeMilliPx": 13000, "edgeLabelSizeMilliPx": 11000, "lineHeightPermille": 1400, "labelWeight": 600, "detailWeight": 400 },
+ "nodeKindFallbacks": {
+ "actor": { "shape": "circle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-actor" },
+ "client": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 8000, "fallbackIconId": "kind-client" },
+ "service": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 8000, "fallbackIconId": "kind-service" },
+ "function": { "shape": "hexagon", "fill": "surfaceMuted", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-function" },
+ "worker": { "shape": "capsule", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 16000, "fallbackIconId": "kind-worker" },
+ "database": { "shape": "cylinder", "fill": "surface", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-database" },
+ "cache": { "shape": "cylinder", "fill": "surfaceMuted", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-cache" },
+ "queue": { "shape": "capsule", "fill": "surfaceMuted", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 16000, "fallbackIconId": "kind-queue" },
+ "storage": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 4000, "fallbackIconId": "kind-storage" },
+ "external": { "shape": "hexagon", "fill": "surfaceMuted", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-external" }
+ },
+ "connector": { "stroke": "connector", "text": "textMuted", "labelBackground": "canvas", "widthMilliPx": 1250, "arrowSizeMilliPx": 6500 },
+ "icons": [
+ { "id": "kind-actor", "subject": "Human actor", "asset": { "path": "assets/light/kind-actor.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-client", "subject": "Client application or device", "asset": { "path": "assets/light/kind-client.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-service", "subject": "Long-running service", "asset": { "path": "assets/light/kind-service.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-function", "subject": "On-demand function", "asset": { "path": "assets/light/kind-function.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-worker", "subject": "Background worker", "asset": { "path": "assets/light/kind-worker.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-database", "subject": "Durable database", "asset": { "path": "assets/light/kind-database.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-cache", "subject": "Disposable cache", "asset": { "path": "assets/light/kind-cache.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-queue", "subject": "Asynchronous queue or stream", "asset": { "path": "assets/light/kind-queue.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-storage", "subject": "Blob or file storage", "asset": { "path": "assets/light/kind-storage.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-external", "subject": "External system", "asset": { "path": "assets/light/kind-external.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } }
+ ]
+ },
+ {
+ "id": "dark",
+ "name": "Stack Dark",
+ "description": "High-contrast cool surfaces for dark canvases.",
+ "palette": { "canvas": "#0B1020", "surface": "#141B2D", "surfaceMuted": "#1E293B", "text": "#F8FAFC", "textMuted": "#CBD5E1", "border": "#94A3B8", "accent": "#69A7FF", "danger": "#FF8A9A", "connector": "#CBD5E1" },
+ "typography": { "fontMetricsId": "stack_sans", "nodeLabelSizeMilliPx": 14000, "nodeDetailSizeMilliPx": 12000, "groupLabelSizeMilliPx": 13000, "edgeLabelSizeMilliPx": 11000, "lineHeightPermille": 1400, "labelWeight": 600, "detailWeight": 400 },
+ "nodeKindFallbacks": {
+ "actor": { "shape": "circle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-actor" },
+ "client": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 8000, "fallbackIconId": "kind-client" },
+ "service": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 8000, "fallbackIconId": "kind-service" },
+ "function": { "shape": "hexagon", "fill": "surfaceMuted", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-function" },
+ "worker": { "shape": "capsule", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 16000, "fallbackIconId": "kind-worker" },
+ "database": { "shape": "cylinder", "fill": "surface", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-database" },
+ "cache": { "shape": "cylinder", "fill": "surfaceMuted", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-cache" },
+ "queue": { "shape": "capsule", "fill": "surfaceMuted", "stroke": "accent", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 16000, "fallbackIconId": "kind-queue" },
+ "storage": { "shape": "rounded-rectangle", "fill": "surface", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 4000, "fallbackIconId": "kind-storage" },
+ "external": { "shape": "hexagon", "fill": "surfaceMuted", "stroke": "border", "text": "text", "accent": "accent", "cornerRadiusMilliPx": 0, "fallbackIconId": "kind-external" }
+ },
+ "connector": { "stroke": "connector", "text": "textMuted", "labelBackground": "canvas", "widthMilliPx": 1750, "arrowSizeMilliPx": 7500 },
+ "icons": [
+ { "id": "kind-actor", "subject": "Human actor", "asset": { "path": "assets/dark/kind-actor.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-client", "subject": "Client application or device", "asset": { "path": "assets/dark/kind-client.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-service", "subject": "Long-running service", "asset": { "path": "assets/dark/kind-service.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-function", "subject": "On-demand function", "asset": { "path": "assets/dark/kind-function.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-worker", "subject": "Background worker", "asset": { "path": "assets/dark/kind-worker.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-database", "subject": "Durable database", "asset": { "path": "assets/dark/kind-database.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-cache", "subject": "Disposable cache", "asset": { "path": "assets/dark/kind-cache.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-queue", "subject": "Asynchronous queue or stream", "asset": { "path": "assets/dark/kind-queue.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-storage", "subject": "Blob or file storage", "asset": { "path": "assets/dark/kind-storage.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } },
+ { "id": "kind-external", "subject": "External system", "asset": { "path": "assets/dark/kind-external.svg", "viewBox": [0, 0, 24, 24], "provenance": { "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark", "sourceRevision": "core-fallback-v1", "copyright": "Stack contributors", "licenseSpdx": "Apache-2.0", "licenseFile": "LICENSE", "modified": false, "redistribution": { "cargo": true, "npm": true, "wasm": true, "commercialApplications": true } } } }
+ ]
+ }
+ ]
}
diff --git a/crates/stack-theme/assets/dark/kind-actor.svg b/crates/stack-theme/assets/dark/kind-actor.svg
new file mode 100644
index 0000000..c116cd3
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-cache.svg b/crates/stack-theme/assets/dark/kind-cache.svg
new file mode 100644
index 0000000..4d59b2b
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-client.svg b/crates/stack-theme/assets/dark/kind-client.svg
new file mode 100644
index 0000000..c9a57cf
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-database.svg b/crates/stack-theme/assets/dark/kind-database.svg
new file mode 100644
index 0000000..69f2b79
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-external.svg b/crates/stack-theme/assets/dark/kind-external.svg
new file mode 100644
index 0000000..56906b8
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-function.svg b/crates/stack-theme/assets/dark/kind-function.svg
new file mode 100644
index 0000000..de7aa41
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-queue.svg b/crates/stack-theme/assets/dark/kind-queue.svg
new file mode 100644
index 0000000..524df6c
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-service.svg b/crates/stack-theme/assets/dark/kind-service.svg
new file mode 100644
index 0000000..9490694
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-storage.svg b/crates/stack-theme/assets/dark/kind-storage.svg
new file mode 100644
index 0000000..674bede
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/dark/kind-worker.svg b/crates/stack-theme/assets/dark/kind-worker.svg
new file mode 100644
index 0000000..bc4d009
--- /dev/null
+++ b/crates/stack-theme/assets/dark/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/default/kind-actor.svg b/crates/stack-theme/assets/default/kind-actor.svg
new file mode 100644
index 0000000..53c45b4
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/default/kind-cache.svg b/crates/stack-theme/assets/default/kind-cache.svg
new file mode 100644
index 0000000..3b63bd2
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/default/kind-client.svg b/crates/stack-theme/assets/default/kind-client.svg
new file mode 100644
index 0000000..40d3f7b
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/default/kind-database.svg b/crates/stack-theme/assets/default/kind-database.svg
new file mode 100644
index 0000000..5637cd5
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/default/kind-external.svg b/crates/stack-theme/assets/default/kind-external.svg
new file mode 100644
index 0000000..5db6001
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/default/kind-function.svg b/crates/stack-theme/assets/default/kind-function.svg
new file mode 100644
index 0000000..7b377b8
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/default/kind-queue.svg b/crates/stack-theme/assets/default/kind-queue.svg
new file mode 100644
index 0000000..0d5cdf7
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/crates/stack-theme/assets/default/kind-service.svg b/crates/stack-theme/assets/default/kind-service.svg
new file mode 100644
index 0000000..6317048
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/crates/stack-theme/assets/default/kind-storage.svg b/crates/stack-theme/assets/default/kind-storage.svg
new file mode 100644
index 0000000..fb0cff6
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/default/kind-worker.svg b/crates/stack-theme/assets/default/kind-worker.svg
new file mode 100644
index 0000000..a8d5004
--- /dev/null
+++ b/crates/stack-theme/assets/default/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/light/kind-actor.svg b/crates/stack-theme/assets/light/kind-actor.svg
new file mode 100644
index 0000000..f917d5c
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/light/kind-cache.svg b/crates/stack-theme/assets/light/kind-cache.svg
new file mode 100644
index 0000000..fd65755
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/light/kind-client.svg b/crates/stack-theme/assets/light/kind-client.svg
new file mode 100644
index 0000000..7342504
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/light/kind-database.svg b/crates/stack-theme/assets/light/kind-database.svg
new file mode 100644
index 0000000..6e63d4c
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/light/kind-external.svg b/crates/stack-theme/assets/light/kind-external.svg
new file mode 100644
index 0000000..f3de7af
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/light/kind-function.svg b/crates/stack-theme/assets/light/kind-function.svg
new file mode 100644
index 0000000..27dfeae
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/stack-theme/assets/light/kind-queue.svg b/crates/stack-theme/assets/light/kind-queue.svg
new file mode 100644
index 0000000..354811e
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/crates/stack-theme/assets/light/kind-service.svg b/crates/stack-theme/assets/light/kind-service.svg
new file mode 100644
index 0000000..58debf8
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/crates/stack-theme/assets/light/kind-storage.svg b/crates/stack-theme/assets/light/kind-storage.svg
new file mode 100644
index 0000000..0c879b8
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/assets/light/kind-worker.svg b/crates/stack-theme/assets/light/kind-worker.svg
new file mode 100644
index 0000000..be420e3
--- /dev/null
+++ b/crates/stack-theme/assets/light/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/stack-theme/schema/catalog.schema.json b/crates/stack-theme/schema/catalog.schema.json
index f9f09a4..736b83f 100644
--- a/crates/stack-theme/schema/catalog.schema.json
+++ b/crates/stack-theme/schema/catalog.schema.json
@@ -10,6 +10,7 @@
"schemaVersion",
"catalogVersion",
"reservedThemeIds",
+ "fallbacks",
"fontMetrics",
"themes"
],
@@ -30,6 +31,9 @@
},
"uniqueItems": true
},
+ "fallbacks": {
+ "$ref": "#/$defs/catalogFallbacks"
+ },
"fontMetrics": {
"type": "array",
"items": {
@@ -56,6 +60,15 @@
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
},
+ "catalogFallbacks": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["missingThemeId", "missingIconId"],
+ "properties": {
+ "missingThemeId": { "$ref": "#/$defs/identifier" },
+ "missingIconId": { "$ref": "#/$defs/iconIdentifier" }
+ }
+ },
"relativePath": {
"type": "string",
"minLength": 1,
@@ -380,6 +393,8 @@
"descent",
"lineGap",
"defaultAdvance",
+ "wideAdvance",
+ "wideRanges",
"glyphAdvances",
"provenance"
],
@@ -420,9 +435,33 @@
"minimum": 1,
"maximum": 32767
},
+ "wideAdvance": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 32767
+ },
+ "wideRanges": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/unicodeRange" }
+ },
"glyphAdvances": { "$ref": "#/$defs/glyphAdvances" },
"provenance": { "$ref": "#/$defs/provenance" }
}
+ },
+ "unicodeRange": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["start", "end"],
+ "properties": {
+ "start": {
+ "type": "string",
+ "pattern": "^U\\+[0-9A-F]{4,6}$"
+ },
+ "end": {
+ "type": "string",
+ "pattern": "^U\\+[0-9A-F]{4,6}$"
+ }
+ }
}
}
}
diff --git a/crates/stack-theme/src/generated/catalog.json b/crates/stack-theme/src/generated/catalog.json
index 3f883f3..778982b 100644
--- a/crates/stack-theme/src/generated/catalog.json
+++ b/crates/stack-theme/src/generated/catalog.json
@@ -3,6 +3,1300 @@
"schemaVersion": "1.0",
"catalogVersion": "0.1.0",
"reservedThemeIds": [],
- "fontMetrics": [],
- "themes": []
+ "fallbacks": {
+ "missingThemeId": "default",
+ "missingIconId": "kind-external"
+ },
+ "fontMetrics": [
+ {
+ "id": "stack_sans",
+ "family": "system-ui",
+ "version": "stack-metrics-1",
+ "unitsPerEm": 1000,
+ "ascent": 800,
+ "descent": -200,
+ "lineGap": 200,
+ "defaultAdvance": 600,
+ "wideAdvance": 1000,
+ "wideRanges": [
+ {
+ "start": "U+1100",
+ "end": "U+115F"
+ },
+ {
+ "start": "U+2329",
+ "end": "U+232A"
+ },
+ {
+ "start": "U+2E80",
+ "end": "U+A4CF"
+ },
+ {
+ "start": "U+AC00",
+ "end": "U+D7A3"
+ },
+ {
+ "start": "U+F900",
+ "end": "U+FAFF"
+ },
+ {
+ "start": "U+FE10",
+ "end": "U+FE19"
+ },
+ {
+ "start": "U+FE30",
+ "end": "U+FE6F"
+ },
+ {
+ "start": "U+FF00",
+ "end": "U+FF60"
+ },
+ {
+ "start": "U+FFE0",
+ "end": "U+FFE6"
+ },
+ {
+ "start": "U+1F300",
+ "end": "U+1FAFF"
+ },
+ {
+ "start": "U+20000",
+ "end": "U+3FFFD"
+ }
+ ],
+ "glyphAdvances": {
+ "U+0020": 250,
+ "U+0021": 300,
+ "U+0022": 400,
+ "U+0027": 250,
+ "U+0028": 350,
+ "U+0029": 350,
+ "U+002C": 300,
+ "U+002D": 400,
+ "U+002E": 300,
+ "U+003A": 300,
+ "U+003B": 300,
+ "U+0049": 300,
+ "U+004D": 850,
+ "U+0057": 900,
+ "U+005B": 350,
+ "U+005D": 350,
+ "U+0066": 350,
+ "U+0069": 250,
+ "U+006A": 300,
+ "U+006C": 250,
+ "U+006D": 900,
+ "U+0072": 400,
+ "U+0074": 350,
+ "U+0077": 800
+ },
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/blob/main/docs/font-metrics.md",
+ "sourceRevision": "stack-sans-metrics-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ ],
+ "themes": [
+ {
+ "id": "default",
+ "name": "Stack Default",
+ "description": "Balanced blue-gray surfaces for general architecture diagrams.",
+ "palette": {
+ "canvas": "#F3F6FA",
+ "surface": "#FFFFFF",
+ "surfaceMuted": "#E5EBF3",
+ "text": "#142033",
+ "textMuted": "#475569",
+ "border": "#64748B",
+ "accent": "#1D63D8",
+ "danger": "#B4233A",
+ "connector": "#475569"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1500,
+ "arrowSizeMilliPx": 7000
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/default/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/default/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/default/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/default/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/default/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/default/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/default/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/default/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/default/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/default/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "id": "light",
+ "name": "Stack Light",
+ "description": "Warm neutral surfaces for documents with light backgrounds.",
+ "palette": {
+ "canvas": "#FAF9F6",
+ "surface": "#FFFFFF",
+ "surfaceMuted": "#EEEAE3",
+ "text": "#1C1917",
+ "textMuted": "#57534E",
+ "border": "#78716C",
+ "accent": "#005DBB",
+ "danger": "#A52A1A",
+ "connector": "#57534E"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1250,
+ "arrowSizeMilliPx": 6500
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/light/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/light/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/light/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/light/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/light/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/light/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/light/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/light/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/light/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/light/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "id": "dark",
+ "name": "Stack Dark",
+ "description": "High-contrast cool surfaces for dark canvases.",
+ "palette": {
+ "canvas": "#0B1020",
+ "surface": "#141B2D",
+ "surfaceMuted": "#1E293B",
+ "text": "#F8FAFC",
+ "textMuted": "#CBD5E1",
+ "border": "#94A3B8",
+ "accent": "#69A7FF",
+ "danger": "#FF8A9A",
+ "connector": "#CBD5E1"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1750,
+ "arrowSizeMilliPx": 7500
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/dark/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/dark/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/dark/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/dark/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/dark/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/dark/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/dark/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/dark/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/dark/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/dark/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
}
diff --git a/crates/stack-theme/src/generated/metadata.rs b/crates/stack-theme/src/generated/metadata.rs
index 13cd487..3b43315 100644
--- a/crates/stack-theme/src/generated/metadata.rs
+++ b/crates/stack-theme/src/generated/metadata.rs
@@ -1,7 +1,39 @@
// Generated by scripts/generate.mjs. Do not edit.
pub const CATALOG_VERSION: &str = "0.1.0";
-pub const CATALOG_REVISION: &str = "sha256:4615e4947392933d443da7142f2c54e95bfb13330f3924de7432c297111978e8";
+pub const CATALOG_REVISION: &str = "sha256:b54ca3435931675d36d6a69b5154a0bd6ce2cbae1cfe14a20a095a8e24f8bae4";
-pub fn icon_svg(_asset_path: &str) -> Option<&'static str> {
- None
+pub fn icon_svg(asset_path: &str) -> Option<&'static str> {
+ match asset_path {
+ "assets/dark/kind-actor.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-actor.svg"))),
+ "assets/dark/kind-cache.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-cache.svg"))),
+ "assets/dark/kind-client.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-client.svg"))),
+ "assets/dark/kind-database.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-database.svg"))),
+ "assets/dark/kind-external.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-external.svg"))),
+ "assets/dark/kind-function.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-function.svg"))),
+ "assets/dark/kind-queue.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-queue.svg"))),
+ "assets/dark/kind-service.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-service.svg"))),
+ "assets/dark/kind-storage.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-storage.svg"))),
+ "assets/dark/kind-worker.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/dark/kind-worker.svg"))),
+ "assets/default/kind-actor.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-actor.svg"))),
+ "assets/default/kind-cache.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-cache.svg"))),
+ "assets/default/kind-client.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-client.svg"))),
+ "assets/default/kind-database.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-database.svg"))),
+ "assets/default/kind-external.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-external.svg"))),
+ "assets/default/kind-function.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-function.svg"))),
+ "assets/default/kind-queue.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-queue.svg"))),
+ "assets/default/kind-service.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-service.svg"))),
+ "assets/default/kind-storage.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-storage.svg"))),
+ "assets/default/kind-worker.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/default/kind-worker.svg"))),
+ "assets/light/kind-actor.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-actor.svg"))),
+ "assets/light/kind-cache.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-cache.svg"))),
+ "assets/light/kind-client.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-client.svg"))),
+ "assets/light/kind-database.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-database.svg"))),
+ "assets/light/kind-external.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-external.svg"))),
+ "assets/light/kind-function.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-function.svg"))),
+ "assets/light/kind-queue.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-queue.svg"))),
+ "assets/light/kind-service.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-service.svg"))),
+ "assets/light/kind-storage.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-storage.svg"))),
+ "assets/light/kind-worker.svg" => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/light/kind-worker.svg"))),
+ _ => None,
+ }
}
diff --git a/crates/stack-theme/src/lib.rs b/crates/stack-theme/src/lib.rs
index 944fab8..98aa726 100644
--- a/crates/stack-theme/src/lib.rs
+++ b/crates/stack-theme/src/lib.rs
@@ -60,12 +60,24 @@ pub struct Catalog {
pub catalog_version: String,
/// Theme identifiers that cannot be registered again.
pub reserved_theme_ids: Vec,
+ /// Deterministic recovery choices for unavailable themes and icons.
+ pub fallbacks: CatalogFallbacks,
/// Deterministic, versioned font measurement tables.
pub font_metrics: Vec,
/// Theme records in canonical catalog order.
pub themes: Vec,
}
+/// Catalog-wide recovery choices used after emitting a missing-resource diagnostic.
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CatalogFallbacks {
+ /// Core theme selected when a requested non-core theme is unavailable.
+ pub missing_theme_id: String,
+ /// Logical icon selected when an icon is unavailable in the resolved theme.
+ pub missing_icon_id: String,
+}
+
/// One deterministic font measurement table.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
@@ -86,12 +98,24 @@ pub struct FontMetrics {
pub line_gap: u32,
/// Advance used for a scalar absent from `glyph_advances`.
pub default_advance: u32,
+ /// Advance used for a scalar covered by `wide_ranges`.
+ pub wide_advance: u32,
+ /// Ordered, non-overlapping Unicode scalar ranges treated as wide.
+ pub wide_ranges: Vec,
/// Unicode scalar advances keyed as uppercase `U+XXXX` values.
pub glyph_advances: BTreeMap,
/// Source, license, and distribution evidence for the metrics.
pub provenance: Provenance,
}
+/// An inclusive Unicode scalar range encoded as `U+XXXX` labels.
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UnicodeRange {
+ pub start: String,
+ pub end: String,
+}
+
/// One theme and its theme-local icon collection.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
@@ -101,6 +125,7 @@ pub struct Theme {
/// Human-readable theme name.
pub name: String,
/// Optional contributor-facing description.
+ #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option,
/// Named colors available to other theme records.
pub palette: Palette,
@@ -208,6 +233,7 @@ pub struct ConnectorStyle {
pub label_background: PaletteToken,
pub width_milli_px: u32,
pub arrow_size_milli_px: u32,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub dash_milli_px: Option>,
}
@@ -217,6 +243,7 @@ pub struct ConnectorStyle {
pub struct Icon {
pub id: String,
pub subject: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option,
pub asset: IconAsset,
}
@@ -266,6 +293,21 @@ mod tests {
assert!(CATALOG_REVISION.starts_with("sha256:"));
assert_eq!(CATALOG_REVISION.len(), 71);
assert_eq!(icon_svg("assets/missing.svg"), None);
+ assert_eq!(
+ catalog
+ .themes
+ .iter()
+ .map(|theme| theme.id.as_str())
+ .collect::>(),
+ ["default", "light", "dark"]
+ );
+ assert!(
+ catalog
+ .themes
+ .iter()
+ .flat_map(|theme| &theme.icons)
+ .all(|icon| icon_svg(&icon.asset.path).is_some())
+ );
}
#[test]
diff --git a/docs/core-theme-review.md b/docs/core-theme-review.md
new file mode 100644
index 0000000..3351b05
--- /dev/null
+++ b/docs/core-theme-review.md
@@ -0,0 +1,30 @@
+# Core theme review
+
+Review date: 2026-09-03
+
+Catalog version: `0.1.0`
+
+Catalog revision: `sha256:b54ca3435931675d36d6a69b5154a0bd6ce2cbae1cfe14a20a095a8e24f8bae4`
+
+## Automated evidence
+
+- The catalog validator accepts `default`, `light`, and `dark` with all ten Stack 1.0 node-kind fallbacks and explicit recovery through `default` / `kind-external` for missing non-core themes or icons.
+- Each theme uses ten distinct logical icon identifiers and at least four fallback shapes, so kinds remain distinguishable without color.
+- Text and danger colors meet a `4.5:1` contrast floor against the surface. Borders, accents, and connectors meet a `3:1` non-text contrast floor.
+- The deterministic `stack_sans` metrics cover explicit narrow glyphs, default advances, pinned wide Unicode ranges, CJK, and emoji without host font measurement.
+- Cargo and npm artifacts expose identical catalog, schema, assets, version, and content revision data.
+- SVG validation rejects scripts, event handlers, external resources, and executable URL values.
+
+## Visual evidence
+
+The dependency-free review page was served from the repository root and checked in headless Chromium at `1440 x 1000` and `390 x 844` CSS pixels.
+
+- All three theme sections, 30 node fallbacks, and 30 embedded SVG icons rendered.
+- Every node kind retained a recognizable subject and a non-color shape cue in each theme.
+- The Japanese and emoji typography sample rendered within every theme card.
+- The narrow viewport had no horizontal overflow after wrapping the content revision metadata.
+- The browser console and page-error log were empty at both viewports.
+
+## License evidence
+
+All runtime SVG assets and the deterministic metrics table are repository-authored Apache-2.0 work. No third-party runtime font, icon, or image is bundled. Development dependencies remain listed in [`THIRD_PARTY_LICENSES.md`](../THIRD_PARTY_LICENSES.md).
diff --git a/docs/font-metrics.md b/docs/font-metrics.md
new file mode 100644
index 0000000..33d3c14
--- /dev/null
+++ b/docs/font-metrics.md
@@ -0,0 +1,15 @@
+# Stack Sans Deterministic Metrics
+
+`stack_sans` version `stack-metrics-1` is a repository-authored layout table for the generic CSS `system-ui` rendering preference. It is not extracted from, and does not redistribute, a font file.
+
+The table exists to make layout decisions independent from the font installed on a native or browser host. Consumers choose an advance in this order:
+
+1. an exact uppercase `U+XXXX` entry in `glyphAdvances`;
+2. `wideAdvance` when the scalar is in one of the inclusive, ordered `wideRanges`;
+3. `defaultAdvance` for every other scalar.
+
+The first version gives narrow Latin punctuation explicit advances, uses a 600-unit default for ordinary alphabetic text, and uses a 1000-unit advance for Hangul, CJK, full-width forms, emoji, and supplementary ideographs. It uses 1000 units per em, an 800-unit ascent, a -200-unit descent, and a 200-unit line gap.
+
+These values are intentionally conservative layout inputs rather than claims about the exact glyph geometry of every platform `system-ui` font. Renderers use them for bounds and wrapping and must not call a host font measurement API. A future metrics adjustment changes the metrics version and catalog revision.
+
+The table is repository-authored work licensed under Apache-2.0. It requires no third-party font attribution or redistribution permission.
diff --git a/packages/theme/assets/dark/kind-actor.svg b/packages/theme/assets/dark/kind-actor.svg
new file mode 100644
index 0000000..c116cd3
--- /dev/null
+++ b/packages/theme/assets/dark/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/dark/kind-cache.svg b/packages/theme/assets/dark/kind-cache.svg
new file mode 100644
index 0000000..4d59b2b
--- /dev/null
+++ b/packages/theme/assets/dark/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/dark/kind-client.svg b/packages/theme/assets/dark/kind-client.svg
new file mode 100644
index 0000000..c9a57cf
--- /dev/null
+++ b/packages/theme/assets/dark/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/dark/kind-database.svg b/packages/theme/assets/dark/kind-database.svg
new file mode 100644
index 0000000..69f2b79
--- /dev/null
+++ b/packages/theme/assets/dark/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/dark/kind-external.svg b/packages/theme/assets/dark/kind-external.svg
new file mode 100644
index 0000000..56906b8
--- /dev/null
+++ b/packages/theme/assets/dark/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/dark/kind-function.svg b/packages/theme/assets/dark/kind-function.svg
new file mode 100644
index 0000000..de7aa41
--- /dev/null
+++ b/packages/theme/assets/dark/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/dark/kind-queue.svg b/packages/theme/assets/dark/kind-queue.svg
new file mode 100644
index 0000000..524df6c
--- /dev/null
+++ b/packages/theme/assets/dark/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/theme/assets/dark/kind-service.svg b/packages/theme/assets/dark/kind-service.svg
new file mode 100644
index 0000000..9490694
--- /dev/null
+++ b/packages/theme/assets/dark/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/theme/assets/dark/kind-storage.svg b/packages/theme/assets/dark/kind-storage.svg
new file mode 100644
index 0000000..674bede
--- /dev/null
+++ b/packages/theme/assets/dark/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/dark/kind-worker.svg b/packages/theme/assets/dark/kind-worker.svg
new file mode 100644
index 0000000..bc4d009
--- /dev/null
+++ b/packages/theme/assets/dark/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/default/kind-actor.svg b/packages/theme/assets/default/kind-actor.svg
new file mode 100644
index 0000000..53c45b4
--- /dev/null
+++ b/packages/theme/assets/default/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/default/kind-cache.svg b/packages/theme/assets/default/kind-cache.svg
new file mode 100644
index 0000000..3b63bd2
--- /dev/null
+++ b/packages/theme/assets/default/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/default/kind-client.svg b/packages/theme/assets/default/kind-client.svg
new file mode 100644
index 0000000..40d3f7b
--- /dev/null
+++ b/packages/theme/assets/default/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/default/kind-database.svg b/packages/theme/assets/default/kind-database.svg
new file mode 100644
index 0000000..5637cd5
--- /dev/null
+++ b/packages/theme/assets/default/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/default/kind-external.svg b/packages/theme/assets/default/kind-external.svg
new file mode 100644
index 0000000..5db6001
--- /dev/null
+++ b/packages/theme/assets/default/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/default/kind-function.svg b/packages/theme/assets/default/kind-function.svg
new file mode 100644
index 0000000..7b377b8
--- /dev/null
+++ b/packages/theme/assets/default/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/default/kind-queue.svg b/packages/theme/assets/default/kind-queue.svg
new file mode 100644
index 0000000..0d5cdf7
--- /dev/null
+++ b/packages/theme/assets/default/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/theme/assets/default/kind-service.svg b/packages/theme/assets/default/kind-service.svg
new file mode 100644
index 0000000..6317048
--- /dev/null
+++ b/packages/theme/assets/default/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/theme/assets/default/kind-storage.svg b/packages/theme/assets/default/kind-storage.svg
new file mode 100644
index 0000000..fb0cff6
--- /dev/null
+++ b/packages/theme/assets/default/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/default/kind-worker.svg b/packages/theme/assets/default/kind-worker.svg
new file mode 100644
index 0000000..a8d5004
--- /dev/null
+++ b/packages/theme/assets/default/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/light/kind-actor.svg b/packages/theme/assets/light/kind-actor.svg
new file mode 100644
index 0000000..f917d5c
--- /dev/null
+++ b/packages/theme/assets/light/kind-actor.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/light/kind-cache.svg b/packages/theme/assets/light/kind-cache.svg
new file mode 100644
index 0000000..fd65755
--- /dev/null
+++ b/packages/theme/assets/light/kind-cache.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/light/kind-client.svg b/packages/theme/assets/light/kind-client.svg
new file mode 100644
index 0000000..7342504
--- /dev/null
+++ b/packages/theme/assets/light/kind-client.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/light/kind-database.svg b/packages/theme/assets/light/kind-database.svg
new file mode 100644
index 0000000..6e63d4c
--- /dev/null
+++ b/packages/theme/assets/light/kind-database.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/light/kind-external.svg b/packages/theme/assets/light/kind-external.svg
new file mode 100644
index 0000000..f3de7af
--- /dev/null
+++ b/packages/theme/assets/light/kind-external.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/light/kind-function.svg b/packages/theme/assets/light/kind-function.svg
new file mode 100644
index 0000000..27dfeae
--- /dev/null
+++ b/packages/theme/assets/light/kind-function.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/theme/assets/light/kind-queue.svg b/packages/theme/assets/light/kind-queue.svg
new file mode 100644
index 0000000..354811e
--- /dev/null
+++ b/packages/theme/assets/light/kind-queue.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/theme/assets/light/kind-service.svg b/packages/theme/assets/light/kind-service.svg
new file mode 100644
index 0000000..58debf8
--- /dev/null
+++ b/packages/theme/assets/light/kind-service.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/theme/assets/light/kind-storage.svg b/packages/theme/assets/light/kind-storage.svg
new file mode 100644
index 0000000..0c879b8
--- /dev/null
+++ b/packages/theme/assets/light/kind-storage.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/assets/light/kind-worker.svg b/packages/theme/assets/light/kind-worker.svg
new file mode 100644
index 0000000..be420e3
--- /dev/null
+++ b/packages/theme/assets/light/kind-worker.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/theme/catalog-metadata.json b/packages/theme/catalog-metadata.json
index e61f0f3..e34c6c1 100644
--- a/packages/theme/catalog-metadata.json
+++ b/packages/theme/catalog-metadata.json
@@ -1,4 +1,4 @@
{
"catalogVersion": "0.1.0",
- "catalogRevision": "sha256:4615e4947392933d443da7142f2c54e95bfb13330f3924de7432c297111978e8"
+ "catalogRevision": "sha256:b54ca3435931675d36d6a69b5154a0bd6ce2cbae1cfe14a20a095a8e24f8bae4"
}
diff --git a/packages/theme/catalog.generated.js b/packages/theme/catalog.generated.js
index 8aabf54..f825f9f 100644
--- a/packages/theme/catalog.generated.js
+++ b/packages/theme/catalog.generated.js
@@ -4,10 +4,1335 @@ const catalogData = {
"schemaVersion": "1.0",
"catalogVersion": "0.1.0",
"reservedThemeIds": [],
- "fontMetrics": [],
- "themes": []
+ "fallbacks": {
+ "missingThemeId": "default",
+ "missingIconId": "kind-external"
+ },
+ "fontMetrics": [
+ {
+ "id": "stack_sans",
+ "family": "system-ui",
+ "version": "stack-metrics-1",
+ "unitsPerEm": 1000,
+ "ascent": 800,
+ "descent": -200,
+ "lineGap": 200,
+ "defaultAdvance": 600,
+ "wideAdvance": 1000,
+ "wideRanges": [
+ {
+ "start": "U+1100",
+ "end": "U+115F"
+ },
+ {
+ "start": "U+2329",
+ "end": "U+232A"
+ },
+ {
+ "start": "U+2E80",
+ "end": "U+A4CF"
+ },
+ {
+ "start": "U+AC00",
+ "end": "U+D7A3"
+ },
+ {
+ "start": "U+F900",
+ "end": "U+FAFF"
+ },
+ {
+ "start": "U+FE10",
+ "end": "U+FE19"
+ },
+ {
+ "start": "U+FE30",
+ "end": "U+FE6F"
+ },
+ {
+ "start": "U+FF00",
+ "end": "U+FF60"
+ },
+ {
+ "start": "U+FFE0",
+ "end": "U+FFE6"
+ },
+ {
+ "start": "U+1F300",
+ "end": "U+1FAFF"
+ },
+ {
+ "start": "U+20000",
+ "end": "U+3FFFD"
+ }
+ ],
+ "glyphAdvances": {
+ "U+0020": 250,
+ "U+0021": 300,
+ "U+0022": 400,
+ "U+0027": 250,
+ "U+0028": 350,
+ "U+0029": 350,
+ "U+002C": 300,
+ "U+002D": 400,
+ "U+002E": 300,
+ "U+003A": 300,
+ "U+003B": 300,
+ "U+0049": 300,
+ "U+004D": 850,
+ "U+0057": 900,
+ "U+005B": 350,
+ "U+005D": 350,
+ "U+0066": 350,
+ "U+0069": 250,
+ "U+006A": 300,
+ "U+006C": 250,
+ "U+006D": 900,
+ "U+0072": 400,
+ "U+0074": 350,
+ "U+0077": 800
+ },
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/blob/main/docs/font-metrics.md",
+ "sourceRevision": "stack-sans-metrics-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ ],
+ "themes": [
+ {
+ "id": "default",
+ "name": "Stack Default",
+ "description": "Balanced blue-gray surfaces for general architecture diagrams.",
+ "palette": {
+ "canvas": "#F3F6FA",
+ "surface": "#FFFFFF",
+ "surfaceMuted": "#E5EBF3",
+ "text": "#142033",
+ "textMuted": "#475569",
+ "border": "#64748B",
+ "accent": "#1D63D8",
+ "danger": "#B4233A",
+ "connector": "#475569"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1500,
+ "arrowSizeMilliPx": 7000
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/default/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/default/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/default/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/default/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/default/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/default/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/default/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/default/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/default/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/default/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "id": "light",
+ "name": "Stack Light",
+ "description": "Warm neutral surfaces for documents with light backgrounds.",
+ "palette": {
+ "canvas": "#FAF9F6",
+ "surface": "#FFFFFF",
+ "surfaceMuted": "#EEEAE3",
+ "text": "#1C1917",
+ "textMuted": "#57534E",
+ "border": "#78716C",
+ "accent": "#005DBB",
+ "danger": "#A52A1A",
+ "connector": "#57534E"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1250,
+ "arrowSizeMilliPx": 6500
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/light/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/light/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/light/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/light/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/light/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/light/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/light/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/light/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/light/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/light/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "id": "dark",
+ "name": "Stack Dark",
+ "description": "High-contrast cool surfaces for dark canvases.",
+ "palette": {
+ "canvas": "#0B1020",
+ "surface": "#141B2D",
+ "surfaceMuted": "#1E293B",
+ "text": "#F8FAFC",
+ "textMuted": "#CBD5E1",
+ "border": "#94A3B8",
+ "accent": "#69A7FF",
+ "danger": "#FF8A9A",
+ "connector": "#CBD5E1"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1750,
+ "arrowSizeMilliPx": 7500
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/dark/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/dark/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/dark/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/dark/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/dark/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/dark/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/dark/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/dark/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/dark/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/dark/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+};
+const iconAssetsData = {
+ "assets/dark/kind-actor.svg": "\n",
+ "assets/dark/kind-cache.svg": "\n",
+ "assets/dark/kind-client.svg": "\n",
+ "assets/dark/kind-database.svg": "\n",
+ "assets/dark/kind-external.svg": "\n",
+ "assets/dark/kind-function.svg": "\n",
+ "assets/dark/kind-queue.svg": "\n",
+ "assets/dark/kind-service.svg": "\n",
+ "assets/dark/kind-storage.svg": "\n",
+ "assets/dark/kind-worker.svg": "\n",
+ "assets/default/kind-actor.svg": "\n",
+ "assets/default/kind-cache.svg": "\n",
+ "assets/default/kind-client.svg": "\n",
+ "assets/default/kind-database.svg": "\n",
+ "assets/default/kind-external.svg": "\n",
+ "assets/default/kind-function.svg": "\n",
+ "assets/default/kind-queue.svg": "\n",
+ "assets/default/kind-service.svg": "\n",
+ "assets/default/kind-storage.svg": "\n",
+ "assets/default/kind-worker.svg": "\n",
+ "assets/light/kind-actor.svg": "\n",
+ "assets/light/kind-cache.svg": "\n",
+ "assets/light/kind-client.svg": "\n",
+ "assets/light/kind-database.svg": "\n",
+ "assets/light/kind-external.svg": "\n",
+ "assets/light/kind-function.svg": "\n",
+ "assets/light/kind-queue.svg": "\n",
+ "assets/light/kind-service.svg": "\n",
+ "assets/light/kind-storage.svg": "\n",
+ "assets/light/kind-worker.svg": "\n"
};
-const iconAssetsData = {};
function deepFreeze(value) {
Object.freeze(value);
@@ -21,7 +1346,7 @@ function deepFreeze(value) {
export const catalog = deepFreeze(catalogData);
export const catalogVersion = "0.1.0";
-export const catalogRevision = "sha256:4615e4947392933d443da7142f2c54e95bfb13330f3924de7432c297111978e8";
+export const catalogRevision = "sha256:b54ca3435931675d36d6a69b5154a0bd6ce2cbae1cfe14a20a095a8e24f8bae4";
export const iconAssets = deepFreeze(iconAssetsData);
export function iconSvg(assetPath) {
return iconAssets[assetPath];
diff --git a/packages/theme/catalog.json b/packages/theme/catalog.json
index 3f883f3..778982b 100644
--- a/packages/theme/catalog.json
+++ b/packages/theme/catalog.json
@@ -3,6 +3,1300 @@
"schemaVersion": "1.0",
"catalogVersion": "0.1.0",
"reservedThemeIds": [],
- "fontMetrics": [],
- "themes": []
+ "fallbacks": {
+ "missingThemeId": "default",
+ "missingIconId": "kind-external"
+ },
+ "fontMetrics": [
+ {
+ "id": "stack_sans",
+ "family": "system-ui",
+ "version": "stack-metrics-1",
+ "unitsPerEm": 1000,
+ "ascent": 800,
+ "descent": -200,
+ "lineGap": 200,
+ "defaultAdvance": 600,
+ "wideAdvance": 1000,
+ "wideRanges": [
+ {
+ "start": "U+1100",
+ "end": "U+115F"
+ },
+ {
+ "start": "U+2329",
+ "end": "U+232A"
+ },
+ {
+ "start": "U+2E80",
+ "end": "U+A4CF"
+ },
+ {
+ "start": "U+AC00",
+ "end": "U+D7A3"
+ },
+ {
+ "start": "U+F900",
+ "end": "U+FAFF"
+ },
+ {
+ "start": "U+FE10",
+ "end": "U+FE19"
+ },
+ {
+ "start": "U+FE30",
+ "end": "U+FE6F"
+ },
+ {
+ "start": "U+FF00",
+ "end": "U+FF60"
+ },
+ {
+ "start": "U+FFE0",
+ "end": "U+FFE6"
+ },
+ {
+ "start": "U+1F300",
+ "end": "U+1FAFF"
+ },
+ {
+ "start": "U+20000",
+ "end": "U+3FFFD"
+ }
+ ],
+ "glyphAdvances": {
+ "U+0020": 250,
+ "U+0021": 300,
+ "U+0022": 400,
+ "U+0027": 250,
+ "U+0028": 350,
+ "U+0029": 350,
+ "U+002C": 300,
+ "U+002D": 400,
+ "U+002E": 300,
+ "U+003A": 300,
+ "U+003B": 300,
+ "U+0049": 300,
+ "U+004D": 850,
+ "U+0057": 900,
+ "U+005B": 350,
+ "U+005D": 350,
+ "U+0066": 350,
+ "U+0069": 250,
+ "U+006A": 300,
+ "U+006C": 250,
+ "U+006D": 900,
+ "U+0072": 400,
+ "U+0074": 350,
+ "U+0077": 800
+ },
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/blob/main/docs/font-metrics.md",
+ "sourceRevision": "stack-sans-metrics-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ ],
+ "themes": [
+ {
+ "id": "default",
+ "name": "Stack Default",
+ "description": "Balanced blue-gray surfaces for general architecture diagrams.",
+ "palette": {
+ "canvas": "#F3F6FA",
+ "surface": "#FFFFFF",
+ "surfaceMuted": "#E5EBF3",
+ "text": "#142033",
+ "textMuted": "#475569",
+ "border": "#64748B",
+ "accent": "#1D63D8",
+ "danger": "#B4233A",
+ "connector": "#475569"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1500,
+ "arrowSizeMilliPx": 7000
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/default/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/default/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/default/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/default/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/default/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/default/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/default/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/default/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/default/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/default/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/default",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "id": "light",
+ "name": "Stack Light",
+ "description": "Warm neutral surfaces for documents with light backgrounds.",
+ "palette": {
+ "canvas": "#FAF9F6",
+ "surface": "#FFFFFF",
+ "surfaceMuted": "#EEEAE3",
+ "text": "#1C1917",
+ "textMuted": "#57534E",
+ "border": "#78716C",
+ "accent": "#005DBB",
+ "danger": "#A52A1A",
+ "connector": "#57534E"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1250,
+ "arrowSizeMilliPx": 6500
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/light/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/light/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/light/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/light/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/light/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/light/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/light/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/light/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/light/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/light/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/light",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "id": "dark",
+ "name": "Stack Dark",
+ "description": "High-contrast cool surfaces for dark canvases.",
+ "palette": {
+ "canvas": "#0B1020",
+ "surface": "#141B2D",
+ "surfaceMuted": "#1E293B",
+ "text": "#F8FAFC",
+ "textMuted": "#CBD5E1",
+ "border": "#94A3B8",
+ "accent": "#69A7FF",
+ "danger": "#FF8A9A",
+ "connector": "#CBD5E1"
+ },
+ "typography": {
+ "fontMetricsId": "stack_sans",
+ "nodeLabelSizeMilliPx": 14000,
+ "nodeDetailSizeMilliPx": 12000,
+ "groupLabelSizeMilliPx": 13000,
+ "edgeLabelSizeMilliPx": 11000,
+ "lineHeightPermille": 1400,
+ "labelWeight": 600,
+ "detailWeight": 400
+ },
+ "nodeKindFallbacks": {
+ "actor": {
+ "shape": "circle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-actor"
+ },
+ "client": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-client"
+ },
+ "service": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 8000,
+ "fallbackIconId": "kind-service"
+ },
+ "function": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-function"
+ },
+ "worker": {
+ "shape": "capsule",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-worker"
+ },
+ "database": {
+ "shape": "cylinder",
+ "fill": "surface",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-database"
+ },
+ "cache": {
+ "shape": "cylinder",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-cache"
+ },
+ "queue": {
+ "shape": "capsule",
+ "fill": "surfaceMuted",
+ "stroke": "accent",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 16000,
+ "fallbackIconId": "kind-queue"
+ },
+ "storage": {
+ "shape": "rounded-rectangle",
+ "fill": "surface",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 4000,
+ "fallbackIconId": "kind-storage"
+ },
+ "external": {
+ "shape": "hexagon",
+ "fill": "surfaceMuted",
+ "stroke": "border",
+ "text": "text",
+ "accent": "accent",
+ "cornerRadiusMilliPx": 0,
+ "fallbackIconId": "kind-external"
+ }
+ },
+ "connector": {
+ "stroke": "connector",
+ "text": "textMuted",
+ "labelBackground": "canvas",
+ "widthMilliPx": 1750,
+ "arrowSizeMilliPx": 7500
+ },
+ "icons": [
+ {
+ "id": "kind-actor",
+ "subject": "Human actor",
+ "asset": {
+ "path": "assets/dark/kind-actor.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-client",
+ "subject": "Client application or device",
+ "asset": {
+ "path": "assets/dark/kind-client.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-service",
+ "subject": "Long-running service",
+ "asset": {
+ "path": "assets/dark/kind-service.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-function",
+ "subject": "On-demand function",
+ "asset": {
+ "path": "assets/dark/kind-function.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-worker",
+ "subject": "Background worker",
+ "asset": {
+ "path": "assets/dark/kind-worker.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-database",
+ "subject": "Durable database",
+ "asset": {
+ "path": "assets/dark/kind-database.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-cache",
+ "subject": "Disposable cache",
+ "asset": {
+ "path": "assets/dark/kind-cache.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-queue",
+ "subject": "Asynchronous queue or stream",
+ "asset": {
+ "path": "assets/dark/kind-queue.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-storage",
+ "subject": "Blob or file storage",
+ "asset": {
+ "path": "assets/dark/kind-storage.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ },
+ {
+ "id": "kind-external",
+ "subject": "External system",
+ "asset": {
+ "path": "assets/dark/kind-external.svg",
+ "viewBox": [
+ 0,
+ 0,
+ 24,
+ 24
+ ],
+ "provenance": {
+ "sourceUrl": "https://github.com/stack-sh/theme/tree/main/assets/dark",
+ "sourceRevision": "core-fallback-v1",
+ "copyright": "Stack contributors",
+ "licenseSpdx": "Apache-2.0",
+ "licenseFile": "LICENSE",
+ "modified": false,
+ "redistribution": {
+ "cargo": true,
+ "npm": true,
+ "wasm": true,
+ "commercialApplications": true
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
}
diff --git a/packages/theme/index.d.ts b/packages/theme/index.d.ts
index fbfa632..6d466f1 100644
--- a/packages/theme/index.d.ts
+++ b/packages/theme/index.d.ts
@@ -21,10 +21,16 @@ export interface Catalog {
readonly schemaVersion: "1.0";
readonly catalogVersion: string;
readonly reservedThemeIds: readonly string[];
+ readonly fallbacks: CatalogFallbacks;
readonly fontMetrics: readonly FontMetrics[];
readonly themes: readonly Theme[];
}
+export interface CatalogFallbacks {
+ readonly missingThemeId: string;
+ readonly missingIconId: string;
+}
+
export interface FontMetrics {
readonly id: string;
readonly family: string;
@@ -34,10 +40,17 @@ export interface FontMetrics {
readonly descent: number;
readonly lineGap: number;
readonly defaultAdvance: number;
+ readonly wideAdvance: number;
+ readonly wideRanges: readonly UnicodeRange[];
readonly glyphAdvances: Readonly>;
readonly provenance: Provenance;
}
+export interface UnicodeRange {
+ readonly start: `U+${string}`;
+ readonly end: `U+${string}`;
+}
+
export interface Theme {
readonly id: string;
readonly name: string;
diff --git a/packages/theme/schema/catalog.schema.json b/packages/theme/schema/catalog.schema.json
index f9f09a4..736b83f 100644
--- a/packages/theme/schema/catalog.schema.json
+++ b/packages/theme/schema/catalog.schema.json
@@ -10,6 +10,7 @@
"schemaVersion",
"catalogVersion",
"reservedThemeIds",
+ "fallbacks",
"fontMetrics",
"themes"
],
@@ -30,6 +31,9 @@
},
"uniqueItems": true
},
+ "fallbacks": {
+ "$ref": "#/$defs/catalogFallbacks"
+ },
"fontMetrics": {
"type": "array",
"items": {
@@ -56,6 +60,15 @@
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
},
+ "catalogFallbacks": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["missingThemeId", "missingIconId"],
+ "properties": {
+ "missingThemeId": { "$ref": "#/$defs/identifier" },
+ "missingIconId": { "$ref": "#/$defs/iconIdentifier" }
+ }
+ },
"relativePath": {
"type": "string",
"minLength": 1,
@@ -380,6 +393,8 @@
"descent",
"lineGap",
"defaultAdvance",
+ "wideAdvance",
+ "wideRanges",
"glyphAdvances",
"provenance"
],
@@ -420,9 +435,33 @@
"minimum": 1,
"maximum": 32767
},
+ "wideAdvance": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 32767
+ },
+ "wideRanges": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/unicodeRange" }
+ },
"glyphAdvances": { "$ref": "#/$defs/glyphAdvances" },
"provenance": { "$ref": "#/$defs/provenance" }
}
+ },
+ "unicodeRange": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["start", "end"],
+ "properties": {
+ "start": {
+ "type": "string",
+ "pattern": "^U\\+[0-9A-F]{4,6}$"
+ },
+ "end": {
+ "type": "string",
+ "pattern": "^U\\+[0-9A-F]{4,6}$"
+ }
+ }
}
}
}
diff --git a/review/index.html b/review/index.html
new file mode 100644
index 0000000..9c73bb5
--- /dev/null
+++ b/review/index.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+ Stack core theme review
+
+
+
+
+ Stack core theme review
+
+
+
+
+
+
diff --git a/schemas/catalog.schema.json b/schemas/catalog.schema.json
index f9f09a4..736b83f 100644
--- a/schemas/catalog.schema.json
+++ b/schemas/catalog.schema.json
@@ -10,6 +10,7 @@
"schemaVersion",
"catalogVersion",
"reservedThemeIds",
+ "fallbacks",
"fontMetrics",
"themes"
],
@@ -30,6 +31,9 @@
},
"uniqueItems": true
},
+ "fallbacks": {
+ "$ref": "#/$defs/catalogFallbacks"
+ },
"fontMetrics": {
"type": "array",
"items": {
@@ -56,6 +60,15 @@
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
},
+ "catalogFallbacks": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["missingThemeId", "missingIconId"],
+ "properties": {
+ "missingThemeId": { "$ref": "#/$defs/identifier" },
+ "missingIconId": { "$ref": "#/$defs/iconIdentifier" }
+ }
+ },
"relativePath": {
"type": "string",
"minLength": 1,
@@ -380,6 +393,8 @@
"descent",
"lineGap",
"defaultAdvance",
+ "wideAdvance",
+ "wideRanges",
"glyphAdvances",
"provenance"
],
@@ -420,9 +435,33 @@
"minimum": 1,
"maximum": 32767
},
+ "wideAdvance": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 32767
+ },
+ "wideRanges": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/unicodeRange" }
+ },
"glyphAdvances": { "$ref": "#/$defs/glyphAdvances" },
"provenance": { "$ref": "#/$defs/provenance" }
}
+ },
+ "unicodeRange": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["start", "end"],
+ "properties": {
+ "start": {
+ "type": "string",
+ "pattern": "^U\\+[0-9A-F]{4,6}$"
+ },
+ "end": {
+ "type": "string",
+ "pattern": "^U\\+[0-9A-F]{4,6}$"
+ }
+ }
}
}
}
diff --git a/scripts/catalog-lib.mjs b/scripts/catalog-lib.mjs
index 4346a78..917a83b 100644
--- a/scripts/catalog-lib.mjs
+++ b/scripts/catalog-lib.mjs
@@ -69,6 +69,10 @@ const nodeKinds = [
"external",
];
+function unicodeScalar(label) {
+ return Number.parseInt(label.slice(2), 16);
+}
+
export async function readJson(filePath) {
return JSON.parse(await readFile(filePath, "utf8"));
}
@@ -201,7 +205,7 @@ export function validateSvgText(svg, assetPath, expectedViewBox) {
export async function validateCatalog(
catalog,
- { root = repositoryRoot, validateAssets = true } = {},
+ { root = repositoryRoot, validateAssets = true, requiredThemeIds = [] } = {},
) {
const schema = await readJson(path.join(repositoryRoot, "schemas/catalog.schema.json"));
const ajv = new Ajv2020({ allErrors: true, strict: true });
@@ -219,6 +223,20 @@ export async function validateCatalog(
fail(`duplicate font metrics id: ${metrics.id}`);
}
metricIds.add(metrics.id);
+ let previousRangeEnd = -1;
+ for (const range of metrics.wideRanges) {
+ const start = unicodeScalar(range.start);
+ const end = unicodeScalar(range.end);
+ if (
+ start > end ||
+ end > 0x10ffff ||
+ (start <= 0xdfff && end >= 0xd800) ||
+ start <= previousRangeEnd
+ ) {
+ fail(`font metrics ${metrics.id} has invalid or overlapping wide ranges`);
+ }
+ previousRangeEnd = end;
+ }
if (Object.values(metrics.provenance.redistribution).some((permitted) => !permitted)) {
fail(`font metrics ${metrics.id} is not redistributable in every supported artifact`);
}
@@ -315,6 +333,23 @@ export async function validateCatalog(
}
}
+ for (const requiredThemeId of requiredThemeIds) {
+ if (!themeIds.has(requiredThemeId)) {
+ fail(`catalog is missing required theme: ${requiredThemeId}`);
+ }
+ }
+
+ if (!themeIds.has(catalog.fallbacks.missingThemeId)) {
+ fail(`catalog fallback references unknown theme ${catalog.fallbacks.missingThemeId}`);
+ }
+ for (const theme of catalog.themes) {
+ if (!theme.icons.some((icon) => icon.id === catalog.fallbacks.missingIconId)) {
+ fail(
+ `theme ${theme.id} is missing catalog fallback icon ${catalog.fallbacks.missingIconId}`,
+ );
+ }
+ }
+
return catalog;
}
diff --git a/scripts/validate.mjs b/scripts/validate.mjs
index fbd4d8d..ed19044 100644
--- a/scripts/validate.mjs
+++ b/scripts/validate.mjs
@@ -2,13 +2,16 @@ import path from "node:path";
import { readJson, repositoryRoot, validateCatalog } from "./catalog-lib.mjs";
-const catalogPaths = [
- "catalog/catalog.json",
- "tests/fixtures/catalog/valid.json",
+const catalogs = [
+ {
+ path: "catalog/catalog.json",
+ requiredThemeIds: ["default", "light", "dark"],
+ },
+ { path: "tests/fixtures/catalog/valid.json", requiredThemeIds: [] },
];
-for (const relativePath of catalogPaths) {
- const catalog = await readJson(path.join(repositoryRoot, relativePath));
- await validateCatalog(catalog);
- console.log(`validated ${relativePath}`);
+for (const entry of catalogs) {
+ const catalog = await readJson(path.join(repositoryRoot, entry.path));
+ await validateCatalog(catalog, { requiredThemeIds: entry.requiredThemeIds });
+ console.log(`validated ${entry.path}`);
}
diff --git a/tests/catalog.test.mjs b/tests/catalog.test.mjs
index f4231c6..3f74680 100644
--- a/tests/catalog.test.mjs
+++ b/tests/catalog.test.mjs
@@ -50,6 +50,137 @@ test("unknown fallback icons are rejected", async () => {
);
});
+const nodeKinds = [
+ "actor",
+ "client",
+ "service",
+ "function",
+ "worker",
+ "database",
+ "cache",
+ "queue",
+ "storage",
+ "external",
+];
+
+function linearChannel(channel) {
+ const value = channel / 255;
+ return value <= 0.04045
+ ? value / 12.92
+ : ((value + 0.055) / 1.055) ** 2.4;
+}
+
+function luminance(color) {
+ const channels = color
+ .slice(1, 7)
+ .match(/.{2}/g)
+ .map((channel) => linearChannel(Number.parseInt(channel, 16)));
+ return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
+}
+
+function contrast(left, right) {
+ const lighter = Math.max(luminance(left), luminance(right));
+ const darker = Math.min(luminance(left), luminance(right));
+ return (lighter + 0.05) / (darker + 0.05);
+}
+
+test("the three core themes provide distinct fallback visuals", () => {
+ assert.deepEqual(
+ catalog.themes.map((theme) => theme.id),
+ ["default", "light", "dark"],
+ );
+
+ assert.deepEqual(catalog.fallbacks, {
+ missingThemeId: "default",
+ missingIconId: "kind-external",
+ });
+ const logicalIconSubjects = new Map();
+
+ for (const theme of catalog.themes) {
+ assert.deepEqual(Object.keys(theme.nodeKindFallbacks), nodeKinds);
+ assert.equal(
+ new Set(
+ Object.values(theme.nodeKindFallbacks).map(
+ (fallback) => fallback.fallbackIconId,
+ ),
+ ).size,
+ nodeKinds.length,
+ );
+ assert.ok(
+ new Set(
+ Object.values(theme.nodeKindFallbacks).map((fallback) => fallback.shape),
+ ).size >= 4,
+ );
+ for (const icon of theme.icons) {
+ assert.equal(typeof iconSvg(icon.asset.path), "string");
+ const existingSubject = logicalIconSubjects.get(icon.id);
+ if (existingSubject !== undefined) {
+ assert.equal(icon.subject, existingSubject);
+ }
+ logicalIconSubjects.set(icon.id, icon.subject);
+ }
+ assert.ok(
+ theme.icons.some((icon) => icon.id === catalog.fallbacks.missingIconId),
+ );
+ }
+});
+
+test("unknown catalog fallback targets are rejected", async () => {
+ const fixture = await readJson(
+ path.join(repositoryRoot, "tests/fixtures/catalog/valid.json"),
+ );
+ fixture.fallbacks.missingThemeId = "missing";
+ await assert.rejects(
+ validateCatalog(fixture),
+ /catalog fallback references unknown theme missing/,
+ );
+});
+
+test("core palettes meet text and non-text contrast floors", () => {
+ for (const theme of catalog.themes) {
+ const { palette } = theme;
+ for (const foreground of ["text", "textMuted", "danger"]) {
+ assert.ok(
+ contrast(palette[foreground], palette.surface) >= 4.5,
+ `${theme.id} ${foreground} contrast is below 4.5:1`,
+ );
+ }
+ for (const foreground of ["border", "accent"]) {
+ assert.ok(
+ contrast(palette[foreground], palette.surface) >= 3,
+ `${theme.id} ${foreground} contrast is below 3:1`,
+ );
+ }
+ assert.ok(
+ contrast(palette.connector, palette.canvas) >= 3,
+ `${theme.id} connector contrast is below 3:1`,
+ );
+ }
+});
+
+test("font metrics cover narrow, explicit, and wide scalar advances", () => {
+ const metrics = catalog.fontMetrics.find((entry) => entry.id === "stack_sans");
+ assert.ok(metrics);
+
+ const advance = (scalar) => {
+ const key = `U+${scalar.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
+ const explicit = metrics.glyphAdvances[key];
+ if (explicit !== undefined) return explicit;
+ const codePoint = scalar.codePointAt(0);
+ const isWide = metrics.wideRanges.some(
+ (range) =>
+ codePoint >= Number.parseInt(range.start.slice(2), 16) &&
+ codePoint <= Number.parseInt(range.end.slice(2), 16),
+ );
+ return isWide ? metrics.wideAdvance : metrics.defaultAdvance;
+ };
+
+ assert.equal(advance(" "), 250);
+ assert.equal(advance("A"), 600);
+ assert.equal(advance("日"), 1000);
+ assert.equal(advance("🚀"), 1000);
+});
+
test("Cargo and npm artifacts expose one semantic catalog revision", async () => {
const sourceCatalog = await readJson(
path.join(repositoryRoot, "catalog/catalog.json"),
@@ -100,6 +231,6 @@ test("Cargo and npm artifacts expose one semantic catalog revision", async () =>
new RegExp(`CATALOG_REVISION: &str = "${catalogRevision}"`),
);
assert.match(catalogRevision, /^sha256:[0-9a-f]{64}$/);
- assert.deepEqual(iconAssets, {});
+ assert.equal(Object.keys(iconAssets).length, 30);
assert.equal(iconSvg("assets/missing.svg"), undefined);
});
diff --git a/tests/fixtures/catalog/valid.json b/tests/fixtures/catalog/valid.json
index 7bdc439..8de6ea1 100644
--- a/tests/fixtures/catalog/valid.json
+++ b/tests/fixtures/catalog/valid.json
@@ -5,6 +5,10 @@
"reservedThemeIds": [
"retired"
],
+ "fallbacks": {
+ "missingThemeId": "fixture",
+ "missingIconId": "generic"
+ },
"fontMetrics": [
{
"id": "fixture_font",
@@ -15,6 +19,13 @@
"descent": -200,
"lineGap": 100,
"defaultAdvance": 600,
+ "wideAdvance": 1000,
+ "wideRanges": [
+ {
+ "start": "U+2E80",
+ "end": "U+9FFF"
+ }
+ ],
"glyphAdvances": {
"U+0020": 250,
"U+0041": 650