From fc765e6888115139ad4ea7202229311d00ec696a Mon Sep 17 00:00:00 2001 From: Ferraz Date: Thu, 9 Jul 2026 20:01:20 -0300 Subject: [PATCH 1/3] feat: render wire fences in Markdown and MDX --- .github/workflows/publish.yml | 2 +- CHANGELOG.md | 5 + README.md | 32 +- docs/CONTEXT.md | 9 +- docs/MVP.md | 24 +- .../0019-build-time-markdown-integration.md | 48 + package.json | 2 +- packages/markdown/LICENSE | 21 + packages/markdown/README.md | 107 ++ packages/markdown/package.json | 74 ++ packages/markdown/src/index.ts | 2 + packages/markdown/src/rehype-wire.ts | 62 + packages/markdown/src/rehype.ts | 1 + packages/markdown/src/remark-wire.ts | 62 + packages/markdown/src/remark.ts | 1 + packages/markdown/src/render-wire.ts | 79 ++ packages/markdown/test/markdown.test.ts | 92 ++ packages/markdown/tsconfig.json | 10 + packages/markdown/tsup.config.ts | 13 + pnpm-lock.yaml | 1107 +++++++++++++++++ scripts/package-smoke-test.mjs | 45 +- scripts/pin-workspace-deps.mjs | 2 +- tsconfig.json | 1 + 23 files changed, 1789 insertions(+), 12 deletions(-) create mode 100644 docs/adr/0019-build-time-markdown-integration.md create mode 100644 packages/markdown/LICENSE create mode 100644 packages/markdown/README.md create mode 100644 packages/markdown/package.json create mode 100644 packages/markdown/src/index.ts create mode 100644 packages/markdown/src/rehype-wire.ts create mode 100644 packages/markdown/src/rehype.ts create mode 100644 packages/markdown/src/remark-wire.ts create mode 100644 packages/markdown/src/remark.ts create mode 100644 packages/markdown/src/render-wire.ts create mode 100644 packages/markdown/test/markdown.test.ts create mode 100644 packages/markdown/tsconfig.json create mode 100644 packages/markdown/tsup.config.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a666f00..b2d3c00 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -99,6 +99,6 @@ jobs: set -euo pipefail node scripts/pin-workspace-deps.mjs tag="${{ inputs.npm_tag || 'latest' }}" - for pkg in core cli wire-lang; do + for pkg in core cli markdown wire-lang; do ( cd "packages/$pkg" && npm publish --provenance --access public --tag "$tag" ) done diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f44a98..1363481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to Wire Lang will be documented in this file. +## Unreleased + +- Added `@wire-lang/markdown` with remark, rehype, and MDX plugins that render + `wire` fenced code blocks as inline SVG. + ## 0.2.0 - Added bus-rail layout support for clearer shared-net routing. diff --git a/README.md b/README.md index d9bb3df..acc6c49 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,27 @@ import { renderSvg } from "wire-lang"; const svg = renderSvg(source); ``` +Render the same source directly from a Markdown or MDX fence with the build-time +plugins in `@wire-lang/markdown`: + +````markdown +```wire +schematic + component R1 Resistor value=220ohm + component D1 LED color=red + connect R1.1, D1.A +``` +```` + +```bash +npm install @wire-lang/markdown +``` + +Use `remarkWire` in a remark pipeline or `rehypeWire` in a rehype/MDX pipeline. +Both replace the fence with standalone inline SVG while the document is built; +see the [`@wire-lang/markdown` guide](./packages/markdown) for complete setup and +runtime tradeoffs. + ## How it compares Think **[Mermaid Charts](https://github.com/mermaid-js/mermaid), but for electronic schematics**: text goes in, documentation- @@ -171,11 +192,12 @@ symbol art; it does not claim formal IEC/IEEE compliance. See ### Packages & development -| Package | Role | -| ------------------------------------ | ---------------------------------------------------------- | -| [`wire-lang`](./packages/wire-lang) | User-facing aggregate package and the `wire` binary | -| [`@wire-lang/core`](./packages/core) | Parser, compiler, schematic model, layout engine, renderer | -| [`@wire-lang/cli`](./packages/cli) | `wire check`, `wire render`, `wire watch` | +| Package | Role | +| -------------------------------------------- | ---------------------------------------------------------- | +| [`wire-lang`](./packages/wire-lang) | User-facing aggregate package and the `wire` binary | +| [`@wire-lang/core`](./packages/core) | Parser, compiler, schematic model, layout engine, renderer | +| [`@wire-lang/cli`](./packages/cli) | `wire check`, `wire render`, `wire watch` | +| [`@wire-lang/markdown`](./packages/markdown) | Remark, rehype, and MDX build-time integration | ```bash pnpm install diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md index 9e52e16..40c5991 100644 --- a/docs/CONTEXT.md +++ b/docs/CONTEXT.md @@ -416,6 +416,11 @@ _Avoid_: ASCII-only source A fenced code block tagged as `wire` for documentation systems that integrate Wire Lang. _Avoid_: DOM source block, wire file +**Markdown Integration**: +The build-time `@wire-lang/markdown` plugins that replace a **Markdown Fence** +with standalone inline SVG through remark, rehype, or MDX. +_Avoid_: browser auto-render, raw HTML passthrough, Mermaid plugin + **VS Code Extension**: A future editor integration for Wire Lang syntax highlighting, diagnostics, and authoring feedback. _Avoid_: MVP core library, SVG renderer @@ -876,7 +881,9 @@ Terminals are user-defined through recommended `pins: ic-pin-list` written as `p - Standard symbols use an IEC-style **Symbol Style Profile** where practical, but the project does not claim formal standards compliance in the MVP. - The default file extension for a **Wire File** is `.wire`. - **Wire Files** are UTF-8; Unicode is allowed in values, labels, and annotations. -- The recommended **Markdown Fence** tag is `wire`, but direct Markdown integration is outside the core MVP. +- The recommended **Markdown Fence** tag is `wire`. Direct integration is + outside the core MVP and is implemented by the build-time + `@wire-lang/markdown` **Markdown Integration** package. - Syntax highlighting and editor extensions are outside the MVP; a **Headless Language Server** and **VS Code Extension** are high-priority post-MVP follow-ups. - A **Wire File** contains exactly one **Source Document** in the MVP. - The MVP supports `//` **Line Comments** and does not need block comments. diff --git a/docs/MVP.md b/docs/MVP.md index 0476eb8..3f98a9c 100644 --- a/docs/MVP.md +++ b/docs/MVP.md @@ -27,7 +27,8 @@ The main product reference is Mermaid: a text-first documentation workflow where - No polished end-user CLI beyond minimal check, render, and watch commands. - No preview server command in the MVP. - No browser auto-render in the MVP. -- No headless language server, Markdown processor, or VS Code extension in the MVP. +- No headless language server, Markdown processor, or VS Code extension in the + MVP. Markdown integration is provided separately as a post-MVP package. - No formal IEC 60617, IEEE 315, or other standards compliance claim. ## Source Format @@ -362,6 +363,11 @@ Initial MVP packages: - `@wire-lang/core`: parser, validators, compiler, schematic model, layout model, and SVG renderer - `@wire-lang/cli`: `wire check`, `wire render`, and `wire watch` +Implemented post-MVP packages: + +- `@wire-lang/markdown`: remark, rehype, and MDX build-time plugins for the + `wire` fenced code tag + Post-MVP packages: - `@wire-lang/browser` @@ -609,6 +615,20 @@ await run() // does not duplicate output await run({ force: true }) // may re-render explicitly ``` +## Post-MVP Markdown and MDX Integration + +`@wire-lang/markdown` provides `remarkWire` and `rehypeWire`. Both recognize the +`wire` fenced code tag, render its source with `@wire-lang/core`, and replace the +source block with a structured inline `` element. `rehypeWire` also works as +an MDX rehype plugin. + +The supported default is build-time rendering: invalid source fails the document +build with a diagnostic mapped into the Markdown file, and the generated page +does not ship the Wire compiler or renderer to the browser. The same processor +can run at request time on a server. Client-side runtime replacement is not part +of this package; applications may call `renderSvg` directly, while automatic DOM +discovery remains the planned `@wire-lang/browser` responsibility. + ## Post-MVP Roadmap High-priority follow-ups: @@ -620,7 +640,6 @@ High-priority follow-ups: - browser auto-render for `pre.wire-lang` and `code.wire-lang` - headless language server using a Langium grammar and the existing validators - VS Code extension with syntax highlighting, diagnostics, and authoring feedback -- Markdown/MDX integrations using the `wire` fenced code tag - custom component libraries passed through the JavaScript API Later extensions: @@ -653,3 +672,4 @@ Later extensions: - [ADR 0015](./adr/0015-no-preview-server-in-mvp.md) records the MVP preview decision. - [ADR 0016](./adr/0016-browser-auto-render-post-mvp.md) records the browser auto-render scope decision. - [ADR 0017](./adr/0017-esm-only-node-20.md) records the runtime and module-format decision. +- [ADR 0019](./adr/0019-build-time-markdown-integration.md) records the build-time Markdown/MDX integration decision. diff --git a/docs/adr/0019-build-time-markdown-integration.md b/docs/adr/0019-build-time-markdown-integration.md new file mode 100644 index 0000000..ea1c35d --- /dev/null +++ b/docs/adr/0019-build-time-markdown-integration.md @@ -0,0 +1,48 @@ +# ADR 0019: Build-time Markdown integration with structured inline SVG + +## Status + +Accepted. + +## Context + +Wire Lang's Mermaid-style documentation workflow needs fenced code blocks tagged +`wire` to render inside Markdown and MDX. The integration could render in the +browser at page load, during each server request, or while the document is +built. It also needs to compose with both sides of the unified ecosystem: +remark's Markdown AST and rehype's HTML AST. + +Emitting generated SVG as raw HTML would require downstream processors to opt +into dangerous/raw HTML handling and would make plugin ordering and sanitizing +harder to reason about. + +## Decision + +Publish `@wire-lang/markdown` with two build-time plugins: + +- `remarkWire` replaces Markdown `code` nodes whose language is exactly `wire`. +- `rehypeWire` replaces standard `pre > code.language-wire` HAST blocks and is + the direct MDX integration. + +Both render with `@wire-lang/core`, parse the renderer-owned SVG into structured +HAST, and insert the `` element in place of the source block. Fatal Wire +diagnostics fail the document build as VFile messages mapped to the fence's +location in the containing document. + +Build time is the documented default because it ships no compiler JavaScript to +the browser and catches invalid diagrams before deployment. Request-time server +processing may use the same plugins. Browser auto-render remains a separate +future `@wire-lang/browser` concern; applications that need it immediately can +call `renderSvg` and manage DOM insertion themselves. + +## Consequences + +- Markdown, rehype, and MDX users share one package and one `wire` fence + convention. +- Generated SVG is visible to later HAST plugins without enabling arbitrary raw + HTML. +- Sanitizers can run before `rehypeWire`; a sanitizer after `remarkWire` needs an + SVG-aware schema. +- Rendering is synchronous and adds Wire compilation work to document builds or + server requests. +- Client-side updates are not automatic and remain outside this package. diff --git a/package.json b/package.json index b24f1a0..e96cdb1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "docs:format": "prettier --write \".github/**/*.yml\" \".github/*.md\" \"README.md\" \"CHANGELOG.md\" \"docs/EXAMPLES.md\" \"docs/brand/README.md\" \"packages/*/README.md\"", "skill:check": "skills add ./skills/wire-lang --list", "skill:publish": "skills add eduardozf/wire-lang --skill wire-lang --agent codex --global --copy --yes", - "pack:dry-run": "pnpm --filter @wire-lang/core pack --dry-run && pnpm --filter @wire-lang/cli pack --dry-run && pnpm --filter wire-lang pack --dry-run", + "pack:dry-run": "pnpm --filter @wire-lang/core pack --dry-run && pnpm --filter @wire-lang/cli pack --dry-run && pnpm --filter @wire-lang/markdown pack --dry-run && pnpm --filter wire-lang pack --dry-run", "package:smoke": "node scripts/package-smoke-test.mjs", "release:check": "pnpm lint && pnpm docs:check && pnpm typecheck && pnpm test && pnpm build && pnpm audit --prod && pnpm pack:dry-run && pnpm package:smoke", "examples:update": "node scripts/update-examples.mjs", diff --git a/packages/markdown/LICENSE b/packages/markdown/LICENSE new file mode 100644 index 0000000..4e19cba --- /dev/null +++ b/packages/markdown/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ferraz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/markdown/README.md b/packages/markdown/README.md new file mode 100644 index 0000000..3103175 --- /dev/null +++ b/packages/markdown/README.md @@ -0,0 +1,107 @@ +# `@wire-lang/markdown` + +Remark, rehype, and MDX plugins that turn fenced `wire` source into standalone +inline SVG with `@wire-lang/core`. + +## Install + +```bash +npm install @wire-lang/markdown +``` + +The package is ESM-only and requires Node.js 20 or newer. Install the unified +processor packages used by your documentation stack as usual. + +## Markdown with remark + +`remarkWire` transforms `wire` code nodes before Markdown becomes HTML. It +inserts structured HAST data, so the pipeline does not need raw HTML support. + +```js +import { remarkWire } from "@wire-lang/markdown"; +import rehypeStringify from "rehype-stringify"; +import remarkParse from "remark-parse"; +import remarkRehype from "remark-rehype"; +import { unified } from "unified"; + +const html = await unified() + .use(remarkParse) + .use(remarkWire) + .use(remarkRehype) + .use(rehypeStringify) + .process(markdown); +``` + +The default export at `@wire-lang/markdown/remark` is the same plugin: + +```js +import remarkWire from "@wire-lang/markdown/remark"; +``` + +## Rehype and MDX + +`rehypeWire` replaces the standard `pre > code.language-wire` HAST produced by +Markdown processors. Use it in an existing rehype pipeline: + +```js +import { rehypeWire } from "@wire-lang/markdown"; +import rehypeStringify from "rehype-stringify"; +import remarkParse from "remark-parse"; +import remarkRehype from "remark-rehype"; +import { unified } from "unified"; + +const html = await unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeWire) + .use(rehypeStringify) + .process(markdown); +``` + +Or pass it directly to the MDX compiler: + +```js +import { compile } from "@mdx-js/mdx"; +import { rehypeWire } from "@wire-lang/markdown"; + +const result = await compile(mdx, { + rehypePlugins: [rehypeWire], +}); +``` + +The default export at `@wire-lang/markdown/rehype` is also available. If the +pipeline sanitizes user-authored HTML, place the sanitizer before `rehypeWire` +so it checks untrusted input without stripping the trusted SVG generated by +Wire Lang. `remarkWire` runs earlier, so a later sanitizer needs an SVG-aware +schema. + +## Fence convention and errors + +Tag the fenced block with exactly `wire`: + +````markdown +```wire +schematic + component R1 Resistor value=220ohm + component D1 LED color=red + connect R1.1, D1.A +``` +```` + +Other fenced languages are left unchanged. A fatal Wire diagnostic fails the +document build as a VFile message. Its line and column point into the original +Markdown or MDX file, and the original `WireLangError` is available as the +message's `cause`. + +## Build-time and runtime rendering + +Build-time rendering is the supported default: run either plugin in the static +site, documentation, or MDX compilation pipeline. The generated page contains +standalone inline SVG, needs no Wire Lang JavaScript in the browser, and reports +invalid diagrams during the build. + +For request-time server rendering, run the same unified processor for each +request. Browser runtime auto-rendering is not part of this package; an +application can bundle `renderSvg` from `@wire-lang/core` and insert its result +itself, at the cost of shipping the compiler and renderer to clients. A +dedicated `@wire-lang/browser` auto-render integration remains planned. diff --git a/packages/markdown/package.json b/packages/markdown/package.json new file mode 100644 index 0000000..30154c9 --- /dev/null +++ b/packages/markdown/package.json @@ -0,0 +1,74 @@ +{ + "name": "@wire-lang/markdown", + "version": "0.3.0", + "type": "module", + "description": "Remark, rehype, and MDX plugins for rendering wire code fences as inline SVG.", + "keywords": [ + "wire-lang", + "schematic", + "markdown", + "mdx", + "remark-plugin", + "rehype-plugin", + "svg" + ], + "license": "MIT", + "author": "Ferraz ", + "homepage": "https://github.com/eduardozf/wire-lang#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/eduardozf/wire-lang.git", + "directory": "packages/markdown" + }, + "bugs": { + "url": "https://github.com/eduardozf/wire-lang/issues" + }, + "engines": { + "node": ">=20" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./remark": { + "types": "./dist/remark.d.ts", + "import": "./dist/remark.js" + }, + "./rehype": { + "types": "./dist/rehype.d.ts", + "import": "./dist/rehype.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "dist/**/*.js", + "dist/**/*.js.map", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@types/hast": "^3.0.5", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3", + "@wire-lang/core": "workspace:*", + "hast-util-from-html": "^2.0.3", + "unified": "^11.0.5", + "vfile": "^6.0.3" + }, + "devDependencies": { + "@mdx-js/mdx": "^3.1.1", + "rehype-stringify": "^10.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2" + }, + "scripts": { + "build:js": "tsup" + } +} diff --git a/packages/markdown/src/index.ts b/packages/markdown/src/index.ts new file mode 100644 index 0000000..4573f53 --- /dev/null +++ b/packages/markdown/src/index.ts @@ -0,0 +1,2 @@ +export { rehypeWire } from "./rehype-wire.js"; +export { remarkWire } from "./remark-wire.js"; diff --git a/packages/markdown/src/rehype-wire.ts b/packages/markdown/src/rehype-wire.ts new file mode 100644 index 0000000..5313f27 --- /dev/null +++ b/packages/markdown/src/rehype-wire.ts @@ -0,0 +1,62 @@ +import type { Element, Nodes, Parents, Root } from "hast"; +import type { Plugin } from "unified"; +import type { VFile } from "vfile"; +import { renderWireElement } from "./render-wire.js"; + +function isElement(node: Nodes, tagName: string): node is Element { + return node.type === "element" && node.tagName === tagName; +} + +function classNames(element: Element): string[] { + const value: unknown = element.properties.className; + if (Array.isArray(value)) { + return value.map(String); + } + return typeof value === "string" ? value.split(/\s+/u) : []; +} + +function wireCodeBlock(node: Nodes): Element | undefined { + if (!isElement(node, "pre")) { + return undefined; + } + + const significantChildren = node.children.filter( + (child) => child.type !== "text" || child.value.trim() !== "", + ); + if (significantChildren.length !== 1 || !isElement(significantChildren[0], "code")) { + return undefined; + } + + const code = significantChildren[0]; + return classNames(code).includes("language-wire") ? code : undefined; +} + +function textContent(node: Nodes): string { + if (node.type === "text") { + return node.value; + } + if ("children" in node) { + return node.children.map(textContent).join(""); + } + return ""; +} + +function transformChildren(parent: Parents, file: VFile): void { + for (let index = 0; index < parent.children.length; index += 1) { + const child = parent.children[index]; + const code = wireCodeBlock(child); + if (code) { + const source = textContent(code).replace(/\n$/u, ""); + parent.children[index] = renderWireElement(source, child, file); + } else if ("children" in child) { + transformChildren(child, file); + } + } +} + +/** Replace HAST `pre > code.language-wire` blocks with inline SVG. */ +export const rehypeWire: Plugin<[], Root> = function rehypeWire() { + return (tree, file) => { + transformChildren(tree, file); + }; +}; diff --git a/packages/markdown/src/rehype.ts b/packages/markdown/src/rehype.ts new file mode 100644 index 0000000..e3177ec --- /dev/null +++ b/packages/markdown/src/rehype.ts @@ -0,0 +1 @@ +export { rehypeWire as default, rehypeWire } from "./rehype-wire.js"; diff --git a/packages/markdown/src/remark-wire.ts b/packages/markdown/src/remark-wire.ts new file mode 100644 index 0000000..e28c616 --- /dev/null +++ b/packages/markdown/src/remark-wire.ts @@ -0,0 +1,62 @@ +import type { ElementContent, Properties } from "hast"; +import type { Code, Parents, Root } from "mdast"; +import type { Plugin } from "unified"; +import type { Literal, Position } from "unist"; +import type { VFile } from "vfile"; +import { renderWireElement } from "./render-wire.js"; + +interface WireDiagramData { + hName: "svg"; + hProperties: Properties; + hChildren: ElementContent[]; +} + +interface WireDiagram extends Literal { + type: "wireDiagram"; + value: string; + data: WireDiagramData; + position?: Position | undefined; +} + +declare module "mdast" { + interface RootContentMap { + wireDiagram: WireDiagram; + } +} + +function isWireFence(node: Parents["children"][number]): node is Code { + return node.type === "code" && node.lang?.split(/\s+/u)[0] === "wire"; +} + +function wireDiagram(node: Code, file: VFile): WireDiagram { + const svg = renderWireElement(node.value, node, file); + + return { + type: "wireDiagram", + value: node.value, + data: { + hName: "svg", + hProperties: svg.properties, + hChildren: svg.children, + }, + position: node.position, + }; +} + +function transformChildren(parent: Parents, file: VFile): void { + for (let index = 0; index < parent.children.length; index += 1) { + const child = parent.children[index]; + if (isWireFence(child)) { + parent.children[index] = wireDiagram(child, file); + } else if ("children" in child) { + transformChildren(child, file); + } + } +} + +/** Render Markdown code fences tagged `wire` to inline SVG HAST nodes. */ +export const remarkWire: Plugin<[], Root> = function remarkWire() { + return (tree, file) => { + transformChildren(tree, file); + }; +}; diff --git a/packages/markdown/src/remark.ts b/packages/markdown/src/remark.ts new file mode 100644 index 0000000..ed15190 --- /dev/null +++ b/packages/markdown/src/remark.ts @@ -0,0 +1 @@ +export { remarkWire as default, remarkWire } from "./remark-wire.js"; diff --git a/packages/markdown/src/render-wire.ts b/packages/markdown/src/render-wire.ts new file mode 100644 index 0000000..f3f4809 --- /dev/null +++ b/packages/markdown/src/render-wire.ts @@ -0,0 +1,79 @@ +import { renderSvg, WireLangError } from "@wire-lang/core"; +import type { Element, Nodes } from "hast"; +import { fromHtml } from "hast-util-from-html"; +import type { Position } from "unist"; +import type { VFile } from "vfile"; + +interface PositionedFence { + readonly position?: Position | undefined; +} + +function stripGeneratedPositions(node: Nodes): void { + delete node.position; + if ("children" in node) { + for (const child of node.children) { + stripGeneratedPositions(child); + } + } +} + +function diagnosticPosition( + fence: PositionedFence, + error: WireLangError, +): Position | null | undefined { + const diagnostic = + error.diagnostics.find((candidate) => candidate.severity === "error") ?? error.diagnostics[0]; + const range = diagnostic?.range; + const fenceStart = fence.position?.start; + + if (!range || !fenceStart) { + return fence.position; + } + + return { + start: { + line: fenceStart.line + range.start.line, + column: range.start.column, + }, + end: { + line: fenceStart.line + range.end.line, + column: range.end.column, + }, + }; +} + +function failDocumentBuild(error: WireLangError, fence: PositionedFence, file: VFile): never { + const diagnostic = + error.diagnostics.find((candidate) => candidate.severity === "error") ?? error.diagnostics[0]; + + file.fail(diagnostic?.message ?? error.message, { + cause: error, + place: diagnosticPosition(fence, error), + ruleId: diagnostic?.code ?? "render", + source: "wire-lang", + }); +} + +export function renderWireElement(source: string, fence: PositionedFence, file: VFile): Element { + let svg: string; + try { + svg = renderSvg(source); + } catch (error) { + if (error instanceof WireLangError) { + failDocumentBuild(error, fence, file); + } + throw error; + } + + const tree = fromHtml(svg, { fragment: true }); + const element = tree.children.find( + (child): child is Element => child.type === "element" && child.tagName === "svg", + ); + if (!element) { + throw new Error("Wire Lang rendered an invalid SVG fragment."); + } + + stripGeneratedPositions(element); + element.position = fence.position; + return element; +} diff --git a/packages/markdown/test/markdown.test.ts b/packages/markdown/test/markdown.test.ts new file mode 100644 index 0000000..621524f --- /dev/null +++ b/packages/markdown/test/markdown.test.ts @@ -0,0 +1,92 @@ +import { compile } from "@mdx-js/mdx"; +import rehypeStringify from "rehype-stringify"; +import remarkParse from "remark-parse"; +import remarkRehype from "remark-rehype"; +import { unified } from "unified"; +import { describe, expect, it } from "vitest"; +import { rehypeWire, remarkWire } from "../src/index.js"; + +const MARKDOWN = `# LED + +\`\`\`wire +schematic + title "LED current limiter" + component R1 Resistor value=220ohm + component D1 LED color=red + connect R1.1, D1.A +\`\`\` +`; + +describe("remarkWire", () => { + it("renders wire fences as inline SVG without enabling raw HTML", async () => { + const file = await unified() + .use(remarkParse) + .use(remarkWire) + .use(remarkRehype) + .use(rehypeStringify) + .process(MARKDOWN); + + const html = String(file); + expect(html).toContain("

