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
6 changes: 3 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ repository = "https://github.com/stack-sh/engine"
[workspace.dependencies]
serde = { version = "=1.0.229", features = ["derive"] }
serde_json = "=1.0.151"
stack-compiler = { git = "https://github.com/stack-sh/compiler.git", rev = "17a0abe9c35e641761ff08fdf59b29a42828d9fd" }
stack-compiler = { git = "https://github.com/stack-sh/compiler.git", rev = "3d2379483da1edaeb24a26d43743587a4f5bd645" }
stack-formatter = { path = "crates/stack-formatter" }
stack-theme = { git = "https://github.com/stack-sh/theme.git", rev = "ed6c500762fc9ccffc8777172ac672a716dcd916" }
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ cargo doc --workspace --no-deps

The renderer emits fixed-dimension standalone SVG with embedded catalog icons, local marker references, escaped authored text, accessible title and description metadata, and no script, event handler, external URL, host font measurement, or runtime I/O. Canonical SVG snapshots are byte-stable and parsed by `scripts/validate-svg.py`; set `UPDATE_STACK_SNAPSHOTS=1` only when intentionally regenerating them. CI also executes one exact numeric geometry fixture in both the native suite and a WASI build.

The npm package exports synchronous `format`, `check`, and `render` functions after asynchronous module initialization. Each operation accepts `string | Uint8Array` and returns a specific typed result with camel-case metadata and portable diagnostics. Invalid UTF-8 remains a normal `STK1001` result. Unsupported JavaScript input types and internal operational failures throw at the adapter boundary. Shared fixtures compare complete native and WebAssembly results, including formatted source, diagnostics, SVG, and metadata. Artifact validation audits WebAssembly imports and package contents; browser consumers retain responsibility for loading the module and performing any DOM, filesystem, network, or clock work.
The npm package exports synchronous `format`, `check`, and `render` functions after asynchronous module initialization. Each operation accepts `string | Uint8Array` and returns a specific typed result with camel-case metadata and portable diagnostics. Diagnostics preserve the compiler's primary range, ordered `expected` values, corrective help, and related source locations. Invalid UTF-8 remains a normal `STK1001` result. Unsupported JavaScript input types and internal operational failures throw at the adapter boundary. Shared fixtures compare complete native and WebAssembly results, including formatted source, diagnostics, SVG, and metadata. Artifact validation audits WebAssembly imports and package contents; browser consumers retain responsibility for loading the module and performing any DOM, filesystem, network, or clock work.

Public npm releases are produced from GitHub Releases after the repository checks pass. See [RELEASING.md](./RELEASING.md) for the first-release bootstrap and subsequent trusted-publishing flow.

