Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Status

This document defines the draft `1.0` core catalog document shape. Catalog and package version `0.7.0` remain pre-1.0 and may change incompatibly before a stable release. The Rust catalog is distributed on crates.io; generated npm artifacts are maintained in this repository. User-imported provider packs use the separate contract in [`PROVIDER_PACKS.md`](./PROVIDER_PACKS.md); their custom-term assets never enter this core catalog.
This document defines the draft `1.0` core catalog document shape. Catalog and package version `0.8.0` remain pre-1.0 and may change incompatibly before a stable release. The Rust catalog is distributed on crates.io; generated npm artifacts are maintained in this repository. User-imported provider packs use the separate contract in [`PROVIDER_PACKS.md`](./PROVIDER_PACKS.md); their custom-term assets never enter this core catalog.

The JSON Schema at [`schemas/catalog.schema.json`](./schemas/catalog.schema.json) is the machine-readable source of truth. [`catalog/catalog.json`](./catalog/catalog.json) is the only source catalog. Generated Cargo and npm copies must not be edited directly. The schema is copied into both packages; `$schema` is an editor-facing canonical repository URL and runtime consumers do not fetch it.

Expand Down Expand Up @@ -45,6 +45,18 @@ Integer units keep Rust and JavaScript consumers from introducing representation

`connector` requires line, text, and label-background palette references plus integer line width and arrow size. `dashMilliPx` is the only optional connector field; absence means a solid line.

## Configured theme overrides

[`schemas/theme-overrides.schema.json`](./schemas/theme-overrides.schema.json) defines a map of at most 32 user theme definitions. Each map key is any valid Stack theme identifier, including `default`, `light`, or `dark`. A definition requires one `extends` value from those three built-in identifiers and at least one `palette` value. Palette values use the same six- or eight-digit hexadecimal sRGB format as the core catalog. Other theme records cannot be configured through this contract.

Resolution always reads `extends` from the original built-in catalog before applying any configured definition. Configured themes therefore do not extend one another. A definition named `default` with `extends: default` intentionally shadows the built-in `default` without forming a cycle. Theme lookup checks configured definitions before the installed catalog, and a missing source-level theme selection requests the effective `default`, so the configured override applies there too.

The resolver inherits typography, node fallbacks, connectors, and icons from the selected built-in theme. A definition that shadows a built-in theme keeps that theme's display metadata. A new identifier uses its identifier as the display name and omits the inherited description. Existing catalog positions are preserved for shadowed themes; new themes are appended in identifier order.

The Rust resolver validates identifier, count, base, palette presence, and color constraints even when a caller does not run JSON Schema validation. It normalizes configured colors to uppercase hexadecimal. Transparent colors and contrast below the core review floors produce structured warnings; the resolver never silently replaces a user color.

With no configured definitions, the effective revision is the base `catalogRevision`. Otherwise it is `sha256:` followed by the lowercase SHA-256 digest of `stack-theme-effective-v1`, a null byte, the base revision, a null byte, and compact JSON for the normalized definition map. Map keys use lexicographic order, definition fields use `extends` then `palette`, palette fields use contract order, absent fields are omitted, and colors use uppercase hexadecimal. The revision changes with either the built-in catalog or the effective user definition while remaining stable across input key order and color casing.

## Font metrics

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.
Expand All @@ -69,7 +81,7 @@ SVG validation uses an element and attribute allowlist. It rejects scripts, even

## Cargo and npm boundary

Cargo `stack-theme` exposes typed Rust records, `catalog()`, `catalog_json()`, `catalog_schema_json()`, `provider_pack_schema_json()`, `icon_svg()`, `CATALOG_VERSION`, and `CATALOG_REVISION`. npm `@stack-sh/theme` exposes the equivalent frozen `catalog`, `providerPackSchema`, `iconAssets`, `iconSvg()`, `catalogVersion`, and `catalogRevision`, plus TypeScript declarations and both schema JSON subpath exports. Referenced core SVG and license files are copied into both package roots; core SVG bytes are also embedded behind the Rust and JavaScript accessors so runtime consumers never resolve catalog paths through the host. Provider-pack asset bytes are supplied by the user and are not embedded.
Cargo `stack-theme` exposes typed Rust records, `catalog()`, `catalog_json()`, `catalog_schema_json()`, `provider_pack_schema_json()`, `theme_overrides_schema_json()`, `resolve_theme_overrides()`, `icon_svg()`, `CATALOG_VERSION`, and `CATALOG_REVISION`. npm `@stack-sh/theme` exposes the equivalent frozen `catalog`, `providerPackSchema`, `themeOverridesSchema`, `iconAssets`, `iconSvg()`, `catalogVersion`, and `catalogRevision`, plus TypeScript declarations and all three schema JSON subpath exports. Referenced core SVG and license files are copied into both package roots; core SVG bytes are also embedded behind the Rust and JavaScript accessors so runtime consumers never resolve catalog paths through the host. Provider-pack asset bytes are supplied by the user and are not embedded.

