diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index de26a9d..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 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 c6d6f1c..f7a15ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to Wire Lang will be documented in this file. +## Unreleased + +- 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 - Fixed dense connections between facing IC pin banks so each net uses a clear, diff --git a/README.md b/README.md index d9bb3df..bf712ea 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,38 @@ import { renderSvg } from "wire-lang"; const svg = renderSvg(source); ``` +Write the same source inside a Markdown or MDX fence: + +````markdown +```wire +schematic + component R1 Resistor value=220ohm + component D1 LED color=red + connect R1.1, D1.A +``` +```` + +```bash +npm install @wire-lang/browser +``` + +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 Think **[Mermaid Charts](https://github.com/mermaid-js/mermaid), but for electronic schematics**: text goes in, documentation- @@ -171,11 +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` | +| 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 d6bcf3a..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**: @@ -420,6 +420,12 @@ _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 `@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. _Avoid_: MVP core library, SVG renderer @@ -819,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." @@ -880,7 +886,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. **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 0f82107..df947ef 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,9 +363,14 @@ 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/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 @@ -609,19 +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`, 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 @@ -631,10 +672,8 @@ 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 -- Markdown/MDX integrations using the `wire` fenced code tag - custom component libraries passed through the JavaScript API Later extensions: @@ -667,3 +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 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 new file mode 100644 index 0000000..c138c03 --- /dev/null +++ b/docs/adr/0019-build-time-markdown-integration.md @@ -0,0 +1,50 @@ +# ADR 0019: Build-time Markdown integration with structured inline SVG + +## Status + +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 + +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/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 77c8c7e..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 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 `