* build: update dependencies Update the AI SDK providers, Vue, Reka UI, Valibot, Zod, es-toolkit, CodeMirror, Storybook, Playwright, Hono and other dependencies to their current releases, consistently across workspaces. The Tauri plugin packages must match their Rust crates, and the new plugin crates require Tauri 2.12, so Cargo.lock, @tauri-apps/api and the Tauri CLI move to 2.12 as well. * build(harness): update the AI SDK harness packages @ai-sdk/harness 1.0.74 pinned ai 7.0.67, so the workspace carried a second copy of ai next to the root one; 1.0.138 depends on the same ai release. The Pi adapter no longer takes a model: HarnessAgent does. The settings were spread from untyped records, so the compiler could not reject the stale key and the chosen model would have been dropped; they are plain literals now. PiAuthOptions is now PiAuthenticationMode, and auth accepts an environment record. Pass the gateway key that way instead of writing it into the process-wide environment while a session is created. Derive the thinking level from the adapter's settings, which adds 'max'. * build: hold vue-tsc at 3.3.11 vue-tsc 3.3.12 no longer sees a v-slot binding inside a component that also has an event listener, so check:vue reports "Cannot find name 'control'" in MCPConnectionEditor and ProfileEditor. 3.3.11 checks them cleanly. * fix(ai): keep retryability for provider errors reported mid-stream From ai 7.0.80 a provider error after the response stream starts is a StreamProviderError rather than an APICallError, so classifyAIChatError lost its isRetryable. * feat(desktop): accept updates only when signed for their version Tauri CLI 2.12 records the app version in each updater signature, and updater 2.13 checks it against the version latest.json announces. With requireSignedVersion it also rejects signatures that carry no version, so a tampered manifest cannot pair a newer version number with an older, still validly signed bundle. Release assembly now fails when a signature does not name the version being released, instead of shipping one that installed apps would reject. * docs: note the dependency update's security fixes in the changelog * feat(ai): recommend the latest models The provider packages now know Claude Sonnet 5.5 and Opus 5.5 and the GPT-6 series. Make Sonnet 5.5 and GPT-6.1 Sol the defaults, list Opus 5.5, Fable 5.1, GPT-6 Astra and GPT-6 Luna, and replace the two free OpenRouter models that OpenRouter no longer serves. * build: align the fig package's valibot with the workspace |
||
|---|---|---|
| .. | ||
| scripts | ||
| src | ||
| tests | ||
| AGENTS.md | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.test.json | ||
| tsdown.config.ts | ||
@open-pencil/dom-css
DOM and CSS projection utilities for OpenPencil.
This package is the compatibility layer between OpenPencil's scene graph and DOM-shaped design documents. It is intentionally separate from @open-pencil/core so browser/CSS parser integrations can evolve without adding DOM dependencies to the renderer and editor core.
Installation
bun add @open-pencil/dom-css @open-pencil/core
@open-pencil/core is a peer dependency because @open-pencil/dom-css projects to and from OpenPencil scene graphs. Consumers that only parse/serialize DesignDOM still need the peer installed for the package entrypoint.
Package-local checks
This package can be validated independently from the app shell:
cd packages/dom-css
bun run test
bun run typecheck
bun run check
Package scripts:
bun run test— package-local Bun tests for runtime, conversion, and Tailwind APIsbun run typecheck— type-checkssrc, tests, and package scriptsbun run build— builds the distributabledistentrypointbun run smoke:dist— imports the builtdistbundle and exercises the public APIbun run check— runs typecheck, tests, build, and dist smoke in sequence
The repository also keeps integration/oracle coverage under tests/engine/dom-css and tests/e2e/dom-css. The package-local suite focuses on the library's public API, while the repo-level E2E suite verifies browser getComputedStyle() parity through Playwright.
Runtime model
Use the browser runtime as the high-fidelity source of truth whenever a DOM is available. It uses native parsing and getComputedStyle() inside an isolated sandbox. Prefer sandbox: 'iframe' for production-style conversion because it isolates authored CSS from host-page styles:
import { createBrowserCSSRuntime } from '@open-pencil/dom-css'
const runtime = createBrowserCSSRuntime({ sandbox: 'iframe' })
const document = runtime.parseHTML('<article class="card">OpenPencil</article>')
const styled = await runtime.computeStyles(document, '.card { width: calc(10rem + 16px); }')
The headless runtime is useful for Bun/Node tests, CLI flows, and fast approximate conversion. It supports common selectors, inheritance, shorthands, CSSOM grouping rules, and simple variable/calc values, but it is not a browser replacement. Do not use it as an oracle for browser-only CSS behavior such as layout-dependent computed values, full custom-property fallback behavior, modern color serialization, or UA defaults. Do not expand headless CSS parsing with ad hoc regex/string parsers; add a maintained parser/runtime dependency or use the browser runtime instead.
import { createHeadlessCSSRuntime } from '@open-pencil/dom-css'
const runtime = createHeadlessCSSRuntime()
HTML/CSS to scene graph
The convenience helpers run the full pipeline:
import { htmlToSceneGraph } from '@open-pencil/dom-css'
const graph = await htmlToSceneGraph(
'<article class="card"><h1>OpenPencil</h1></article>',
{
cssText: '.card { display: flex; gap: 12px; width: 320px; padding: 24px; }'
}
)
For DesignDOM output without creating a scene graph, use htmlToDesignDocument().
JSX/DOM authoring
Use the package as a JSX import source when you want DOM-shaped authoring that still flows through DesignDOM, CSSOM, and SceneGraph conversion:
/** @jsxImportSource @open-pencil/dom-css */
import { createBrowserCSSRuntime, jsxToSceneGraph } from '@open-pencil/dom-css'
const graph = await jsxToSceneGraph(
<article class="card">
<h1>OpenPencil</h1>
</article>,
{
cssText: '.card { display: flex; width: 320px; padding: 24px; }',
runtime: createBrowserCSSRuntime({ sandbox: 'iframe' })
}
)
The JSX runtime preserves class, attributes, inline style, text, fragments, and simple function components as DesignDOM. Class semantics still come from generated or authored CSS passed to a CSS runtime; the JSX layer does not interpret Tailwind or CSS utility names directly.
When running in a browser, use the browser-first helpers so native getComputedStyle() is used automatically. Import them from @open-pencil/dom-css/browser so browser bundles do not load headless-only CSSOM dependencies:
/** @jsxImportSource @open-pencil/dom-css */
import { browserJSXToSceneGraph } from '@open-pencil/dom-css/browser'
const graph = await browserJSXToSceneGraph(
<article class="card">
<h1>OpenPencil</h1>
</article>,
{
cssText: '.card { display: flex; width: 320px; padding: 24px; }',
sandbox: 'iframe'
}
)
Use browserJSXToDesignDocument() / browserJSXToSceneGraph() for authored CSS. Use browserTailwindJSXToDesignDocument() / browserTailwindJSXToSceneGraph() only when Tailwind utilities should be compiled at runtime by the host app. Browser Tailwind compilation needs the host bundler to provide Tailwind source CSS or stylesheet loading; see the Tailwind recipes below.
Tailwind pipeline
Tailwind classes flow through Tailwind's own compiler, then through the CSS runtime. Prefer the browser helpers when a document is available so custom properties, calc(), modern colors, and browser-default behavior come from native getComputedStyle().
Browser recipe: precompiled Tailwind CSS
The most portable browser path is to compile Tailwind CSS in the host app, then pass the resulting CSS as normal cssText:
import { browserHTMLToSceneGraph } from '@open-pencil/dom-css/browser'
import tailwindCSS from './generated-tailwind.css?raw'
const graph = await browserHTMLToSceneGraph(
'<article class="flex w-80 rounded-xl bg-white p-6">OpenPencil</article>',
{
cssText: tailwindCSS,
sandbox: 'iframe'
}
)
This also works with JSX:
/** @jsxImportSource @open-pencil/dom-css */
import { browserJSXToSceneGraph } from '@open-pencil/dom-css/browser'
import tailwindCSS from './generated-tailwind.css?raw'
const graph = await browserJSXToSceneGraph(
<article class="flex w-80 rounded-xl bg-white p-6">OpenPencil</article>,
{
cssText: tailwindCSS,
sandbox: 'iframe'
}
)
Browser recipe: runtime Tailwind compilation with supplied CSS
If the app wants to compile utility candidates at runtime, provide Tailwind source CSS yourself. Do not rely on the default Node-oriented stylesheet loader in browser bundles:
import { browserTailwindHTMLToSceneGraph } from '@open-pencil/dom-css/browser'
const classes = ['flex', 'w-80', 'rounded-xl', 'bg-white', 'p-6']
const graph = await browserTailwindHTMLToSceneGraph(
`<article class="${classes.join(' ')}">OpenPencil</article>`,
classes,
{
css: await fetch('/tailwind-source.css').then((response) => response.text()),
sandbox: 'iframe'
}
)
Use loadStylesheet when the supplied Tailwind CSS contains imports and the host app owns import resolution:
import { browserTailwindHTMLToSceneGraph } from '@open-pencil/dom-css/browser'
const classes = ['flex', 'w-80', 'rounded-xl', 'bg-white', 'p-6']
const graph = await browserTailwindHTMLToSceneGraph(
`<article class="${classes.join(' ')}">OpenPencil</article>`,
classes,
{
css: '@import "tailwindcss";',
loadStylesheet: async (id) => {
const url = id === 'tailwindcss' ? '/tailwindcss/index.css' : `/tailwindcss/${id}`
return fetch(url).then((response) => response.text())
},
sandbox: 'iframe'
}
)
Bun/Node recipe
In Bun or Node, the package can load Tailwind's default stylesheet through filesystem-backed module resolution. Without a DOM, this uses the headless CSS runtime; pass a browser runtime only when the process has a real document available, such as Playwright or a browser extension page:
import { tailwindHTMLToSceneGraph } from '@open-pencil/dom-css'
const classes = ['flex', 'w-80', 'p-6', 'rounded-xl', 'bg-white']
const graph = await tailwindHTMLToSceneGraph(
`<article class="${classes.join(' ')}">OpenPencil</article>`,
classes
)
compileTailwindCSS() remains available when callers want to manage CSS compilation and runtime selection themselves.
Current scope
- DOM-shaped
DesignDocument/DesignElementtypes - Browser-backed runtime adapter for native HTML parsing, serialization, and computed-style extraction
- Headless runtime adapter with
parse5HTML parsing and CSSOM-backed style computation for basic selectors, nested CSSOM rules, cascade order, inheritance, common shorthands, and simple custom-property/calc values - SceneGraph ⇄ DesignDOM conversion for simple HTML/CSS-shaped layouts, including flex alignment/wrapping, self alignment, absolute positioning basics, logical padding, independent side borders, constraints, clipping, opacity, typography, and shadows
- JSX runtime helpers for DOM-shaped authoring into DesignDOM and SceneGraph
- Tailwind v4 compiler adapter
- Browser oracle fixtures for CSS variables,
calc(), sandboxed browser-runtime output, modern color output, JSX/Tailwind browser helpers, and Tailwind utility output
Roadmap
- Expand reusable fixtures: inputs, badges, nav/menu rows, dialog shells, and richer cards
- Map more computed CSS properties to scene graph fields through browser-native computed style or dependency-backed parsers: richer shadows, typography details, position constraints, borders, gradients, and grid once OpenPencil's grid support matures
- Improve SceneGraph → CSS export so generated HTML/CSS is useful for JSX, Tailwind, and web export
- Keep
@open-pencil/dom-cssstable before splitting lower-level file-format packages such as future@open-pencil/kiwiand@open-pencil/fig