`npm run generate` validates the source catalog, checks package version equality, computes one revision, and updates both package artifacts. `npm run generate:check` fails when a generated artifact is missing or stale. Generated package data is checked into Git so Cargo and npm builds do not need network, filesystem discovery, Git, a clock, locale, or host font measurement at runtime.

Expand Down
85 changes: 84 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/stack-sh/theme"
rust-version = "1.85"
version = "0.7.0"
version = "0.8.0"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "=0.11.0"
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

`stack-sh/theme` is the canonical public contract for Stack diagram themes, icons, font metrics, and local provider icon packs.

The core catalog contract is defined by [`CONTRACT.md`](./CONTRACT.md) and [`schemas/catalog.schema.json`](./schemas/catalog.schema.json). The separate local-only provider-pack contract is defined by [`PROVIDER_PACKS.md`](./PROVIDER_PACKS.md) and [`schemas/provider-pack.schema.json`](./schemas/provider-pack.schema.json). The canonical core source is [`catalog/catalog.json`](./catalog/catalog.json); Rust and JavaScript package trees are generated from that source with one content revision.
The core catalog and configured palette contract are defined by [`CONTRACT.md`](./CONTRACT.md), [`schemas/catalog.schema.json`](./schemas/catalog.schema.json), and [`schemas/theme-overrides.schema.json`](./schemas/theme-overrides.schema.json). The separate local-only provider-pack contract is defined by [`PROVIDER_PACKS.md`](./PROVIDER_PACKS.md) and [`schemas/provider-pack.schema.json`](./schemas/provider-pack.schema.json). The canonical core source is [`catalog/catalog.json`](./catalog/catalog.json); Rust and JavaScript package trees are generated from that source with one content revision.

The current `0.7.0` catalog contains the core `default`, `light`, and `dark` themes, repository-authored fallbacks for every Stack 1.0 node kind, 30 provider-neutral explicit icons, and versioned host-independent font metrics. The explicit icon catalog covers clients and compute, networking and delivery, data and events, development workflows, security, general collaboration tools, and AI systems. Its identifiers are `api`, `web`, `mobile`, `desktop`, `server`, `container`, `cluster`, `cloud`, `scheduler`, `webhook`, `identity`, `observability`, `gateway`, `load-balancer`, `dns`, `cdn`, `firewall`, `network`, `event`, `stream`, `search`, `analytics`, `repository`, `pipeline`, `secret`, `document`, `task`, `chat`, `email`, and `ai`.
The current `0.8.0` catalog contains the core `default`, `light`, and `dark` themes, repository-authored fallbacks for every Stack 1.0 node kind, 30 provider-neutral explicit icons, and versioned host-independent font metrics. It also exposes a palette-only resolver that lets user configuration add any valid theme name or intentionally shadow a built-in name while inheriting the built-in typography, node styles, connectors, and icons. The built-in palettes themselves are unchanged in this release.

Provider-specific assets are not bundled. The provider-pack contract lets a CLI or browser validate an archive that the user explicitly selected from the provider's official source, keep it local, preserve the artwork, and carry source and terms notices into diagram output.

Expand All @@ -13,7 +13,7 @@ Provider-specific assets are not bundled. The provider-pack contract lets a CLI
Add the Rust catalog from crates.io with:

```sh
cargo add stack-theme@0.7.0
cargo add stack-theme@0.8.0
```

The package supports Rust 1.85 or newer and includes only the generated catalog API, repository-authored SVG assets, public schemas, package documentation, and the Apache-2.0 license.
Expand Down Expand Up @@ -75,4 +75,4 @@ Repository-authored source code, catalog data, and assets are licensed under the

Third-party fonts, icons, and other assets keep their own licenses and are not relicensed under Apache-2.0. Their provenance and redistribution terms must be recorded in [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md) before they are committed. Current provider icons are user-imported and are never committed or copied into the Cargo or npm packages.