Expand Down
2 changes: 1 addition & 1 deletion crates/stack-engine-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stack-engine-wasm"
version = "0.1.0"
version = "0.2.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
Expand Down
11 changes: 11 additions & 0 deletions crates/stack-engine-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub struct Diagnostic {
pub message: String,
/// Primary end-exclusive source range.
pub range: SourceRange,
/// Ordered source values or constructs valid at the primary range.
pub expected: Vec<String>,
/// Optional corrective guidance.
pub help: Option<String>,
/// Other source locations involved in the diagnostic.
Expand Down Expand Up @@ -217,6 +219,7 @@ impl From<stack_engine::Diagnostic> for Diagnostic {
severity: Severity::from(diagnostic.severity),
message: diagnostic.message,
range: SourceRange::from(diagnostic.range),
expected: diagnostic.expected,
help: diagnostic.help,
related: diagnostic
.related
Expand Down Expand Up @@ -291,6 +294,7 @@ export interface Diagnostic {
readonly severity: Severity;
readonly message: string;
readonly range: SourceRange;
readonly expected: readonly string[];
readonly help: string | null;
readonly related: readonly RelatedInformation[];
}
Expand Down Expand Up @@ -433,6 +437,11 @@ fn diagnostic_to_js(diagnostic: Diagnostic) -> Result<JsValue, JsValue> {
)?;
set(&output, "message", diagnostic.message.into())?;
set(&output, "range", range_to_js(diagnostic.range)?)?;
let expected = Array::new();
for value in diagnostic.expected {
expected.push(&value.into());
}
set(&output, "expected", expected.into())?;
set_optional_string(&output, "help", diagnostic.help)?;
let related = Array::new();
for information in diagnostic.related {
Expand Down Expand Up @@ -553,13 +562,15 @@ mod tests {
severity: stack_engine::Severity::Warning,
message: "fallback used".to_owned(),
range,
expected: vec!["available-resource".to_owned()],
help: Some("install the resource".to_owned()),
related: vec![stack_engine::RelatedInformation {
message: "requested here".to_owned(),
range,
}],
});
assert_eq!(converted.severity, Severity::Warning);
assert_eq!(converted.expected, ["available-resource"]);
assert_eq!(converted.help.as_deref(), Some("install the resource"));
assert_eq!(converted.related[0].message, "requested here");
assert_eq!(converted.related[0].range.start.byte_offset, 1);
Expand Down
2 changes: 1 addition & 1 deletion crates/stack-engine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stack-engine"
version = "0.1.0"
version = "0.2.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
Expand Down
9 changes: 8 additions & 1 deletion crates/stack-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ fn resource_diagnostic(
severity: Severity::Warning,
message,
range: SourceRange::from(span),
expected: Vec::new(),
help: Some(help.to_owned()),
related: Vec::new(),
})
Expand Down Expand Up @@ -285,6 +286,7 @@ fn order_diagnostic(
severity: Severity::Warning,
message: "order hint could not be satisfied by deterministic layout".to_owned(),
range: SourceRange::from(span),
expected: Vec::new(),
help: Some("Adjust the order hint or same-rank constraints.".to_owned()),
related: Vec::new(),
})
Expand Down Expand Up @@ -383,6 +385,8 @@ pub struct Diagnostic {
pub message: String,
/// Primary end-exclusive source range.
pub range: SourceRange,
/// Ordered source values or constructs valid at the primary range.
pub expected: Vec<String>,
/// Optional corrective guidance.
pub help: Option<String>,
/// Other source locations involved in the diagnostic.
Expand Down Expand Up @@ -459,6 +463,7 @@ impl From<compiler_diagnostic::Diagnostic> for Diagnostic {
},
message: diagnostic.message,
range: SourceRange::from(diagnostic.span),
expected: diagnostic.expected,
help: diagnostic.help,
related: diagnostic
.related
Expand Down Expand Up @@ -780,7 +785,7 @@ mod tests {
}

#[test]
fn diagnostic_conversion_keeps_warning_help_and_related_ranges() {
fn diagnostic_conversion_keeps_expected_help_and_related_ranges() {
let start = compiler_diagnostic::SourcePosition {
byte_offset: 3,
line: 2,
Expand All @@ -796,6 +801,7 @@ mod tests {
severity: compiler_diagnostic::Severity::Warning,
message: "warning".to_owned(),
span: compiler_diagnostic::Span { start, end },
expected: vec!["right".to_owned(), "down".to_owned()],
help: Some("help".to_owned()),
related: vec![compiler_diagnostic::RelatedInformation {
message: "related".to_owned(),
Expand All @@ -805,6 +811,7 @@ mod tests {

let portable = Diagnostic::from(diagnostic);
assert_eq!(portable.severity, Severity::Warning);
assert_eq!(portable.expected, ["right", "down"]);
assert_eq!(portable.help.as_deref(), Some("help"));
assert_eq!(portable.related[0].message, "related");
assert_eq!(
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "stack-engine-workspace",
"private": true,
"version": "0.1.0",
"version": "0.2.0",
"workspaces": [
"packages/engine"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ const checked = check(new TextEncoder().encode('stack 1.0 diagram "API" { node a
const rendered = render('stack 1.0 diagram "API" { node api "API" }');
```

Each operation is synchronous after module initialization and accepts either a JavaScript string or `Uint8Array`. Invalid Stack source, including invalid UTF-8 bytes, returns normal portable diagnostics. A JavaScript value of any other type throws `TypeError` at the package boundary.
Each operation is synchronous after module initialization and accepts either a JavaScript string or `Uint8Array`. Invalid Stack source, including invalid UTF-8 bytes, returns normal portable diagnostics. Diagnostics include the primary range, ordered `expected` values, corrective help, and related source locations. A JavaScript value of any other type throws `TypeError` at the package boundary.

The package does not read files, contact a network service, inspect the DOM, observe a clock, or measure host fonts. Consumers own module loading and all host I/O.
2 changes: 1 addition & 1 deletion packages/engine/THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

| Component | Version / revision | Selected license | Source |
| --- | --- | --- | --- |
| `stack-compiler` | `17a0abe9c35e641761ff08fdf59b29a42828d9fd` | Apache-2.0 | <https://github.com/stack-sh/compiler> |
| `stack-compiler` | `3d2379483da1edaeb24a26d43743587a4f5bd645` | Apache-2.0 | <https://github.com/stack-sh/compiler> |
| `stack-theme` | `ed6c500762fc9ccffc8777172ac672a716dcd916` | Apache-2.0 | <https://github.com/stack-sh/theme> |
| `serde` / `serde_core` | `1.0.229` | Apache-2.0 | <https://github.com/serde-rs/serde> |
| `serde_json` | `1.0.151` | Apache-2.0 | <https://github.com/serde-rs/json> |
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stack-sh/engine",
"version": "0.1.0",
"version": "0.2.0",
"description": "Browser WebAssembly adapter for Stack diagram operations",
"type": "module",
"license": "Apache-2.0",
Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/operation-cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
"value": "stack 1.0 diagram \"Fallback\" { theme neon node api \"API\" { icon \"missing\" } }"
}
},
{
"name": "actionable-error-string",
"input": {
"kind": "string",
"value": "stack 1.0\ndiagram \"Example\" {\n node app \"App\"\n layout { direction hoo }\n}\n"
}
},
{
"name": "invalid-utf8-bytes",
"input": {
Expand Down
2 changes: 1 addition & 1 deletion tests/specification-revision
Original file line number Diff line number Diff line change
@@ -1 +1 @@
f382069928c805fe69b7a192bfd6a877036bc036
7f9154d22702ddf02f2713bbc06dde7bdf635806
1 change: 1 addition & 0 deletions tests/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const diagnostic: Diagnostic | undefined = checked.diagnostics[0];
formatted.formattedSource?.toUpperCase();
rendered.svg?.startsWith("<svg");
diagnostic?.range.start.byteOffset.toFixed(0);
diagnostic?.expected.join(", ");

// @ts-expect-error Stack source is intentionally limited to string or Uint8Array.
format({ source: text });
Expand Down
21 changes: 21 additions & 0 deletions tests/wasm.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ test("invalid UTF-8 is a normal diagnostic result for every operation", () => {
}
});

test("browser diagnostics preserve actionable compiler guidance", () => {
const actionable = wasmOutputs().find(
({ name }) => name === "actionable-error-string",
);
assert.ok(actionable);
assert.equal(actionable.render.svg, null);
assert.equal(actionable.check.metadata.engineVersion, "0.2.0");
assert.deepEqual(actionable.check.diagnostics[0], {
code: "STK2002",
severity: "error",
message: "Unknown layout direction 'hoo'.",
range: {
start: { byteOffset: 68, line: 4, column: 22 },
end: { byteOffset: 71, line: 4, column: 25 },
},
expected: ["right", "down"],
help: "Use 'right' for horizontal flow or 'down' for vertical flow.",
related: [],
});
});

test("the JavaScript boundary rejects unsupported source values consistently", () => {
for (const operation of [format, check, render]) {
assert.throws(
Expand Down