LED

"); + expect(html).toContain(""); + }); + + it("leaves other fenced languages unchanged", async () => { + const file = await unified() + .use(remarkParse) + .use(remarkWire) + .use(remarkRehype) + .use(rehypeStringify) + .process("```js\nconst wire = true;\n```\n"); + + expect(String(file)).toBe('
const wire = true;\n
'); + }); + + it("fails the document build at the Wire diagnostic's Markdown location", async () => { + const processor = unified() + .use(remarkParse) + .use(remarkWire) + .use(remarkRehype) + .use(rehypeStringify); + + await expect( + processor.process(`# Broken + +\`\`\`wire +schematic + component X1 Flux +\`\`\` +`), + ).rejects.toMatchObject({ + line: 5, + ruleId: "component.unknown-type", + source: "wire-lang", + }); + }); +}); + +describe("rehypeWire", () => { + it("renders standard pre > code.language-wire HAST blocks", async () => { + const file = await unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeWire) + .use(rehypeStringify) + .process(MARKDOWN); + + const html = String(file); + expect(html).toContain(" { + const compiled = await compile(MARKDOWN, { rehypePlugins: [rehypeWire] }); + + expect(String(compiled)).toContain('"data-wire-lang-version": "0.3.0"'); + expect(String(compiled)).not.toContain("language-wire"); + }); +}); diff --git a/packages/markdown/tsconfig.json b/packages/markdown/tsconfig.json new file mode 100644 index 0000000..c14fac5 --- /dev/null +++ b/packages/markdown/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src"], + "references": [{ "path": "../core" }] +} diff --git a/packages/markdown/tsup.config.ts b/packages/markdown/tsup.config.ts new file mode 100644 index 0000000..f842250 --- /dev/null +++ b/packages/markdown/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts", "src/remark.ts", "src/rehype.ts"], + format: ["esm"], + target: "node20", + platform: "node", + outDir: "dist", + dts: false, + sourcemap: true, + // tsc -b emits the .d.ts files into dist; do not wipe them here. + clean: false, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 727d548..4483657 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,43 @@ importers: packages/core: {} + packages/markdown: + dependencies: + '@types/hast': + specifier: ^3.0.5 + version: 3.0.5 + '@types/mdast': + specifier: ^4.0.4 + version: 4.0.4 + '@types/unist': + specifier: ^3.0.3 + version: 3.0.3 + '@wire-lang/core': + specifier: workspace:* + version: link:../core + hast-util-from-html: + specifier: ^2.0.3 + version: 2.0.3 + unified: + specifier: ^11.0.5 + version: 11.0.5 + vfile: + specifier: ^6.0.3 + version: 6.0.3 + devDependencies: + '@mdx-js/mdx': + specifier: ^3.1.1 + version: 3.1.1 + rehype-stringify: + specifier: ^10.0.1 + version: 10.0.1 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + remark-rehype: + specifier: ^11.1.2 + version: 11.1.2 + packages/wire-lang: dependencies: '@wire-lang/cli': @@ -296,6 +333,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -630,15 +670,42 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@26.1.1': resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.2': + resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} + '@vitest/expect@4.1.10': resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} @@ -668,6 +735,11 @@ packages: '@vitest/utils@4.1.10': resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn@8.17.0: resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} @@ -680,6 +752,13 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -698,14 +777,35 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -729,18 +829,56 @@ packages: supports-color: optional: true + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -748,6 +886,9 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -769,6 +910,52 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -858,6 +1045,9 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lru-cache@11.5.1: resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} engines: {node: 20 || >=22} @@ -865,6 +1055,121 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -898,6 +1203,12 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} @@ -946,10 +1257,42 @@ packages: engines: {node: '>=14'} hasBin: true + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -985,12 +1328,24 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} @@ -1025,6 +1380,12 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -1061,6 +1422,36 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite@8.1.3: resolution: {integrity: sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1145,6 +1536,9 @@ packages: jsdom: optional: true + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -1155,6 +1549,9 @@ packages: engines: {node: '>= 14.6'} hasBin: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@biomejs/biome@2.5.3': @@ -1300,6 +1697,36 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdx': 2.0.14 + acorn: 8.17.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.17.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -1498,14 +1925,40 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + '@types/estree@1.0.9': {} + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.14': {} + + '@types/ms@2.1.0': {} + '@types/node@26.1.1': dependencies: undici-types: 8.3.0 + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.2': {} + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 @@ -1547,12 +2000,20 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + acorn@8.17.0: {} any-promise@1.3.0: {} assertion-error@2.0.1: {} + astring@1.9.0: {} + + bail@2.0.2: {} + balanced-match@4.0.4: {} brace-expansion@5.0.6: @@ -1566,12 +2027,26 @@ snapshots: cac@6.7.14: {} + ccount@2.0.1: {} + chai@6.2.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 + collapse-white-space@2.1.0: {} + + comma-separated-tokens@2.0.3: {} + commander@4.1.1: {} confbox@0.1.8: {} @@ -1584,10 +2059,36 @@ snapshots: dependencies: ms: 2.1.3 + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + detect-libc@2.1.2: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + entities@6.0.1: {} + es-module-lexer@2.1.0: {} + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.17.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -1617,12 +2118,43 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.9 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 expect-type@1.3.0: {} + extend@3.0.2: {} + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -1642,6 +2174,114 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.5 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.2.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + + html-void-elements@3.0.0: {} + + inline-style-parser@0.2.7: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-hexadecimal@2.0.1: {} + + is-plain-obj@4.1.0: {} + joycon@3.1.1: {} lightningcss-android-arm64@1.32.0: @@ -1699,12 +2339,321 @@ snapshots: load-tsconfig@0.2.5: {} + longest-streak@3.1.0: {} + lru-cache@11.5.1: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-extensions@2.0.0: {} + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.2 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.9 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 @@ -1734,6 +2683,20 @@ snapshots: package-json-from-dist@1.0.1: {} + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-scurry@2.0.2: dependencies: lru-cache: 11.5.1 @@ -1768,8 +2731,77 @@ snapshots: prettier@3.9.4: {} + property-information@7.2.0: {} + readdirp@4.1.2: {} + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.9 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + resolve-from@5.0.0: {} rimraf@6.1.3: @@ -1839,10 +2871,25 @@ snapshots: source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + stackback@0.0.2: {} std-env@4.1.0: {} + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -1876,6 +2923,10 @@ snapshots: tree-kill@1.2.2: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + ts-interface-checker@0.1.13: {} tslib@2.8.1: @@ -1915,6 +2966,58 @@ snapshots: undici-types@8.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite@8.1.3(@types/node@26.1.1)(esbuild@0.28.1)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 @@ -1955,9 +3058,13 @@ snapshots: transitivePeerDependencies: - msw + web-namespaces@2.0.1: {} + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 yaml@2.9.0: {} + + zwitch@2.0.4: {} diff --git a/scripts/package-smoke-test.mjs b/scripts/package-smoke-test.mjs index 49dae10..3835f34 100644 --- a/scripts/package-smoke-test.mjs +++ b/scripts/package-smoke-test.mjs @@ -27,6 +27,9 @@ async function packAll(packDir) { await run(runner, ["--filter", "@wire-lang/cli", "pack", "--pack-destination", packDir], { cwd: repoRoot, }); + await run(runner, ["--filter", "@wire-lang/markdown", "pack", "--pack-destination", packDir], { + cwd: repoRoot, + }); await run(runner, ["--filter", "wire-lang", "pack", "--pack-destination", packDir], { cwd: repoRoot, }); @@ -62,6 +65,7 @@ async function main() { "--no-fund", tarball(packDir, `wire-lang-core-${version}.tgz`), tarball(packDir, `wire-lang-cli-${version}.tgz`), + tarball(packDir, `wire-lang-markdown-${version}.tgz`), tarball(packDir, `wire-lang-${version}.tgz`), ], { cwd: consumerDir }, @@ -93,13 +97,52 @@ if (!svg.startsWith(" { diff --git a/tsconfig.json b/tsconfig.json index fd13e7a..1d67b0d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "references": [ { "path": "./packages/core" }, { "path": "./packages/cli" }, + { "path": "./packages/markdown" }, { "path": "./packages/wire-lang" } ] } From 044a2954d583e1e1d13ebadd8b2859448abd57e5 Mon Sep 17 00:00:00 2001 From: Ferraz Date: Thu, 9 Jul 2026 20:10:29 -0300 Subject: [PATCH 2/3] fix: map diagnostic columns through fence indentation - offset Wire diagnostic columns by the fence marker's column so errors in fences nested in lists or blockquotes point at the right character - look up the primary diagnostic once in render-wire - cover the rehypeWire failure path and indented fences in tests - make the MDX version assertion survive release bumps - note the remark-stringify limitation in the README --- packages/markdown/README.md | 5 +++ packages/markdown/src/render-wire.ts | 25 ++++++++----- packages/markdown/test/markdown.test.ts | 49 ++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/packages/markdown/README.md b/packages/markdown/README.md index 3103175..9d20a3d 100644 --- a/packages/markdown/README.md +++ b/packages/markdown/README.md @@ -38,6 +38,11 @@ The default export at `@wire-lang/markdown/remark` is the same plugin: import remarkWire from "@wire-lang/markdown/remark"; ``` +`remarkWire` is for pipelines that continue into HTML. A pipeline that +serializes back to Markdown with `remark-stringify` cannot serialize the +replaced diagram node; keep the fence intact there and render later with +`rehypeWire` instead. + ## Rehype and MDX `rehypeWire` replaces the standard `pre > code.language-wire` HAST produced by diff --git a/packages/markdown/src/render-wire.ts b/packages/markdown/src/render-wire.ts index f3f4809..5260e13 100644 --- a/packages/markdown/src/render-wire.ts +++ b/packages/markdown/src/render-wire.ts @@ -1,3 +1,4 @@ +import type { Diagnostic, SourceRange } from "@wire-lang/core"; import { renderSvg, WireLangError } from "@wire-lang/core"; import type { Element, Nodes } from "hast"; import { fromHtml } from "hast-util-from-html"; @@ -17,38 +18,44 @@ function stripGeneratedPositions(node: Nodes): void { } } +function primaryDiagnostic(error: WireLangError): Diagnostic | undefined { + return ( + error.diagnostics.find((candidate) => candidate.severity === "error") ?? error.diagnostics[0] + ); +} + function diagnosticPosition( fence: PositionedFence, - error: WireLangError, + range: SourceRange | null | undefined, ): Position | null | undefined { - const diagnostic = - error.diagnostics.find((candidate) => candidate.severity === "error") ?? error.diagnostics[0]; - const range = diagnostic?.range; const fenceStart = fence.position?.start; if (!range || !fenceStart) { return fence.position; } + // The fence content starts one line below the opening fence marker and, when + // the fence sits inside a list or blockquote, shares the marker's + // indentation, which Markdown strips from the Wire source. + const columnOffset = fenceStart.column - 1; return { start: { line: fenceStart.line + range.start.line, - column: range.start.column, + column: columnOffset + range.start.column, }, end: { line: fenceStart.line + range.end.line, - column: range.end.column, + column: columnOffset + range.end.column, }, }; } function failDocumentBuild(error: WireLangError, fence: PositionedFence, file: VFile): never { - const diagnostic = - error.diagnostics.find((candidate) => candidate.severity === "error") ?? error.diagnostics[0]; + const diagnostic = primaryDiagnostic(error); file.fail(diagnostic?.message ?? error.message, { cause: error, - place: diagnosticPosition(fence, error), + place: diagnosticPosition(fence, diagnostic?.range), ruleId: diagnostic?.code ?? "render", source: "wire-lang", }); diff --git a/packages/markdown/test/markdown.test.ts b/packages/markdown/test/markdown.test.ts index 621524f..ffe627c 100644 --- a/packages/markdown/test/markdown.test.ts +++ b/packages/markdown/test/markdown.test.ts @@ -62,6 +62,30 @@ schematic `), ).rejects.toMatchObject({ line: 5, + column: 16, + ruleId: "component.unknown-type", + source: "wire-lang", + }); + }); + + it("offsets diagnostic columns by the fence indentation", async () => { + const processor = unified() + .use(remarkParse) + .use(remarkWire) + .use(remarkRehype) + .use(rehypeStringify); + + await expect( + processor.process(`- Broken + + \`\`\`wire + schematic + component X1 Flux + \`\`\` +`), + ).rejects.toMatchObject({ + line: 5, + column: 18, ruleId: "component.unknown-type", source: "wire-lang", }); @@ -83,10 +107,33 @@ describe("rehypeWire", () => { expect(html).not.toContain("language-wire"); }); + it("fails the document build at the Wire diagnostic's Markdown location", async () => { + const processor = unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeWire) + .use(rehypeStringify); + + await expect( + processor.process(`# Broken + +\`\`\`wire +schematic + component X1 Flux +\`\`\` +`), + ).rejects.toMatchObject({ + line: 5, + column: 16, + ruleId: "component.unknown-type", + source: "wire-lang", + }); + }); + it("works as an MDX rehype plugin", async () => { const compiled = await compile(MARKDOWN, { rehypePlugins: [rehypeWire] }); - expect(String(compiled)).toContain('"data-wire-lang-version": "0.3.0"'); + expect(String(compiled)).toMatch(/"data-wire-lang-version": "\d+\.\d+\.\d+"/u); expect(String(compiled)).not.toContain("language-wire"); }); }); From c9f467eb77b9fd69da57c0b8f20b4a78770dd98b Mon Sep 17 00:00:00 2001 From: Ferraz Date: Sun, 6 Sep 2026 11:04:19 -0300 Subject: [PATCH 3/3] feat: default to asynchronous browser rendering with static opt-in --- .github/workflows/publish.yml | 2 +- CHANGELOG.md | 6 +- README.md | 38 ++- docs/CONTEXT.md | 15 +- docs/MVP.md | 74 +++-- docs/adr/0016-browser-auto-render-post-mvp.md | 4 + .../0019-build-time-markdown-integration.md | 4 +- .../adr/0020-browser-default-static-opt-in.md | 43 +++ package.json | 2 +- packages/browser/LICENSE | 21 ++ packages/browser/README.md | 80 +++++ packages/browser/package.json | 54 +++ packages/browser/src/index.ts | 123 +++++++ packages/browser/test/browser.test.ts | 119 +++++++ packages/browser/tsconfig.json | 21 ++ packages/browser/tsup.config.ts | 14 + packages/markdown/README.md | 123 +++---- packages/markdown/src/index.ts | 1 + packages/markdown/src/options.ts | 4 + packages/markdown/src/rehype-wire.ts | 7 +- packages/markdown/src/remark-wire.ts | 7 +- packages/markdown/test/markdown.test.ts | 38 ++- pnpm-lock.yaml | 313 +++++++++++++++++- scripts/package-smoke-test.mjs | 21 +- scripts/pin-workspace-deps.mjs | 8 +- skills/wire-lang/SKILL.md | 7 +- skills/wire-lang/references/boundaries.md | 10 +- skills/wire-lang/references/markdown.md | 62 ++-- tsconfig.json | 19 +- 29 files changed, 1065 insertions(+), 175 deletions(-) create mode 100644 docs/adr/0020-browser-default-static-opt-in.md create mode 100644 packages/browser/LICENSE create mode 100644 packages/browser/README.md create mode 100644 packages/browser/package.json create mode 100644 packages/browser/src/index.ts create mode 100644 packages/browser/test/browser.test.ts create mode 100644 packages/browser/tsconfig.json create mode 100644 packages/browser/tsup.config.ts create mode 100644 packages/markdown/src/options.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9a36bb7..efe38e2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -99,6 +99,6 @@ jobs: set -euo pipefail node scripts/pin-workspace-deps.mjs tag="${{ inputs.npm_tag || 'latest' }}" - for pkg in core cli markdown wire-lang; do + for pkg in core cli browser markdown wire-lang; do ( cd "packages/$pkg" && npm publish --provenance --access public --tag "$tag" ) done diff --git a/CHANGELOG.md b/CHANGELOG.md index afcff9c..f7a15ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ All notable changes to Wire Lang will be documented in this file. ## Unreleased -- Added `@wire-lang/markdown` with remark, rehype, and MDX plugins that render - `wire` fenced code blocks as inline SVG. +- Added `@wire-lang/browser` for asynchronous rendering after HTML loads, with + source preservation, repeatable discovery, and per-block errors. +- Added `@wire-lang/markdown` with remark, rehype, and MDX plugins that preserve + `wire` fences by default and render inline SVG with `{ mode: "static" }`. ## 0.3.1 diff --git a/README.md b/README.md index acc6c49..bf712ea 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,7 @@ import { renderSvg } from "wire-lang"; const svg = renderSvg(source); ``` -Render the same source directly from a Markdown or MDX fence with the build-time -plugins in `@wire-lang/markdown`: +Write the same source inside a Markdown or MDX fence: ````markdown ```wire @@ -81,13 +80,25 @@ schematic ```` ```bash -npm install @wire-lang/markdown +npm install @wire-lang/browser ``` -Use `remarkWire` in a remark pipeline or `rehypeWire` in a rehype/MDX pipeline. -Both replace the fence with standalone inline SVG while the document is built; -see the [`@wire-lang/markdown` guide](./packages/markdown) for complete setup and -runtime tradeoffs. +Initialize the renderer in your site's browser entry point: + +```js +import wire from "@wire-lang/browser"; + +const { errors } = await wire.initialize(); +for (const { error } of errors) console.error(error); +``` + +The default workflow renders diagrams asynchronously after HTML is ready. +Your Markdown processor must preserve `pre > code.language-wire` blocks. See +the [browser guide](./packages/browser) for setup and client navigation. + +For ahead-of-time rendering with no browser runtime, use `remarkWire` or +`rehypeWire` from `@wire-lang/markdown` with `{ mode: "static" }`. See the +[Markdown/MDX guide](./packages/markdown) for complete build configuration. ## How it compares @@ -192,12 +203,13 @@ symbol art; it does not claim formal IEC/IEEE compliance. See ### Packages & development -| Package | Role | -| -------------------------------------------- | ---------------------------------------------------------- | -| [`wire-lang`](./packages/wire-lang) | User-facing aggregate package and the `wire` binary | -| [`@wire-lang/core`](./packages/core) | Parser, compiler, schematic model, layout engine, renderer | -| [`@wire-lang/cli`](./packages/cli) | `wire check`, `wire render`, `wire watch` | -| [`@wire-lang/markdown`](./packages/markdown) | Remark, rehype, and MDX build-time integration | +| Package | Role | +| -------------------------------------------- | ----------------------------------------------------------- | +| [`wire-lang`](./packages/wire-lang) | User-facing aggregate package and the `wire` binary | +| [`@wire-lang/core`](./packages/core) | Parser, compiler, schematic model, layout engine, renderer | +| [`@wire-lang/cli`](./packages/cli) | `wire check`, `wire render`, `wire watch` | +| [`@wire-lang/browser`](./packages/browser) | Asynchronous rendering after HTML loads | +| [`@wire-lang/markdown`](./packages/markdown) | Markdown and MDX integration with optional static rendering | ```bash pnpm install diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md index af4f8ee..68f6e5b 100644 --- a/docs/CONTEXT.md +++ b/docs/CONTEXT.md @@ -273,7 +273,7 @@ The public happy-path API that turns a **Source Document** or **Schematic Model* _Avoid_: CLI command, simulation API **DOM Auto Render**: -A post-MVP browser integration that finds Wire Lang source blocks in the DOM and replaces or augments them with rendered schematics. +The `@wire-lang/browser` integration that asynchronously finds source blocks after HTML loads, hides successfully rendered source, and inserts sibling SVG containers. This is the default documentation workflow. _Avoid_: MVP command-line rendering, manual API usage **DOM Source Block**: @@ -421,9 +421,10 @@ A fenced code block tagged as `wire` for documentation systems that integrate Wi _Avoid_: DOM source block, wire file **Markdown Integration**: -The build-time `@wire-lang/markdown` plugins that replace a **Markdown Fence** -with standalone inline SVG through remark, rehype, or MDX. -_Avoid_: browser auto-render, raw HTML passthrough, Mermaid plugin +The `@wire-lang/markdown` plugins that preserve a **Markdown Fence** for +**DOM Auto Render** by default. Explicit static mode replaces it with inline SVG +ahead of time through remark, rehype, or MDX. +_Avoid_: raw HTML passthrough, Mermaid plugin **VS Code Extension**: A future editor integration for Wire Lang syntax highlighting, diagnostics, and authoring feedback. @@ -824,7 +825,7 @@ Terminals are user-defined through recommended `pins: ic-pin-list` written as `p > **Domain expert:** "No. The **Developer CLI** should show **Human Diagnostic Output** by default and provide **Machine Diagnostic Output** with JSON for agents and scripts." > > **Dev:** "How does browser auto-render find Wire Lang diagrams?" -> **Domain expert:** "After the MVP, **DOM Auto Render** should find **DOM Source Blocks** matching `pre.wire-lang` and `code.wire-lang`." +> **Domain expert:** "**DOM Auto Render** finds **DOM Source Blocks** matching `pre > code.language-wire`, `pre.wire-lang`, and `code.wire-lang`." > > **Dev:** "Should auto-render destroy the original source block?" > **Domain expert:** "No. It should preserve the **DOM Source Block** and insert a separate **DOM Render Container** for the SVG." @@ -886,8 +887,8 @@ Terminals are user-defined through recommended `pins: ic-pin-list` written as `p - The default file extension for a **Wire File** is `.wire`. - **Wire Files** are UTF-8; Unicode is allowed in values, labels, and annotations. - The recommended **Markdown Fence** tag is `wire`. Direct integration is - outside the core MVP and is implemented by the build-time - `@wire-lang/markdown` **Markdown Integration** package. + outside the core MVP. **DOM Auto Render** is the default; the + `@wire-lang/markdown` **Markdown Integration** package also offers static rendering. - Syntax highlighting and editor extensions are outside the MVP; a **Headless Language Server** and **VS Code Extension** are high-priority post-MVP follow-ups. - A **Wire File** contains exactly one **Source Document** in the MVP. - The MVP supports `//` **Line Comments** and does not need block comments. diff --git a/docs/MVP.md b/docs/MVP.md index e089aa4..df947ef 100644 --- a/docs/MVP.md +++ b/docs/MVP.md @@ -365,12 +365,12 @@ Initial MVP packages: Implemented post-MVP packages: -- `@wire-lang/markdown`: remark, rehype, and MDX build-time plugins for the - `wire` fenced code tag +- `@wire-lang/browser`: asynchronous DOM discovery and rendering after HTML loads +- `@wire-lang/markdown`: remark, rehype, and MDX plugins that preserve `wire` + fences by default and render SVG ahead of time with `{ mode: "static" }` Post-MVP packages: -- `@wire-lang/browser` - `@wire-lang/language-server` - editor integrations such as a VS Code extension @@ -615,37 +615,54 @@ Standard symbols use an IEC-style visual profile where practical. Wire Lang does ## Post-MVP Browser Auto Render -Browser auto-render is outside the MVP. The planned post-MVP browser integration finds source blocks by default: +`@wire-lang/browser` implements the default documentation workflow after HTML +is ready. It exports asynchronous `initialize()`, `run()`, and `render(source)` +functions and a default object containing those functions. Imports have no DOM +side effects. `initialize()` defaults to `startOnLoad: true`, waits for +`DOMContentLoaded` when necessary, and runs once. `startOnLoad: false` leaves +rendering to explicit `run()` calls. + +Default discovery matches: ```css -pre.wire-lang, code.wire-lang +pre > code.language-wire, pre.wire-lang, code.wire-lang ``` -`run()` should preserve the original source block and insert a separate rendered container. It should be idempotent by default: - -```ts -await run() -await run() // does not duplicate output -await run({ force: true }) // may re-render explicitly -``` +`run({ root?, selector?, force? })` reads source as text, hides successfully +rendered source blocks, and inserts a sibling `div.wire-lang-diagram` with SVG. +It preserves the original source. Repeated and concurrent calls do not duplicate +output. Changed source or `force: true` triggers rendering again. A supplied root +is included if it matches. Discovery does not cross shadow roots automatically. + +Calls resolve to `{ rendered, errors }`; errors retain the source element and +original exception. Invalid blocks remain available as source with a +`data-wire-error` attribute, while other diagrams continue. Failed rerenders +remove stale SVG and restore the source's previous hidden state. Invalid +selectors or missing DOM context reject the call. Applications decide how to +present returned errors and call `run()` after navigation or DOM updates. MDX +applications must run after hydration to avoid competing with the framework. + +`render(source)` asynchronously returns the same SVG string as core's +`renderSvg(source)`. Work yields to the event loop before each diagram, but +compilation and layout of an individual diagram remain on the main thread. +The standalone browser ESM bundle includes core and needs no import map. ## Post-MVP Markdown and MDX Integration -`@wire-lang/markdown` provides `remarkWire` and `rehypeWire`. Both recognize the -`wire` fenced code tag, render its source with `@wire-lang/core`, and replace the -source block with a structured inline `` element. `rehypeWire` also works as -an MDX rehype plugin. - -The supported default is build-time rendering: invalid source fails the document -build with a diagnostic mapped into the Markdown file, and the generated page -does not ship the Wire compiler or renderer to the browser. Diagnostic range -endpoints account for indentation and container markers removed from each -content line, using the original Markdown stored in the VFile. If the original -text is unavailable or no longer matches the code, columns fall back to the -opening fence indentation. The same processor -can run at request time on a server. Client-side runtime replacement is not part -of this package; applications may call `renderSvg` directly, while automatic DOM -discovery remains the planned `@wire-lang/browser` responsibility. +`@wire-lang/markdown` provides `remarkWire` and `rehypeWire`, accepting +`{ mode?: "browser" | "static" }`. Browser mode is the default and preserves +source nodes for the browser runtime. Standard Markdown HTML already contains +`pre > code.language-wire`, so that workflow needs no Wire Markdown plugin. + +Static mode renders source with core and replaces the fence with structured +inline SVG nodes. It requires no Wire JavaScript in the generated page and works +in builds or server requests. Static `rehypeWire` also works as an MDX plugin. +Fatal diagnostics fail static processing and map back into the Markdown VFile. +Range endpoints account for the prefix removed from each original content line. +If VFile text is missing or no longer matches, columns fall back to the opening +fence indentation. Browser diagnostics are relative to the source block instead. + +See [ADR 0020](./adr/0020-browser-default-static-opt-in.md) for the default change. ## Post-MVP Roadmap @@ -655,7 +672,6 @@ High-priority follow-ups: `anchor`, and `side` for components and groups, plus group-aware layout (today these emit `render.not-yet-honored` / `group.not-yet-honored` warnings). Per-component `orientation` is now honored. -- browser auto-render for `pre.wire-lang` and `code.wire-lang` - headless language server using a Langium grammar and the existing validators - VS Code extension with syntax highlighting, diagnostics, and authoring feedback - custom component libraries passed through the JavaScript API @@ -690,4 +706,4 @@ Later extensions: - [ADR 0015](./adr/0015-no-preview-server-in-mvp.md) records the MVP preview decision. - [ADR 0016](./adr/0016-browser-auto-render-post-mvp.md) records the browser auto-render scope decision. - [ADR 0017](./adr/0017-esm-only-node-20.md) records the runtime and module-format decision. -- [ADR 0019](./adr/0019-build-time-markdown-integration.md) records the build-time Markdown/MDX integration decision. +- [ADR 0019](./adr/0019-build-time-markdown-integration.md) records the original static Markdown integration, superseded in part by ADR 0020. diff --git a/docs/adr/0016-browser-auto-render-post-mvp.md b/docs/adr/0016-browser-auto-render-post-mvp.md index 70a86ec..f437af1 100644 --- a/docs/adr/0016-browser-auto-render-post-mvp.md +++ b/docs/adr/0016-browser-auto-render-post-mvp.md @@ -1,3 +1,7 @@ # Browser Auto Render Post-MVP Wire Lang's MVP will not ship browser auto-render or `@wire-lang/browser`; the first release focuses on the core library and developer CLI feedback loop. We chose this over including Mermaid-style `run()` immediately because the initial user workflow is coding-agent driven: write `.wire`, run `wire check`, run `wire render`, and inspect the SVG file; browser auto-render remains a high-priority post-MVP integration once the language, diagnostics, layout, and renderer stabilize. + +The post-MVP integration is now implemented. See +[ADR 0020](./0020-browser-default-static-opt-in.md) for the browser default and +static rendering alternative. diff --git a/docs/adr/0019-build-time-markdown-integration.md b/docs/adr/0019-build-time-markdown-integration.md index ea1c35d..c138c03 100644 --- a/docs/adr/0019-build-time-markdown-integration.md +++ b/docs/adr/0019-build-time-markdown-integration.md @@ -2,7 +2,9 @@ ## Status -Accepted. +Superseded in part by [ADR 0020](./0020-browser-default-static-opt-in.md). +Structured SVG and static diagnostic mapping are retained; browser rendering +is now the default and static mode is explicit. ## Context diff --git a/docs/adr/0020-browser-default-static-opt-in.md b/docs/adr/0020-browser-default-static-opt-in.md new file mode 100644 index 0000000..5dac94e --- /dev/null +++ b/docs/adr/0020-browser-default-static-opt-in.md @@ -0,0 +1,43 @@ +# ADR 0020: Browser rendering by default with static rendering opt-in + +## Status + +Accepted. Supersedes the build-time default in ADR 0019 and implements the +post-MVP browser scope from ADR 0016. + +## Context + +Authors want to place `wire` fences in Markdown and have a site render them +after HTML loads, following Mermaid's browser integration style. Static output +remains useful for pages that need diagrams without client JavaScript. + +## Decision + +Provide `@wire-lang/browser` with asynchronous `initialize`, `run`, and `render` +APIs. Initialization waits for HTML readiness and discovers standard Markdown +code blocks plus explicit `wire-lang` blocks. Preserve source in hidden elements +and insert separate rendered containers. Serialize discovery calls so overlapping +initialization and manual calls cannot duplicate output. Report individual block +errors without stopping the remaining diagrams. + +Keep core's synchronous, DOM-independent renderer. The browser wrapper yields +before rendering each diagram and bundles core into standalone ESM. This makes +script-tag imports possible without a bundler, but does not move layout work +into a worker. Imports do not initialize automatically. + +Markdown plugins default to browser mode and preserve source nodes. Users opt +into ahead-of-time SVG with `{ mode: "static" }`. Retain structured SVG insertion +and original Markdown diagnostic mapping for that static path. + +## Consequences + +- Browser mode requires site initialization and downloads the renderer. Without + JavaScript, source blocks remain visible. +- Ordinary Markdown processors need no Wire plugin when they retain standard + `language-wire` blocks. Plugin configuration alone never injects a script. +- Applications call `run()` after navigation or content updates, and after + framework hydration. Mutation observation and framework lifecycle adapters + are not included. +- Static mode adds compilation work during the build and needs no client runtime. +- Source errors surface during browser rendering by default, or during the build + in static mode. The underlying SVG drawing is identical in both modes. diff --git a/package.json b/package.json index 44be717..34c973a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "docs:format": "prettier --write \".github/**/*.yml\" \".github/*.md\" \"README.md\" \"CHANGELOG.md\" \"docs/EXAMPLES.md\" \"docs/brand/README.md\" \"packages/*/README.md\"", "skill:check": "skills add ./skills/wire-lang --list", "skill:publish": "skills add eduardozf/wire-lang --skill wire-lang --agent codex --global --copy --yes", - "pack:dry-run": "pnpm --filter @wire-lang/core pack --dry-run && pnpm --filter @wire-lang/cli pack --dry-run && pnpm --filter @wire-lang/markdown pack --dry-run && pnpm --filter wire-lang pack --dry-run", + "pack:dry-run": "pnpm --filter @wire-lang/core pack --dry-run && pnpm --filter @wire-lang/cli pack --dry-run && pnpm --filter @wire-lang/browser pack --dry-run && pnpm --filter @wire-lang/markdown pack --dry-run && pnpm --filter wire-lang pack --dry-run", "package:smoke": "node scripts/package-smoke-test.mjs", "release:check": "pnpm lint && pnpm docs:check && pnpm typecheck && pnpm test && pnpm build && pnpm audit --prod && pnpm pack:dry-run && pnpm package:smoke", "examples:update": "node scripts/update-examples.mjs", diff --git a/packages/browser/LICENSE b/packages/browser/LICENSE new file mode 100644 index 0000000..4e19cba --- /dev/null +++ b/packages/browser/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ferraz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/browser/README.md b/packages/browser/README.md new file mode 100644 index 0000000..352e70b --- /dev/null +++ b/packages/browser/README.md @@ -0,0 +1,80 @@ +# `@wire-lang/browser` + +Render Wire Lang diagrams asynchronously after HTML is ready. This is the +default documentation workflow. To generate SVG ahead of time instead, use +[`@wire-lang/markdown`](../markdown) with `{ mode: "static" }`. + +## Install and initialize + +```bash +npm install @wire-lang/browser +``` + +Keep fenced `wire` blocks in your Markdown. Most Markdown processors produce +`
...
` for these blocks. +Load this code in your site's browser entry point: + +```js +import wire from "@wire-lang/browser"; + +const result = await wire.initialize(); +for (const { element, error } of result.errors) { + console.error("Could not render Wire diagram", element, error); +} +``` + +`initialize()` waits for `DOMContentLoaded` if necessary, then renders once. +Importing the package does not start rendering. No Markdown plugin is required +when your processor already emits the standard `language-wire` class. + +The published `dist/index.js` bundles the core renderer and has no external +JavaScript imports. You can also serve that file from your site and import it +in a `