Maintainers use the [trusted publishing procedure](./docs/releasing.md#ongoing-trusted-publishing) for subsequent crates.io releases. See the [0.7.0 release notes](./docs/releases/v0.7.0.md) for the cache card update.
Maintainers use the [trusted publishing procedure](./docs/releasing.md#ongoing-trusted-publishing) for subsequent crates.io releases. See the [0.8.0 release notes](./docs/releases/v0.8.0.md) for configured palette overrides.
3 changes: 2 additions & 1 deletion THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ The repository uses the following third-party libraries:
| TypeScript | 7.0.2 | Development-only npm declaration checking | https://github.com/microsoft/TypeScript/tree/v7.0.2 | Microsoft Corporation | Apache-2.0 | Installed by `npm ci`; not included in `@stack-sh/theme` |
| serde | 1.0.229 | Rust catalog deserialization and public data types | https://github.com/serde-rs/serde/tree/v1.0.229 | David Tolnay | MIT OR Apache-2.0 | Cargo dependency; no separately bundled asset |
| serde_json | 1.0.151 | Embedded JSON catalog deserialization | https://github.com/serde-rs/json/tree/v1.0.151 | David Tolnay | MIT OR Apache-2.0 | Cargo dependency; no separately bundled asset |
| sha2 | 0.11.0 | Effective theme catalog revision hashing | https://github.com/RustCrypto/hashes/tree/sha2-v0.11.0/sha2 | RustCrypto Developers | MIT OR Apache-2.0 | Cargo dependency; no separately bundled asset |

Ajv, saxes, and TypeScript retain their upstream licenses in installed package metadata and the npm lockfile. serde and serde_json retain their upstream Cargo package metadata and license terms. No dependency source or license text is copied into either generated distribution package by this repository.
Ajv, saxes, and TypeScript retain their upstream licenses in installed package metadata and the npm lockfile. serde, serde_json, and sha2 retain their upstream Cargo package metadata and license terms. No dependency source or license text is copied into either generated distribution package by this repository.

Before adding a third-party asset, record all of the following in this file:

Expand Down
2 changes: 1 addition & 1 deletion catalog/catalog.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/stack-sh/theme/main/schemas/catalog.schema.json",
"schemaVersion": "1.0",
"catalogVersion": "0.7.0",
"catalogVersion": "0.8.0",
"reservedThemeIds": [],
"fallbacks": {
"missingThemeId": "default",
Expand Down
1 change: 1 addition & 0 deletions crates/stack-theme/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ include = ["src/**", "assets/**", "licenses/**", "schema/**", "README.md", "LICE
[dependencies]
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
2 changes: 1 addition & 1 deletion crates/stack-theme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

The public contract and compatibility policy are documented in the repository's [`CONTRACT.md`](https://github.com/stack-sh/theme/blob/main/CONTRACT.md).

Add version 0.7.0 from crates.io with `cargo add stack-theme@0.7.0`. The package supports Rust 1.85 or newer.
Add version 0.8.0 from crates.io with `cargo add stack-theme@0.8.0`. The package supports Rust 1.85 or newer. `resolve_theme_overrides()` applies palette-only user definitions over the original built-in catalog, including intentional overrides of `default`, `light`, and `dark`.
2 changes: 1 addition & 1 deletion crates/stack-theme/schema/catalog.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"identifier": {
"type": "string",
"pattern": "^[a-z][a-z0-9_-]{0,63}$"
"pattern": "^(?!.*--)[a-z][a-z0-9_-]{0,63}$"
},
"iconIdentifier": {
"type": "string",
Expand Down
53 changes: 53 additions & 0 deletions crates/stack-theme/schema/theme-overrides.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/stack-sh/theme/main/schemas/theme-overrides.schema.json",
"title": "Stack Theme Overrides",
"description": "Palette-only user theme definitions applied over built-in Stack themes.",
"type": "object",
"maxProperties": 32,
"propertyNames": {
"$ref": "#/$defs/identifier"
},
"additionalProperties": {
"$ref": "#/$defs/themeOverride"
},
"$defs": {
"identifier": {
"type": "string",
"pattern": "^(?!.*--)[a-z][a-z0-9_-]{0,63}$"
},
"color": {
"type": "string",
"pattern": "^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$"
},
"themeOverride": {
"type": "object",
"additionalProperties": false,
"required": ["extends", "palette"],
"properties": {
"extends": {
"enum": ["default", "light", "dark"]
},
"palette": {
"$ref": "#/$defs/paletteOverride"
}
}
},
"paletteOverride": {
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"properties": {
"canvas": { "$ref": "#/$defs/color" },
"surface": { "$ref": "#/$defs/color" },
"surfaceMuted": { "$ref": "#/$defs/color" },
"text": { "$ref": "#/$defs/color" },
"textMuted": { "$ref": "#/$defs/color" },
"border": { "$ref": "#/$defs/color" },
"accent": { "$ref": "#/$defs/color" },
"danger": { "$ref": "#/$defs/color" },
"connector": { "$ref": "#/$defs/color" }
}
}
}
}
2 changes: 1 addition & 1 deletion crates/stack-theme/src/generated/catalog.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/stack-sh/theme/main/schemas/catalog.schema.json",
"schemaVersion": "1.0",
"catalogVersion": "0.7.0",
"catalogVersion": "0.8.0",
"reservedThemeIds": [],
"fallbacks": {
"missingThemeId": "default",
Expand Down
4 changes: 2 additions & 2 deletions crates/stack-theme/src/generated/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by scripts/generate.mjs. Do not edit.
pub const CATALOG_VERSION: &str = "0.7.0";
pub const CATALOG_REVISION: &str = "sha256:4a8b94b746c6b120998bfbe701edd722449a28c89c424b0a33f67561756ded5a";
pub const CATALOG_VERSION: &str = "0.8.0";
pub const CATALOG_REVISION: &str = "sha256:e0d119e8048f74ebbc85e8cc34c1f25ccc5b4fe6cb8274c80a92271b81fa30ea";

pub fn icon_svg(asset_path: &str) -> Option<&'static str> {
match asset_path {
Expand Down
Loading