Project/更新日志
先登记表,再归档。
登记表反映当前各包的 npm dist-tags。下方归档原样渲染仓库 CHANGELOG.md;机器校验的发布真值见 docs/release/release-state.json。
当前各包的 npm latest 分别为: element v0.43.3 · create v0.43.3 · ui v0.43.3 · router v0.41.0-alpha.6。
本项目遵循 Keep a Changelog 与 SemVer。历史条目在描述旧版本时保留旧名称;当前文档使用 openElement 契约。
不存在共同完整版本:element、create、ui 在 0.43.3,而 router 的 latest 是 0.41.0-alpha.6 预发布。
稳定线
稳定维护线仅覆盖 @openelement/element、@openelement/create、@openelement/ui。@openelement/router 没有 0.43.x;其 npm latest 是 0.41.0 预发布。没有任何单一稳定版本覆盖全部四个包。静态、请求时与 Universal WC SSR 契约继续受 ADR-0119、ADR-0122 和 ADR-0135 冻结;ADR-0140 允许兼容 patch,但不预排 0.44 功能列车。
已撤回的残缺产物
npm 上 0.41.0 时代的 beta.1–beta.3 产物——在 0.41 线正式版之前发布——是已撤回的残缺发布:既非受支持的产品线,也不构成升级路径。dist-tag beta 上的 v0.44.0-beta.2.2 预发布同样是残缺发布:element、create、ui 已发布,Router 从未发布,因此它不是四包版本。
※ 已撤回的 0.41.0 时代 npm beta.1–beta.3 残缺产物在活跃发布叙事中保持撤回状态。历史被保留,不被改写。
以下内容原样渲染自仓库 CHANGELOG.md。当前发布真值由 docs/release/release-state.json 机器校验;当前 npm dist-tags 摘要见上方登记表。
归档正文以英文原文发布(English original)。
All notable user-visible changes are recorded here, newest first. This is an aggregated historical archive, not a release-by-release record; details for individual versions are recoverable from Git history. Current product truth lives in:
docs/architecture/product-model.mddocs/release/release-state.jsondocs/release/public-interface-snapshot.json
1.0.0-alpha.9
The styling blood-swap: one @theme token source, opt-in Tailwind seams,
components on shadcn roles, and an accessible site. Open Props is removed
entirely (ten linked sites, no compatibility layer); @openelement/ui
guarantees the role table's SHAPE — role names, dark pairs, a forced-colors
tier — and embeds no scale values at all: values come from the Router's
Tailwind preset (ON) or the consumer's own sheet (OFF), per
CUSTOMIZATION.md "Value delivery". An intentional pixel break, not a
migration.
- @theme single source (#1504).
@openelement/ui/theme.css: 24 shadcn-convention roles seated on Tailwind v4 default scales (0 authored scale values); the retired alias layer is deleted whole; the OP sweep leaves only negative guards. - Opt-in Tailwind preset + two seams (#1505).
@tailwindcss/vite4.3.3 enters the Router Vite seam as an optional peer, default OFF (preset-OFF builds are hash-proven byte-identical). When ON: component styles compile into the declared@layer theme, base, components, utilitiesorder, and global style reaches the document and every DSD shadow template as<link>— the full-inlinestyleText()delivery fails closed with stable error codes. - Components on roles (#1506). All twelve ui components restyled on the
role sheet; the customization surface is a declared semver contract
(
CUSTOMIZATION.mdships in the package) guarded to never shrink against alpha.8 (parts) and never drift from what components consume. - Accessible site + interaction (#1507). The docs site's open-search
combobox runs on Zag (1.43.3) with Floating UI positioning (full keyboard
- ARIA e2e); an axe gate is a permanent resident of the site e2e chain;
focus skeleton and the per-primitive decision record
(
INTERACTION-PRIMITIVES.md) land together. Static components gained no mandatory client JavaScript.
- ARIA e2e); an axe gate is a permanent resident of the site e2e chain;
focus skeleton and the per-primitive decision record
(
- Canonical bootstrap.
npm create @openelement@alpha <name>becomes the documented one-liner (npm's init alias maps the scope), canonized in the single source, READMEs, generator, and an ADR-0161 note. - Release engineering. The version-bump task drives all ten stamp
points plus generated outputs in one command; the transitional manual
retires. Dialog exit animation rides
allow-discrete+@starting-style. The element runtime/compiler split is evaluated and DEFERRED to alpha10 with measured evidence (ADR-0162). - Post-train audit consolidation (#1521/#1522). The Tailwind preset's DSD link injection becomes opt-in (default off — the compiled claim requires template children to match the Part Program exactly; the head link already delivers theme custom properties across shadow boundaries), and the dogfood e2e now walks the preset-default path. The site's open-search dialog gains session-epoch invalidation (close during first load stays closed; in-flight Pagefind rounds cannot write back; a failed first load retries). Doc claims align with reality: the SaaS stack reads as qualification-pending, i18n examples are runnable, build phase order and DSD/light-DOM defaults match the implementation, absolute deployment/paint claims are qualified.
- Tailwind-on starter by default (#1524). One Enter buys element compile + router fullstack + Tailwind: the default scaffold pins tailwindcss/@tailwindcss/vite, wires the router preset, and carries a starter-local @theme role sheet (zero authored scale values). --no-tailwind keeps the minimal form; both forms are standing, machine-tested consumer worlds, and getting-started documents the element-as-library path. The whole workspace stamps one version (www and the tooling packages had drifted two releases behind).
- Router hardening (audit ride-alongs). node-http cancels unconsumed
bodies on HEAD/204/304; bind failures answer with actionable one-liners;
package-scan errors separate "not installed" from "no ./manifest
subpath" (new
OE_PACKAGE_ISLAND_PACKAGE_MISSING).
1.0.0-alpha.8
Platform train: the repository and its consumer surface move from the Deno
host to Node/pnpm; product behavior stays put. Every Deno.* runtime API in
product and tooling source ports to node:*, the manifests consolidate into
one pnpm workspace with a single lockfile, the test runner becomes vitest
everywhere, the CI gate surface runs on Node, and the @openelement/create
starter becomes a plain Node/pnpm project (ADR-0161, superseding the
Deno-native distribution decision in ADR-0108).
Highlights
- Host ports (B1a/B1b). Product source (router, create), the fixtures,
tools/repoevidence machinery, www site tooling, and the SaaS app lose everyDeno.*API in favor ofnode:fs,node:process,node:child_process, and friends; the ports-and-adapters bridge and its gate are deleted outright. A node-porting residuals inventory tracks what the port left behind and each residue's expiry path (release/qualify tooling, #1387). - One workspace, one lockfile (B2). All 19
deno.jsonmanifests becomepackage.jsonmembers of a single pnpm workspace (pnpm-workspace.yaml, onepnpm-lock.yaml); all 7deno.lockfiles and the vendor/nodeModulesDirmachinery retire. Package truth (name, version, exports, deps, publish files) is read from package.json by the package-graph, release-state, version-bump, and alias tooling, and the packed alpha.7 manifests are byte-identical to the published artifacts. The JSR publish config is dropped; npm stays the only publish channel. - vitest everywhere (B3). All Deno.test suites migrate to vitest, including the browser mode suites; the runner surface is a vitest workspace with per-project configs, and a codemod script documents the migration.
- CI on the Node/pnpm task surface (B4). The workflow gates run
pnpm/nodetasks (fmt via oxfmt, lint via oxlint, tsc typecheck, vitest, gate.ts),.dvmrcretires in favor of.node-version(24.18 development line), and a dependency-age supply-chain gate runs over the pnpm lockfile. - Consumer surface closeout (B5, ADR-0161). The generated starter is a
plain Node/pnpm project: exact
@openelement/*dependency pins and pnpm lifecycle scripts inpackage.json(the Deno import map and its task surface retire), a generatedtsconfig.jsonbehindpnpm check, andnode --testas the starter test runner. The documented create bootstrap is thenpm exec @openelement/create@alphainvocation — the Deno consumer surface retired with the host, so nothing in the consumer path needs a Deno install. Runtime floors are stated per verified fact: Node.js 24+ for generated projects (the packed engines' floor, CI-exercised). The packed-starter consumer qualifications and the starter smoke drive the starter's own pnpm scripts (dev/check/test/build/start/preview), the preview CLI spawns the app's own vite on a Node host, and the guides' install/build commands (README, getting-started, tutorial, deployment, testing, zh counterparts) teach the pnpm face.
Validation
pnpm run check(fmt + lint + typecheck + markdown lint) green on this tree.packages/createvitest project green, including the packed-CLI template smoke (vp pack dry-run → node-run packed CLI → generated-starter shape).- Site content gates green:
check:content-data,check:install-command,check:content(guide examples type-checked against framework sources after the Node test-runner rewrite).
1.0.0-alpha.7
vp toolchain train: the release toolchain swaps engines; product behavior
stays put. The packed-artifact generator moves from deno pack to the
pinned vp pack pipeline, the format/lint engines move from deno fmt / deno
lint to oxfmt 0.70.0 + oxlint 1.85.0 with a one-time repo-wide reformat, and
the element compiler's JSX text whitespace handling is re-based onto the
exact React contract. The source line is 1.0.0-alpha.7; npm publication is
a separate, gated step, so
docs/release/release-state.json keeps
registry truth at the verified alpha.6 state (@alpha = 1.0.0-alpha.6)
until the post-publish sync.
Highlights
- Packed-artifact generator:
deno pack→vp pack(#1496). The npm tarballs for all four packages are produced by the pinnedvite-plusvp packpipeline with explicit client-runtime entries,fixedExtension: falseandtreeshake: false, replacing thedeno packinvocation. The packed-artifact pin gates were re-baselined to the vp output (re-baseline #1), and the vp pipeline emits per-package pack summaries for the candidate-evidence recorder. - Format/lint engines: deno fmt / deno lint → oxfmt + oxlint (#1497).
deno task fmt/fmt:check/lintnow run oxfmt 0.70.0 and oxlint 1.85.0 as pinned npm deps. The former deno_lint effective set (86 rules) was mapped with zero silent loss — 66 rules verbatim, 8 under oxlint names, 2 option-tuned, 12 documented unmapped — and the task-contract checks now pin the ox engines, rejecting any task that shells out to the retireddeno fmt/deno lint. A one-time repo-wide JS/TS reformat (570 files, pure format diff) re-baselined the tree (re-baseline #2); format-coupled surfaces (packages/element/__fixtures__/,tests/fixtures/*/app/) keep their audited bytes via oxfmt ignores. The markdown leg is transitionally ungated by the formatter; markdownlint-cli2 still gates prose. - JSX text whitespace follows the React contract (#1498). The element
compiler's JSX text lowering — formerly "collapse every whitespace run to
one space" — now implements the
cleanJSXElementLiteralChildrules the React toolchain itself applies (node-boundary stripping, interior newline-folding, sibling-delimiting-run removal). Multiline JSX layout produced by React-family formatters (oxfmt/Prettier included) now serializes identically to the inline layout (40⏎<span>4</span>renders404, not40 4), removing the formatter-versus-renderer coupling the A2 reformat had to work around. - Pre-train debt riders (#1495). Server-runtime and build-time errors
are cataloged with stable OEC codes (25 codes across 128 call sites,
rendered into the generated error reference); element's
preUpgradeCapturesare regrouped by root to stop page-lifetime retention; the island media-query bound is shared instead of forked; the retired content-dates writer ritual and dead dev triggers were dropped from the repo docs and git hooks.
Compatibility
- This is an alpha prerelease. Alpha trains do not carry a stable
compatibility promise; breaking changes between alphas are possible, and
npm
lateststays on the stable 0.43 line. The@alphadist-tag still resolves to1.0.0-alpha.6until this train is published. - Packed artifact bytes changed. The vp generator re-cuts the published
tarball internals (
fixedExtension: false, no treeshake); anything pinning packed artifact hashes or sizes must re-baseline against this train's output. No public API changed; the public-interface snapshot is untouched. - JSX whitespace semantics changed at the edges. Source whose rendered output relied on the old collapse-everything rule may differ where newline-separated inline siblings previously gained an inter-node space (React semantics remove it). Formatted-source layout is now rendering-inert.
deno fmt/deno lintare retired as repo engines. One formatting delta against pre-alpha7 trees is expected;.oxfmtrc.jsoncarries the former deno style (printWidth 100, spaces, single quotes).
Validation
Run on this candidate branch (alpha7-toolchain):
- version-bump six-point consistency at
1.0.0-alpha.7(dry run reviewed, then applied with--write, which regenerates the four fixture locks viafixtures:locks:update). release:state-machine:checkgreen offline and against the live registry (read-only npm); the release-state suite 12 tests green.deno task --cwd tools/repo generate:all(9 generators) green and drift-free;deno task check(oxfmt --check, oxlint, typecheck, markdownlint) green.- Element suite 442 tests and Router suite 974 tests green.
1.0.0-alpha.6
Architecture-debt repayment: a typed server runtime, one serializer kernel,
and manifest-driven client asset injection. This train restructures internal
seams — generated-entry growth, serializer forks, compiler/Router coupling,
chunk-name-derived identity, release-name leakage into artifacts — under
ADR-0160 (stages S0–S6,
Amendments 1–3). The source line is 1.0.0-alpha.6; npm publication is a
separate, gated step, so
docs/release/release-state.json keeps
registry truth at the verified alpha.5 state (@alpha = 1.0.0-alpha.5)
until the post-publish sync.
Highlights
- Typed server runtime (#1470). The request-time semantics that lived
inside generated-entry template strings — response-header channel with
protocol-header precedence and multi-value
Set-Cookie, the streamed route's late-mutation gate, CSP auto-nonce, the page/document/render seams, the action POST protocol (CSRF floor, named-action dispatch, RFC 9457 problem documents, PRG, body limit, 303 coercion) and the streaming pump — moved into typecheckable modules exported from the public@openelement/router/server-runtimesubpath. Generated entries are reduced to imports, route-descriptor data and wiring, enforced by a dedicated gate (standalone parse, no runtime bodies in codegen, client graph never reaching server runtime). - One serializer kernel (#1469). The server serializer and the runtime
seed serializer — two parallel Part Program tree walkers — collapsed into
one host-free kernel (
serialize-program.ts) with every execution-mode difference an explicit seam. A differential parity harness replayed the full corpus through both implementations requiring byte-identical output before the legacy walkers were deleted. - Manifest-driven client asset injection (#1471). A
ClientAssetManifestprotocol joins island declarations with the Phase 2 build manifest and Rollup module metadata; a chunk is matched by the module ids it contains, never by parsing chunk file names. Build order becomes SSR → client → SSG, script tags are serialized at document time from one resolvedclientScriptsfield, and the post-build HTML-rewriting injection pass is deleted. Package-island identity is exact-match through the same import map and alias table the build ships; each package island ships as its ownisland-<tag>-<hash>.jschunk. - Compiler decoupled from Router (ADR-0160 rule c). Island admission and
module-scan vocabulary became injected plain-data descriptors; the default
semantic core admits none and fails closed. The dead
defineIslandvocabulary entries were removed with recorded forensics. - Artifacts carry protocol versions, not release names (rule e). Shipped
source no longer carries the retired 0.23/0.40/0.42/0.44 trains; streaming
admission budgets are named once in an import-free policy module; the
Site's version truth derives from
release-state.jsonand is cross-asserted at bump time and by the offline release gate. - Single-duty module splits (#1473). The 2,779-line compiled Part
Program executor, the 2,381-line compile facade and the 999-line Vite
plugin decomposed into single-duty modules (every moved block
byte-verified against the pre-split file); the
ParserPortseam records where a future parser backend swaps in. - ui: instance state and experimental tiers.
readInstanceState/writeInstanceStatejoin the public surface (barrel re-export plus a side-effect-free./instance-statesubpath). The ui manifest now records a per-class status and the API reference renders it. - Qualification harness (#1472). The scaffold → workspace-alias → build
→ static-serve → Playwright ritual that three harnesses implemented
privately moved to
tests/lib/qualify-harness/; the candidate-evidence producer split into single-duty record, fresh-clone, aggregate and validate modules.
Fixes
- Client asset manifest fails closed (Amendment 3). Exactly one manifest
record may claim the client entry — two or more fail
OE_CLIENT_ASSET_ENTRY_AMBIGUOUSnaming every candidate. One delivery tag is owned by exactly one island entry — a second claimant failsOE_CLIENT_ASSET_ISLAND_TAG_DUPLICATEwhatever asset either side would resolve to. The SSG join throwsOE_CLIENT_ASSET_ISLAND_UNMAPPEDfor an admitted island with no manifest record, and the pass writes the complete per-page manifest set or nothing. A missing, malformed or entry-less client manifest and an admitted island resolving to no asset are namedOE_CLIENT_ASSET_*failures instead of warn-and-continue or a silent empty entry; the postprocessor validates only the selected (islandTagNames) metadata, so an unselected island cannot fail the join. - Workspace package islands resolve through the alias table. A
workspace-member specifier such as the Site's
@openelement/ui/open-buttonresolves to its real module path (import map, thenresolve.aliaswith @rollup/plugin-alias matching semantics) and joins by exact identity; the pre-repair build silently mis-attributed those islands to the client entry chunk. - A streamed-seed type mismatch has its own code. The mismatch in
connectedCallbackreportsOE_STREAM_TYPE_MISMATCHinstead of reusingOE_PROGRAM_MISSING, so a host can tell seed contract drift from a missing compiled program. - SSG stays nonce-free. The CSP auto-nonce is gated off hono/ssg prerender passes.
Compatibility
- This is an alpha prerelease. Alpha trains do not carry a stable
compatibility promise; breaking changes between alphas are possible, and
npm
lateststays on the stable 0.43 line. The@alphadist-tag still resolves to1.0.0-alpha.5until this train is published. - Regenerate build output. Generated entries and manifests changed shape
in this train — generated server entries import the typed runtime (native
server bytes moved 27507 → 14231, lit 25562 → 14344; client entries only
gained the serialized idle-fallback constant),
dist/server/client-script.jsbecameclient-assets.js, and package island chunks are re-cut and namedisland-<tag>-<hash>.js. Rebuild rather than reusing pre-alpha6disttrees. - Invalid or ambiguous island declarations now fail the build. Silent
warn-and-continue paths are gone: a missing client entry, an unrecorded or
ambiguously-owned island tag, and an admitted island with no asset are
errors. A build that was previously green by silently mis-attributing a
package island will now fail with a named
OE_CLIENT_ASSET_*code. - Six ui components are experimental.
open-card,open-callout,open-dialog,open-dropdown,open-tabsandopen-inputcarry no compatibility promise until each has standalone adoption evidence; the other four ui components are pinned stable. - Public surface additions.
@openelement/router/server-runtime(the generated entries' request-time runtime, documented in the Router README), the ui./instance-statesubpath,ClientScriptDescriptor/ResolvedDocument.clientScripts,ModuleSemanticsOptions/ModuleVocabularyDescriptor, and theSTREAM_*policy constants on the element authoring leaf. No prior public export was removed in this train.
Validation
Run on this candidate branch (release/1.0.0-alpha.6):
deno task --cwd tools/repo release:state-machine:check(offline) andrelease:registry-check(read-only npm) — both green; the six version points verified consistent at1.0.0-alpha.6.- Element suite 429 tests and Router suite 970 tests green; the release-state suite 12 tests green.
deno task fmt,deno task check(fmt:check, lint, typecheck, markdownlint) andtools/repo#generate:allgreen and drift-free.- Release-train static steps green, including every generator, boundary,
provenance and link check, the Site build with all
wwwconsistency checks (doc figures re-baselined against the alpha6 build), coverage thresholds, the static-only and light-probe fixtures, and the Nitro Node and Workers proofs. - Browser and packed qualification on this branch: the three-engine
fixture browser gates (native, Lit, ui dogfood), the three-engine
Element conformance matrix, starter-smoke across three engines, the
packed-tarball consumer gates at
1.0.0-alpha.6(lit renderer app green on all 9 cells; Router route/framework modes; element and ui packed consumers), and the npm publish dry-run. The full three-engine Site E2E suite and the remaining train steps are carried by the merged train's exact-SHA CI evidence (#1474) and re-verified by the release dispatch on the final candidate SHA before anything publishes.
1.0.0-alpha.5
Rendered as data resolves: part-level streaming, WC admission tiers, runtime
convergence. ADR-0157/0158/0159 are accepted (2026-09-25 owner ruling). The
qualification evidence boundary — including honest negatives (T1 offline
snapshot falsified, third-party components verified shell-born only) — is
recorded in docs/release/alpha5-qualification.md.
- Element / Router — rendered as data resolves: opted-in dynamic document GETs may defer eligible compiled text/Region Parts after status, security, headers and Cookie decisions. The same Part Program still drives SSR, fresh DOM and claim; server Part backfill is independent of island hydration. Actions and non-opted-in/static routes retain their non-streaming paths.
- Element — ownership and keyed identity: a single internal tree-shaped lifetime scope owns component activations and child Part/Region resources; it preserves DOM on disconnect and removes owned ranges on replacement. Keyed reorders retain stationary node identity/focus and use bounded moves. Old Part Program material is retained; no cross-alpha migration is implied.
- Router — internal renderer selection: Native/Lit imports and hydration ownership converge behind an internal adapter without a public renderer plug-in SDK or a third rendering path.
- Evidence, not support expansion: the Native reference route exercises a delayed Part, late failure, no-JS tail and ordinary POST action against a non-streamed comparison route. Local raw measurements are advisory. The WC snapshot experiment does not grant Lit-in-stream or T1/T2; a Workers module call is not a real deployment. Bun remains advisory.
1.0.0-alpha.4
Self-consistency train: the shipped package manifest stops lying about a
module that installs itself, the candidate gate stops re-proving content it has
already proved, and the config, error and navigation surfaces are converged.
The source line is 1.0.0-alpha.4; npm publication is a separate, gated step,
so docs/release/release-state.json records registry truth (@alpha =
1.0.0-alpha.3) and this train's own version stays a repository baseline until
a post-publish sync. No migration steps beyond one retired spelling noted
below, so an alpha.3 consumer upgrades with no action.
- Release — the packed Element entry declares its claim seam (#1425): the
published
sideEffects: falseon@openelement/elementwas wrong, and it shipped a real defect. The default entry installs the compiled claim executor through an import-time seam (src/index.tsbare-importsinternal/compiled/runtime/claim-install.ts, which callsinstallClaimExecutor(...)at module scope), so a consumer's own bundle dropped both halves: the bare import reads as an unused statement in a side-effect-free module and the installer body has no used exports. Every island that had to adopt server-rendered DOM then threw[compiled-kernel] existing DOM in the resolved root needs the claim executor— the packed-starter browser matrix failed 3/3 browsers, and the failure was reproduced in the consumer's own client bundle (island-app-shell-*.js), not inferred. The manifest now names both sides of the edge (./src/index.jsand./src/internal/compiled/runtime/claim-install.js; naming only one still drops the other, pinned by test), andpack-surface.tsgained the fail-closedfindUndeclaredSeamInstallsrule: a module-scopeinstallX(...)in a package whosesideEffectsdoes not name that module is exactly the shape that ships looking correct and breaks at runtime. The client-only reduction from #1416 is unaffected — that entry imports neither module. This is the one item here that changes what a consumer downloads, and it takes effect only at the next publish: every1.0.0-alpha.3already on npm still carries the wrong manifest, so a starter installed from it today silently does not hydrate. - Release — the browser-matrix skip is gone (#1425): with the root cause
fixed,
OPEN_ELEMENT_SKIP_BROWSER_MATRIXand every workflow injection of it are deleted. The packed-starter matrix, the dev continuation probe and the start continuation probe are unconditionally required again, so the qualification that caught this defect runs on every candidate. No tracked file mentions the variable any more (the alpha.3 entry above and ADR-0155 keep their historical narration, which is a record, not a guard). - CI — tree-SHA evidence reuse (#1425 follow-up, ADR-0156): a green
evidence package proves a TREE, not a commit — every record carries
shaandtree, every log is bound to the tree by its clean-proof line, and every tarball is byte-hashed. Two commits with the same tree have byte-identical content, so a merge that stalesdevno longer forces ~1–2 hours of runner time re-measuring content the suite has already measured. A new read-onlyreusejob (with exactlycontents: readandactions: read) resolves the newest in-window successful run whose API-computed commit tree equals the candidate's and that carries every lane's artifact; the four lanesneeds: reuseand, when it resolves, replay a verified no-op that downloads the source run's evidence and stampsreused: {runId, sha}. Reuse is strictly tree equality, and the paths out are fail-closed and total: no match, any API error, no token, or a run outside the retention window all resolve toreused=falseand every lane then runs its full gate. The aggregate does not trust the resolver — it still requires each record'streeto equal the checked-out tree and additionally rejects a stamp naming the candidate's own commit, a stamp disagreeing with the record'ssha, and a bundle whose reused jobs name more than one source run; the clean-proof lines, step argv and the Site E2EcandidateShaare all bound to the record's ownsha. A resolver job failure fails the aggregate rather than degrading silently. The decision record isdocs/adr/ADR-0156-tree-sha-evidence-reuse.md, including its P7 four-question review and the rejected alternatives (message-scoped reuse, per-lane resolution, a checked-in tree→run index). - CI — every remaining lane is bounded, and a red Site E2E run now stages its
evidence (#1402, #1409):
dependency-review,codeql#analyzeand bothpublished-consumersjobs had no ceiling, so a stalled extractor or a hung published-starter qualification could hold a runner for the platform default (six hours for CodeQL); each now carries one sized to its real work. The fresh-clone lane used to run its Site E2E suite before staging the report, so a failing suite threw past the staging step and its bundle carried logs but no report — the failing test's name was then unrecoverable without a live repro, which is exactly the class the alpha.2 closeout could not diagnose. A red run is now recorded before it is raised: the raw report plus sidecar are staged whenever the runner managed to write them,ran: falsewhen it did not, and the failure is re-raised afterresult.jsonso the exit status and the validator's verdict are unchanged. A red run still cannot smuggle a pass — the aggregate fails closed on a red or absent sidecar and rebinds the report bytes to the candidate commit. - Router — the accepted config surface grows to eleven keys, and head becomes
structured (#1411 follow-up):
dirs: { routes, islands, components }moves the source roots (the tokens / app-shell / head conventions follow the three roots' shared base, so a fullsrc/*move carries them);packageIslandsadditionally derivesssr.noExternalso a listed package's islands are bundled, with no publicssr.noExternalkey;head.scriptsandhead.stylesheetsjoin the config file, so a config entry and an inlineinject.scriptsentry emit the same bytes through one serializer;speculation,viewTransition,build.manifestBudgetandi18nvalidate as data. Raw markup stays out on purpose —injectis deliberately not a config key. The newapp/head.tsxconvention carries the structural head a URL list cannot express (preloads, icons, feed links, inline CSS); it is compiled into the app's module graph, not read as text, becauseloadConfigFromFilerefuses a?inlineCSS import. Entries are validated at the boundary — unsafe attribute names, non-string values,javascript:URLs,@importand unclosed<style>all fail the build instead of being silently dropped. An accepted head key that no code reads fails closed too. Two latent build-context bugs surfaced and are fixed: config-file i18n options never reached the integration (the context derived them before the config resolved, so the SSG emitted a single-locale site withlocales="[]"), and the head compile now keeps bare specifiers external instead of bundling package sources into a data module. - Retired — inline
openElement({ html })(#1411 follow-up): the flathtmloption is gone and fails closed withCONFIG_RENAMED_KEYnaminghead. This is the one authored-spelling change in this train: six fixtures, three packed-consumer harnesses,apps/saasand the Site itself are migrated in the same train.middleware.usedeliberately stays inline-only (the config file'smiddlewareblock carriescorsOrigin), and/guide/configurationnow says the two are either/or rather than mixing them, which remains a hard error. - Element — one error dialect, mechanically enforced (#1386): the package
used to raise failures through four conventions (OEC diagnostics,
OpenElementErrorwith codes, ~40 bareErrors carrying[compiled-*]prefixes, and three dedicated exception classes), so a consumer could not catch a failure by code without importing every class and pattern-matching the rest.OpenElementErrorand its per-surface code catalogue now live ininternal/protocol/errors.ts— import-free, so the ADR-0148 semantic core and the Part Program protocol raise framework errors without gaining host state — and every throw inpackages/element/srcspeaks it. The released values (OPEN_ELEMENT_COMPILED_CLAIM_MISMATCH,OPEN_ELEMENT_COMPILED_PROGRAM_INVALID,OE_PROGRAM_MISSING,SSR_DOM_ACCESS_UNSUPPORTED) are pinned so a later cleanup cannot rename them; new codes use theOE_prefix. The dialect is proved by an AST walk over every module undersrc/that fails on any barethrowor leftoverTypeError, plus a duplicate/off-namespace catalogue check. - Element — the emitted module is type-checked at build time (#1386):
the compiler emits the compiled module as TypeScript text and the bundler
lowers it, but nothing checked that text, so a compiler change could emit a
program that fails
deno checkor a consumer'stsgorun and the first evidence was a consumer's build.typeCheckEmittedModule()compiles the emitted text as a real program and returns a checker's diagnostics for it; the Vite adapter gains an opt-intypeCheckEmittedbuild gate (off by default, because it runs a program per emitted module — a dev-server transform must not pay that). It is a pure function of its inputs and only reports diagnostics belonging to the emitted files, so a caller's incomplete resolution map cannot masquerade as an emitted-module defect. - Element — claim-vs-fresh is decided from content, not a child count
(#1381): the kernel chose claim by
root.childNodes.length > 0, so a compiled light-root element carrying a whitespace-only text node — the shape an HTML formatter or a parsed file produces — threwPartProgramClaimErroron upgrade for a node that is invisible in the rendered page. Formatting whitespace is now normalized before a fresh mount. The fail-closed direction is pinned as hard as the fix, because widening it would be the correctness regression: a real element, a serializer anchor comment, visible authored text and whitespace preceding real content all still throw the structured, catchable mismatch, and a serialized light host still claims in place so server node identity survives. - Router — navigation state has one owner (#1385): the client router's
three pieces of shared mutable state (the monotonic latest-wins ticket, the
dedup key of the last browser-landed URL, and the one-shot marker of the
router's own guard-veto restore) lived across four functions. They now live
in
internal/router/navigation-state.tsbehind three questions — issue/owns, isDuplicateLanding/recordLanding, armRestore/consumeRestore — so cancelled pending execution is a side effect of owning intent, never of attempting a navigation; a guard veto, a stale ticket, an aborted traversal or a disposal all leave the current route's in-flight render alone. The machine is DOM-free, so its transitions are pinned without a browser, and the four interaction cases that motivated the extraction are pinned in the router suite. - Router — browser-shaped action POSTs must present an Origin (#1382): the
generated action POST floor let a
multipart/form-dataorapplication/x-www-form-urlencodedbody through when it carried neither Origin nor Fetch Metadata, because that allowance exists for non-browser callers. A browser-shaped body can present the same absent headers, so such a body is now rejected with the same 403 and RFC 9457 problem document when it also carries browser navigation evidence (Upgrade-Insecure-Requests: 1, or thetext/htmlAccept a form navigation sends). A scripted client sends neither marker and is unaffected;Origin: nullis untouched, since null is an origin the browser did send. The residual window is written down rather than implied:Origin: nullwithout Fetch Metadata,text/plainform bodies, custom API routes and ambient-auth apps, and theOPEN_ELEMENT_DISABLE_CSRF=1opt-out, which disables this rule too. - Docs — how i18n actually works (#1383):
/guide/i18n(plus itszhtwin) states the boundary that had no page: catalogs are application code. It covers the two file conventions — declarative locales resolved throughPagePropsContext.locale, and a loader's file-suffix scheme over the content tree — and the three rules that keep them honest (one link helper, never infer a locale from a two-letter segment, the default locale stays unprefixed). For catalogs it names the boring standards and when each fits (ICU MessageFormat for tooling compatibility; paraglide once volume grows), both build-time because a static-first framework prerenders its pages. What stays out is explicit: no message format, no Accept-Language negotiation, no content translation inside the framework. - Repo — the docs figures and the release bookkeeping follow the tree:
www#check:doc-figurespinscomparison.md/.zh.mdagainst a fresh measurement ofwww/dist, so this train re-baselines them (one page per locale from/guide/i18n, plus the runtime growth from the Element error dialect: html 64→66, sitemap locs 62→64, manifests 64→66, per-locale pages 31→32, fragments 62→64, manifest entries 304→314, rail pages 54→56, code-block pages 42→44,island-open-cinematic-scroll81,984→85,765 raw and 25,215→26,855 gzip,/payload 217,362→221,626 B). The six version points move throughdeno task version-bumprather than by hand, the state machine admitsv1.0.0-alpha.4, and the README, the Site version source and the registry-truth constants follow. - Tracked, not fixed:
typeCheckEmittedis implemented and tested but not yet enabled in the Router build CLI (packages/router/src/cli/{build-ssg,build-client}.ts), because that tree belonged to another lane in this run; enabling it there is a follow-up. The#1387portable-host migration (moving the build-timeDeno.*calls inpackages/router/src/{vite,cli}andpackages/create/srctonode:*, and retiring theexistsSyncseam) is still a deferred roadmap item, not part of this train — the build-time Deno-host requirement documented in the READMEs therefore still stands. Thewww/tools/generate-api-reference.tsconfig-type anchor still points atOpenElementOptions(which no longer carries the config-file keys); the API reference covers the config surface through/guide/configurationand the interface snapshot, and re-pointing the anchor atOpenElementUserConfigis a follow-up.
1.0.0-alpha.3
Door-handle train: the surfaces a consumer actually touches — the config
file, the packed npm facade, the error vocabulary, the documentation
pipeline, and the shipped bundle — are converged and gated. The source
line is 1.0.0-alpha.3; npm publication is a separate, gated step, so
docs/release/release-state.json keeps its registry block at the alpha.2
truth until the post-publish sync. No migration steps: the config file is
an optional overlay and nothing here is a breaking change, so an alpha.2
consumer upgrades with no action.
- Element, Router — framework options get one home (#1411):
openelement.config.tsis optional and near-empty; with an empty object every option comes from a file convention — design tokens fromapp/styles/tokens.css, the application shell fromapp/islands/app-shell.tsx, site title frompackage.json. A non-empty config file next to inlineopenElement(...)options is a hard error ("framework options have two homes"), and an unknown or wrong-typed key throws with the accepted-key list; there is no silent merging. The starter template'svite.config.tsis nowplugins: [...openElement()]with zero CSS strings,<app-shell>is registered by convention and stays deletable, favicon and og tags reach the built document, and the firstdeno task devrun no longer prints the production CORS advisory. The documented consumer scaffold command is the short all-permissions form the owner ruled on 2026-09-21 (the full command, with its concrete dist-tag, lives in the create README and the getting-started guide), admitted as an exact path-and-line exemption in thecheck-no-allow-alltripwire while every first-party invocation stays scoped. - Release — packed npm facade (#1412): per-package
keywords,engines(node>=24; deno>=2.9on the two Deno-driven toolchains) andsideEffects(falsefor element/router/ui,['./src/cli.js']for create) are written from one source and re-read from the real tarball bytes by the newpack-surface:checkgate, which also fails closed on a repository path or ADR citation in shipped text, on an export subpath the shipped README never names, and on a module-scope global write in a package declaredsideEffects: false. Every Element facade subpath is documented in the package README — the eight that existed when this landed, plusclient-onlybelow; 54 shipped files lost their repository-internal references; the create README states the current "do not run the bin under npx" limitation (the Node entry is tracked by #1387). - Element, Router — error experience (#1413): the compiler hands the
build a structured diagnostic (
{id, loc, frame, message, diagnostics}) instead of a pre-joined display string, so an overlay can underline the authored line and a consumer can read the location without parsing the message apart. The three user-facing runtime failures name the compiled module, the tag and the authoredthis.<property>instead of an index the author cannot map back; router authoring throws carry stable codes with phase and severity plus the remediation sentence they were missing; thestartCLI prints one actionable line built from the message and cause chain, with--debugexpanding the full stack./errorsis a derived artifact — everyfail(…, 'OEC9xxx', …)literal plus the element error protocol and router code maps — and 38 codes render per locale from the source that raises them; a hand-written list failscheck:errors. - Docs — pipeline before content (#1414): five pipeline pieces landed
first, so the facts are rendered rather than retyped. The API reference
renders each export's declared signature and its option-bag members; an
undocumented public export is red, and all 51 empty summaries are now
documented at their declarations; the config option table is generated
from the application options type;
/errorsis generated from the definitions; and the install command has one owner (packages/create/src/install-command.ts) which the site interpolates andstarter-smokeasserts against the packed CLI's own output. Content followed the pipeline: the routing-and-data Metadata and Data boundary sections are written out,delegatesFocus/formAssociated/converterare documented on core-concepts, and getting-started's Build section is user-facing. - Repo — legacy cleanup (#1415):
deno task version-bump <version>owns all six version points — 4× packagedeno.json,CREATE_VERSION, and the four fixturedeno.lockfiles — with a dry run by default and--writeapplying the edits, regenerating the locks, and then failing closed unless all six agree; hand-editing them burned two alpha.2 release rounds. Aworkflow_runcompanion re-runs the FAILED jobs of AutoFlow CI exactly once (attempt 1 only), because webkit fails deterministically per runner and only a fresh machine can change the outcome (#1409). A workspace-alias-hijack guard refuses to alias@openelement/*onto a checkout's sources when an app is scaffolded inside a framework clone — a build that would otherwise report success while resolving a different framework version (#1371 family). Theevidence/directory (0.43.3 tarballs) is gone and its ignore rule is documented as staying; the per-fixture Nitro rules collapsed to**/.output*|.wrangler|.nitro; and the ten fixture and e2e directories that had no README have one. - Element — bundle and update cost (#1416):
@openelement/element/client-onlyis a fresh-DOM entry without the existing-DOM claim executor, and the Router selects it only when no island on a page can hydrate server DOM (an island that says nothing keeps the full entry, because guessing the other way breaks hydration). Measured on the JFB keyed-table harness: 77,557 B → 68,630 B, −8,927 B (−11.5%), with the claim diagnostic strings at grep count 0 in the final bundle. Keyed updates also skip the slot walk when an entry's item reference is unchanged, turn the created-entry lookup into a Set, and resolve the fallback insertion reference on demand: instrumented on05_swap1k,updateItemValuescalls drop from 1000 to 0 per swap and the synchronous segment from 3.0 ms to 2.7 ms median (−10%, 480 samples). - Honest measurement — the swap1k target was not met: swap1k did not
reach single digits, and this section records why rather than restating the
goal. On the same runs the afterframe protocol floor (one
requestAnimationFrameplus oneMessageChanneltask with no DOM work at all) measured ~13–15 ms — 15.2 ms baseline against 14.5 ms with the change, and ~12.8 ms median in the separately documented floor measurement — whilemoveEntries' 997 realinsertBeforecalls cost ~2.3–2.7 ms.moveEntriesis unchanged byte for byte (Svelte's algorithm, ruled out of scope for this train), so the reported total contains the protocol floor plus that walk. The reactive boundary this exposes is documented in both locales on core-concepts: mutating an item in place is outside the contract — the same reference comparison asignal()holding an object draws — and the supported shape is a new item or a new array. - Tracked, not fixed: the packed-starter browser matrix runs behind
OPEN_ELEMENT_SKIP_BROWSER_MATRIX=1(#1425). It fails 3/3 browsers with awaitForFunctiontimeout in the consumer harness while the identical probe against a manually scaffolded packed starter passes all three browsers. The rest of packed qualification stays required; the guard is to be deleted when the investigation closes, not carried forward.
1.0.0-alpha.2
Constitutional-enforcement train: the alpha.1 review's fail-closed and
single-source findings are fixed, and the compiled when grammar grows to
the full admitted set. Published to npm as 1.0.0-alpha.2 under the
@alpha dist-tag.
- Element — conditional grammar (#1372):
whenregions accept>,>=,<,<=against a finite numeric literal; strict===/!==against number, string, or boolean literals (no cross-type coercion —1never matches"1"); and barethis.<property>truthiness with negation. Ternaries were already two-branch regions and now compose with the widened operators. All three executors evaluate conditions through one shared module (condition-holds.ts); the operator/literal space is closed by one predicate shared by both wire validators; the convergence guard pins the new invariant. The showcase island re-baseline is recorded honestly (78,176 → 79,199 raw bytes, +1.31%). - Element — security (#1373):
meta.tagsattribute names are validated against the canonicalisSafeAttributeNameand fail the render closed (UNSAFE_META_ATTR_NAME); CMS-fed metadata can no longer smuggle attribute injection through name grammar. - Element — correctness (#1374, #1375): the each-region item-key
derivation is one shared typed function (number
1and string"1"keep distinct identity from SSR through claim); runtime update-phase errors route into the kernel error boundary instead of escaping into the signal writer's stack, with retain-and-retry isolation semantics. - Tooling (#1376, #1377, #1378):
deno task verifyis gate:ci-equivalent and pinned by a contract test; local agent-workspace directories are ignored and git hooks are a documented setup prerequisite; the Router npm README states the build-time Deno-host requirement for./viteand./cli/*. - Site (#1379, #1380): the roadmap publish-state derives from
release-state.json(P6); the README comparison's Fresh row states the niche argument with dated stall facts. - CI hygiene (#1400, interim #1402): doc-figures chunk-raw rows carry the ±1% cross-runner tolerance; site-e2e retries a single flake per test. Investigations open: doc-figures determinism (#1401), job timeouts and flake management (#1402), content-dates pre-push hook (#1405).
1.0.0-alpha.1
New repository baseline for Element and Router, published to npm as
1.0.0-alpha.1 under the @alpha dist-tag (npm latest stays on the
stable 0.43 line). This is not an
upgrade of the 0.x
lines and no migration path from 0.x is offered: new projects start from
@openelement/create.
Packages: exactly four public packages —
@openelement/element,@openelement/router,@openelement/create, and the experimental@openelement/ui.@openelement/appand@openelement/adapter-viteare retired; their responsibilities now live in Element and Router tooling subpaths.Element: one mandatory compiler lowers supported TSX into a Part Program;
OpenElementsubclasses are authored with the@elementdecorator and@propertystate. Server serialization, fresh DOM, and existing-DOM claim all consume the same compiled artifact. Element installs without Router.Router: Route Mode (explicit route records) and Framework Mode (file routes, loaders/actions/forms, SSR/SSG, Vite integration, Nitro mount) ship from
@openelement/router,./vite,./nitro-mount, and./cli/*. Route Mode installs without Element. The unimplemented client-sideRouteConfig.loader/actionfields and the publicSpaLoader*/SpaAction*types are removed: the client router never ran them, so the public API no longer promises it. Data fetching stays on the route modules' serverloader/action.Breaks from 0.x: package names and import paths changed with the new baseline;
@openelement/element/sanitizeis gone andtrustedHtmlis the trust boundary; raw head fragments are passed through verbatim with only fail-closed invariants (<script>and executable<style>rejected); servedContent-Typevalues derive from the maintainedmimepackage; runtimes without the Web StandardURLPatternfail fast; the generated standalonedist/server/serve.mjsis replaced by the portablefetch(Request) -> Responseentry plus the start CLI; the ADR-0120 hard rule "pages with actions cannot be prerendered" is repealed — a page may be hybrid: prerendered static GET plus a request-time action POST, withdist/server/emitted whenever any route has an action (ADR-0120 amendment, 2026-09-16).Install (Alpha):
deno run --allow-read --allow-write --allow-env --allow-net --deny-ffi --no-prompt --minimum-dependency-age 0 npm:@openelement/create@alpha my-app@alphatracks the 1.0 prerelease line; a versionless install resolves the stable 0.43 line. Deno 2.9+ is required.Known limitations: Alpha APIs may still change.
@openelement/uiis experimental and outside the stable API promise. Hosted/deployed SaaS qualification, production SMTP, and real scan-engine qualification are external pending.
Review-round corrections (post-baseline, this branch)
- Correctness: the reading-page heading-id allocator now probes for the
first free suffix against every id already in the document (a page with an
element
id="foo-2"plus twoFooheadings no longer emits a duplicate DOM id); RSSpubDatevalidation is a UTC calendar round-trip, so impossible dates (2026-02-29,2026-04-31) fail closed instead of being silently normalized byDate. - Public API (Router):
head.structuredDataentries and values are typed as a recursiveJsonValue(StructuredDataEntry = { readonly [key: string]: JsonValue }) instead ofRecord<string, unknown>;JsonValueis exported. Runtime validation is unchanged. - Public API (UI):
openPropsRootSheetand thetoRootCsstransform are removed. The remainingopenPropsTokenSheettoken block selects:root, :host, so one generated sheet serves document and shadow adoption; the structural fallback stays:host-only. - Build: the production Site build is hermetic — source dates come from
the committed
www/lib/content-dates.jsonmanifest and the retired-URL baseline fromwww/tools/site-baseline-routes.json, sosite:buildneeds no network, no remote and no.git. Git-based refresh/verify tasks (content-dates:*,retired-url:check --refresh) run in CI, not in the build. - Release:
@openelement/uinow shipsTHIRD_PARTY_NOTICES.mdinside its tarball (open-props MIT text), asserted by the packed-artifact gate. - Tokens: the open-props adapter lives at
packages/ui/tools/generate-ui-tokens.ts(taskdeno task --cwd packages/ui generate:ui-tokens); build-artifact emitters are namedemit-*and no longer fake--checktasks.
0.44.0-beta.1
First public v0.44 prerelease (dist-tag beta; npm latest stays on the
stable 0.43 line). The TSX-to-Part Program compiler and page-route SSR bound
to the compiled program. The 0.41.0-era npm
beta.1–beta.3 artifacts remain withdrawn partial publishes, unrelated to
this line.
0.43.3 / 0.43.2 / 0.43.1 / 0.43.0
Stable maintenance line (npm latest). Compatible bug, security, runtime,
documentation and release-truth patches under ADR-0140 — no 0.44 feature train.
0.42.0
WC light fullstack, stable. The stable cut of the 0.42 alpha line: the request-time Application Loop (ADR-0120) frozen on top of the 0.41.x static freeze (ADR-0119), with freeze scope and non-goals in ADR-0122.
- Frozen scope (ADR-0122 §1–§4): the loop contract (loader/action
signatures,
fail()/redirect()algebra + HTTP encodings, PRG revalidation, no-JS baseline), the action protocol (x-openelement-action, morph client contract, channel symmetry), the fail-closed CSRF same-origin default, and first-mile start semantics. Breaking changes to these require an amendment ADR. - Breaking changes since 0.41.x:
IslandOptions.strategyrenamed tohydratewith no alias (ADR-0127); shape-1 SSR markup gains one fallback-tag wrapper element (ADR-0128); head-injection tightening —<base>/<meta http-equiv>/ raw<script>rejected inheadExtras; unfrozen alpha exports removed (i18nStaticPaths,switchLocale,AppIslandOptions,OPEN_PROPS_TOKEN_CSS, theOpenElementRouteNodere-export); pure-static builds no longer emitdist/server/; minimum Deno version is 2.8. - Not frozen / not claimed: session/flash, cache/ISR (
revalidatestays inert forward-compat data), streaming SSR, performance SLOs, third-party WC SSR corpus, production runtime recovery.
0.42.0-alpha.17
Registration-decoupling train (ADR-0128): route modules whose default export is
definePage(...) register the page class under the route-path-derived fallback
tag; export const tagName on a definePage route only names a content element.
Shape-1 pages gain the fallback-tag page element as an outer DSD wrapper around
the content element; user CSS targeting the old root tag must target the new
fallback tag or the content element. Plain element routes keep their tagName
export as the registration tag.
0.42.0-alpha.16
Starter-first remediation train.
- Island runtime:
hydrate: 'only'islands bind events and signals instead of rendering inert; function-modedefineIslandislands re-render on signal change. - Routing/SSR:
notFound()from a page render propagates to a real 404; unmatched request-time paths render the styled 404 page withCache-Control: no-store; successful GET pages relax toprivate, no-cachewhile POST responses keepno-store. - element runtime:
<style>/<script>text children serialize as raw text;data:URIs are allowed onimg srconly; keyedForsemantics locked and its teardown leak fixed. - Breaking (unfrozen alpha surface): head-injection sanitization tightened
(
<base>and<meta http-equiv=...>stripped fromheadExtras/head fragments);IslandOptions.strategyrenamed tohydratewith no alias (ADR-0127). - CSRF and enhanced forms: cross-site POSTs rejected while same-origin native form posts pass; enhanced forms morph correctly into slotted light-DOM pages.
- Starter surface: working blog routes, pinned dev vite version, styled 404 route.
0.42.0-alpha.15
Backlog-zero train: the element runtime gains a built-in allow-list HTML
sanitizer sanitizeHtml (later removed in the 1.0 baseline in favor of the
trustedHtml boundary) and keyed <For each key> reconciliation via an
optional key prop (ADR-0124), with displaced entries disposed on duplicate
keys. URL safety decoding tightened and _blank links get rel neutralized.
The SPA router gains a URLPattern fallback for Firefox, and framework throws
converge on OpenElementError.
0.42.0-alpha.14
Simplification and consumer-packaging train: packed-package consumers of JSR
dependencies unblocked; client runtimes are bundled via virtual modules; jsonc
parsing delegated to @std/jsonc; no-skip version continuity in release
tooling.
0.42.0-alpha.13
Standards-as-seams train (ADR-0123).
- Morph robustness: the enhance/island client is a real tested module;
focus, scroll and form-control state survive enhanced updates; nested DSD
templates instantiate recursively;
open:readyfires for every strategy bucket. - Route standards: all route matching is WHATWG URLPattern — the SPA client
router and the generated server matcher share one semantics;
renderIntent.modeis'static' | 'dynamic'with the'auto'alias removed. - Server seams: a WinterCG-shaped fetch middleware contract runs identically
in dev,
start, fixtures and Nitro;cli/previewmerged intocli/start --mode=preview. - Protocol: fetch-channel error responses are RFC 9457
application/problem+json, including the CSRF 403. - Components:
open-inputbecomes a real native-form citizen;open-dropdownmoves to the Popover API with CSS anchor positioning. - Site search: full-text, ranked, bilingual via Pagefind.
0.42.0-alpha.12
Maintenance train; no new product surface beyond the fixes.
- Correctness: a guard-vetoed
redirect()from a post-action loader re-run no longer wipes page data; the SPA client router matches Hono-style:param{.+}catch-all patterns; malformed percent-encoded URLs answer 400 instead of hanging. - CSRF floor proven: the generated action POST same-origin floor has real
deny/allow coverage, with
OPEN_ELEMENT_DISABLE_CSRF=1as the documented opt-out. - Honest claims: ISR labelled forward-compat/inert wherever it appears; the
security guide documents the built-in CSRF floor; the starter
headerNavconfig renders.
0.42.0-alpha.11
Maintenance train.
- Security:
validateSafeUrltab/newline bypass closed (module-scriptdata:XSS); desktop examples bind loopback only; the Mastodon example no longer caches API errors as data. - Correctness: a guard-vetoed redirect during navigation keeps the current
page's loader data;
useLoaderData<T>()typesT | undefined; theme broadcast no longer clobbers host-owneddata-theme; disconnect→reconnect no longer resets non-reflected prop state; JSX callbackrefis consumed;open-buttonanchor-mode disabled sync works both ways;open-input/open-badgeobserve dynamic attribute changes. - Honesty over surface: the homepage flagship example is real compilable API; dead options, config keys and misleading types removed across element/app/adapter-vite.
0.42.0-alpha.10
Cleanup and hardening train; no new product surface.
- Runtime correctness: element
update()routes re-render errors toonRenderError; SPA loader failures take the__openElementErrorchannel and SPA loader/action honorredirect()/notFound()with real navigation; UI double-escaping removed, open-tabs accessibility rewrite with instance-unique ARIA ids, open-dialog SSRopensync; generated-data writes fail closed in build mode;getStaticPathserrors honordynamicRouteFailure: 'fail'; the start CLI static server shares the test fixture and unwraps request-time responses. - Breaking (unfrozen alpha surface):
@openelement/appdropsi18nStaticPaths/switchLocale/AppIslandOptionsand theOpenElementRouteNodere-export;@openelement/ui/open-props-tokensno longer exportsOPEN_PROPS_TOKEN_CSS; the generated ui manifest corrects open-tabs slots to['tab','panel'].@openelement/elementgains@experimentalexports: ISR cache types and the third-party client runtimehydrateOpenElement/disposeOpenElement.
0.42.0-alpha.9
Cleanup train; no new product surface. <open-button> binds its click handler
so shadow-DOM submit events reach the outer form; request-scoped context is
passed explicitly through render/hydrate entry points.
0.42.0-alpha.8
Incomplete release (npm-unpublished). Tagged as a mechanical version bump,
but the packages were never published; superseded immediately by
0.42.0-alpha.9.
0.42.0-alpha.7
Patch release; all five packages published to npm under the alpha dist-tag
(historical five-package line).
0.42.0-alpha.6
Second independent review of the application loop.
- Morph client correctness: an explicit
<form action>wins over the page URL on enhanced submits; the popstate guard survives reloads and bfcache restores; morphed-in islands show the server render; morph matching is an ordered walk with exact deletion and relocation; nested DSD compares normalized on both sides; forms inside late-hydrating islands get the enhancement listener; a cancelableopen:action-errorhook precedes the network-failure reload. - Detection:
hasEnhancedFormsfollows relative imports. - Protocol tail: malformed form bodies answer 400 on both channels; the redirect duck type honors the 3xx whitelist; 405 responses carry no-store/Vary.
0.42.0-alpha.5
First hardening pass on the 0.42 line (ADR-0121).
- Root cause: the alpha.3 morph enhancement never fired because
submitis not composed and page content lives inside page-element DSD shadow roots. The client is rewritten around shadow-root submit interception and shadow-content morphing; island-survival claims are mechanically true. - Protocol (ADR-0121): named-action dispatch is own-key gated; one
x-openelement-actionheader (true= ActionResult JSON,enhance= HTML morph) withVary; an action returning aResponseis a contract violation; the default PRG strips the?/namemarker; every 3xx coerces to 303 on POST andredirect()validates its status; fetch callers always receive ActionResult JSON; request-time responses carryCache-Control: no-store; action POSTs get a 10 MB body limit; non-GET/POST methods answer 405. - Morph continuity: form-scoped
data-open-regiontargeting with navigation fallback, id-keyed + lookahead identity matching, popstate reload, cancelableopen:action-failure, submitter name/value preserved, 500 / cross-origin responses navigate instead of morphing, double-submit guard, fragment preservation,<details>/media state protection. - Also fixed: dev SSR crash on every route;
[...path]request-time routes; zero-island apps with enhanced forms; the starter's/contactroute now builds and is POST-smoked.
0.42.0-alpha.4
Hardening and recipes (ADR-0120).
- Validation recipes verified in tests: zod (
/register) and valibot (/subscribe) run inside fixture actions with 422/303 asserted in three engines. better-auth and Drizzle recipes are published as doc-level, honestly marked unverified. - The
createstarter gains a request-time/contactroute exercising the full loop (rendering: 'dynamic'+ action +data-open-enhance). - Fix from alpha.3: the morph no longer replaces an island whose light DOM carries whitespace-only text around the DSD template.
0.42.0-alpha.3
Revalidation continuity (ADR-0120): enhanced forms (data-open-enhance) morph
the returned document into place instead of reloading — submission returns the
same HTML the no-JS path renders (303/422), the client morphs it, and
history.pushState follows the PRG target. A hydrated island whose light-DOM
surface is unchanged keeps its shadow state; data-open-preserve exempts any
subtree; the island client script is never re-executed by a morph. A
data-open-region container limits the morph to the matching region.
0.42.0-alpha.2
The form/action loop (ADR-0120): plain HTML forms work without JavaScript on
rendering: 'dynamic' routes. Actions run before loaders; fail(4xx, data)
returns take the 422 re-render channel with the echo; successful mutations
answer 303 (PRG); redirects thrown from actions coerce to 303; POST without an
action is a defined 404. Named actions dispatch via formaction='?/name';
unknown names are a defined 404. Fetch callers receive the ActionResult
discriminated union.
0.42.0-alpha.1
First alpha of the 0.42 line (ADR-0120): request-time rendering gains
semantics. rendering: 'dynamic' routes skip prerendering and are served per
request by the generated dist/server/index.js, with
dist/server/server-manifest.json recording the partition. Hard rule: pages
with actions cannot be prerendered — a route module exporting an action without
mode: 'dynamic' fails the build (repealed by the ADR-0120 amendment of
2026-09-16: hybrid static GET + request-time POST is now allowed). Pure-static
projects emit no new artifacts.
0.41.2 / 0.41.1
Patch releases: release-tooling hardening and hygiene only — no public API, topology or runtime-default changes.
0.41.0
Stable five-package release (ADR-0119; historical line): the interface freeze
covers defineElement, definePage, buildApp, the package graph, the
supported subpaths and the static/SPA semantics of defineApp; request-time
data, forms, sessions and cache stay explicitly unfrozen until 0.42/0.44.
- Breaking: adapter-vite internal subpaths pruned at the freeze
(
app-vite,build-context,head-injection,i18n-plugin,plugin,generated-data-resolver,plugin-mdx,route-manifest,cli/build-client,cli/build-ssg) — use the root,nitro-mount,cli/buildandsitemapinstead. - ui control geometry is squared (
--btn-radius,--badge-radius,--ui-control-radius:--radius-round→--radius-1, 6px) — visual breaking change; update screenshots and custom control CSS. - The release verifier now supports stable
x.y.zversions, and the version guards stay honest on a stable current line.
0.41.0-alpha.19
Cleanup sweep; no new product surface. Fixes the reflect
removal suppression (Boolean default: true desync), the popstate
redirect-then-block URL fork, and For drift-token separator collisions.
Breaking type-surface changes: the element root switches to explicit type
export lists, SafeHtml/UnsafeHtml/StyleSheetRule leave the root, and the
internal open-element-render/open-element-hydration subpaths are pruned
from element exports.
0.41.0-alpha.18
Fixes the reflect: true static-prop write loop and SSR attribute overwrite;
resolves the root-level <Show>/<For> CSR edge; unifies prop attribute
casing; makes For branch tokens content-sensitive; fixes client-runtime
double hydration; runs router guards on history traversal; honors
prefers-color-scheme in theme-init. Dynamic-route render failures now fail the
build (opt-out 'warn'). Breaking removals of dead exports, fields and scripts.
0.41.0-alpha.17
Covers hydration and binding behavior in a real browser: signal text patching,
event hydration, SSR/hydration mismatch fallback and form submission through
shadow boundaries. A failing route render produces a defined 500 result with
RenderError diagnostics. Breaking surface removals: element root build
utilities (migrate to @openelement/element/build-utils), app root
RouteConfig/RouterInstance/RouterMode types, adapter-vite
ExternalManifest type and SsgPageOutput.hydrationHints.
0.41.0-alpha.16
Fixes unknown dynamic-route params serving 200: a notFound() thrown from a
page element's render propagates through the DSD render chain as protocol
control flow so the request-time server entry answers 404. SSR and hydration
event markers align for custom-element hosts and Show/For branches;
hydration validates marker counts and branch tokens and falls back to client
re-render on mismatch. Static-props observedAttributes merge at
class-definition time. Windows drive-letter island paths normalized.
0.41.0-alpha.14
Recovers the release line with an exact-version starter and verified published
consumers; all five packages published under the alpha dist-tag (historical
five-package line) with post-publish Deno, Node ESM, Nitro and third-party Web
Component smoke coverage.
0.41.0-alpha.13
Publication failed; changes shipped in alpha.14.
- Removes the alpha-only
defineLayoutalias; usedefineElementwith the same arguments for layout elements. - Restores declared static-prop defaults when reflected attributes are removed.
- Hardens SSR prop injection, custom-element hydration, params parsing, nested SSR depth, and adopted stylesheet composition.
- Stabilizes SPA action errors, caches same-route GET requests, bounds render data contexts, and compiles client routes into a declaration-ordered trie.
- Moves UI tokens to a CSS source of truth with generated-output drift checks; Create template generation becomes asynchronous, deterministic, and bound to the same-version release invariant.
0.41.0-alpha.12
Audit-remediation foundation release.
- Fixes core runtime issues, including the
signal-contextinfinite loop andErrorBoundaryretry. - Hardens SSG/build: command-injection closure, dynamic-route encoding, and
pure-Node
process.cwd()compatibility.
0.41.0-alpha.11
- Restores frozen-install and changed-path workflow truth.
- Fixes query decoding, SPA page-host data, dialog inert restoration, and theme propagation.
- Consolidates the Element/Adapter protocol seam and removes verified dead DSD, CEM, route-scanner, and UI escape code.
- Repairs clean Nitro Workers builds, semantic visual smoke, and package artifact allowlists.