Project/Changelog
Published, candidate, withdrawn and historical release evidence for OpenElement.
The currently published package line is v0.43.3.
The project follows Keep a Changelog and SemVer. Historical entries preserve older names where they describe older releases; current docs use the openElement contract.
The published five-package line — unified product and website surface, sealed export seams.
The eleven-package era — JSR-only, before the collapse. Historical record.
v0.43.3 is the published stable maintenance baseline on the 0.43 track. The static, request-time, and Universal WC SSR contracts remain frozen under ADR-0119, ADR-0122, and ADR-0135; ADR-0140 admits compatible bug, security, runtime, documentation, and release-truth patches without scheduling a 0.44 feature train.
The npm beta.1–beta.3 artifacts are withdrawn partial releases, not supported product lines or upgrade targets.
※ Withdrawn partial artifacts (beta.1–beta.3) stay withdrawn from the active release story. History is kept, not rewritten.
Historical archive. The maintained, per-release record lives in
docs/release/and is the authoritative source. This file is an aggregated history only: it is not re-synchronized release by release and there is no mechanism keeping it in lockstep withdocs/release/. When the two disagree,docs/release/wins.
Current truth lives in:
docs/current/VERSION_PLAN.mddocs/current/PACKAGE_SURFACE.mddocs/status/STATUS.mddocs/roadmap/ROADMAP.mdHistorical changelog details remain available through git history and release evidence.
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 untouched
0.41.x static freeze (ADR-0119), with freeze scope and non-goals in ADR-0122
(accepted 2026-08-14, TP-6). See the per-alpha entries below for the full
train history; docs/release/v0.42.0.md is the authoritative note and
docs/release/v0.42.0-migration.md the upgrade guide.
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.IslandOptions.strategy renamed to
hydrate with no alias (ADR-0127); shape-1 SSR markup gains one
fallback-tag wrapper element (#960, ADR-0128); head-injection tightening —
<base> / <meta http-equiv> / raw <script> rejected in headExtras
(#931); unfrozen alpha exports removed (i18nStaticPaths, switchLocale,
AppIslandOptions, OPEN_PROPS_TOKEN_CSS, the OpenElementRouteNode
re-export); pure-static builds no longer emit dist/server/ (#953);
minimum Deno version is 2.8 (the generated dist/server requires Node ≥
20.19 or Node 24 — the stale Node 18+ claim is tracked as #969).@openelement/* specifiers to 0.42.0, refresh the lockfile,
rebuild (byte-level upgrade proof in the release note).revalidate stays
inert forward-compat data), streaming SSR, performance SLOs, third-party
WC SSR corpus (0.43), production runtime recovery (0.44).Registration-decoupling train (#960, ADR-0128) plus the architect final review's hardening and documentation set.
definePage(...) now register the page class under the route-path-derived
fallback tag (app/routes/index.tsx → index-page); the
export const tagName on a definePage route is ignored for registration
and only names a content element (#960). Shape-1 pages (starter pattern)
gain the fallback-tag page element as an outer DSD wrapper around the
content element; hydration markers and morph behavior are unchanged, but
user CSS targeting the old root tag must target the new fallback tag or
the content element. Plain element routes (no definePage) keep their
tagName export as the registration tag. Fixes the silent loss of the
definePage render when a module self-registered a content element under
the same tag. Migration: docs/release/v0.42.0-migration.md.Starter-first remediation train (issues #915–#959): the remaining headline gaps from the round-3 audit close, with e2e gates pinning each fix.
hydrate: 'only' islands now bind events and signals
instead of rendering inert (#939); function-mode defineIsland islands
re-render on signal change (#940).notFound() from a page render propagates to a
real 404 status (#922); unmatched request-time paths render the styled 404
page with Cache-Control: no-store (#923). Successful GET pages relax from
no-store to private, no-cache so the UA can restore scroll position and
use bfcache (#943); POST responses keep no-store.<style>/<script> text children serialize as raw
text — CSS selectors with > no longer corrupt (#932); data: URIs are
allowed on img src only, not href (#929); keyed For semantics locked
(a data change without a key change does not re-render, #915), its
AbortSignal teardown leak fixed (#916), and the coverage matrix landed
(#918).<base> and <meta http-equiv=...> are now stripped from
headExtras/head fragments (#931). Apps injecting a CSP meta or base tag
through headExtras must move those into the document shell / SSG
postprocess directly.IslandOptions.strategy
is renamed to hydrate (#920), matching the app-side
defineIslandConfig({ hydrate }) name; the old name is deleted with no
alias. Migration: rename strategy → hydrate at each element
defineIsland() call site. App-side authoring is unaffected.content/blog,
@openelement/generated/blog-data, styled 404 app/routes/404.tsx) — a
copyable example for the documented virtual module and the shadow-DOM
styling constraint.Origin: null + Sec-Fetch-Site: same-origin) pass (#921, #938); enhanced forms morph correctly into
slotted light-DOM pages (#937).For lists
reactive — per-item boundary markers pair hydrated list groups with their
For target by branch ordinal (#917).ErrorBoundary (ADR-0053 Layer 2) and bubble outward when a
fallback itself throws (#919); a subclass render() must branch on
hasError (#958).isTrusted=false and native default actions (anchors, checkboxes) may
run twice..mdx route files are discovered and rendered (#954); index
routes keep clean URLs when a same-named sibling directory exists (#956);
request-time builds emit a standalone dist/server/serve.mjs entry
(#959); the corsOrigin misconfiguration warning is emitted once per
process (#925).render() failure instead of crashing SSR; the depth-limit error is
logged once, not once per bubbled frame; the standalone serve.mjs
validates PORT/OPEN_ELEMENT_PORT; the dev island client entry matches
URLs with query strings; the missing-tagName scanner note is silenced
for definePage routes; the SSR instantiate-failure fallback now preserves
attributes like the render-failure fallback (#892 partial); target="_blank"
case variants get a forced noopener; CSS escapes/comments are folded
before the style blocklist check; event-binding disposers register under
signal lifecycles; keyed For rewinds its insertion cursor on duplicate-key
displacement.Backlog-zero train: built-in HTML sanitization and keyed reconciliation land in the element runtime, alongside a wide simplification sweep.
sanitizeHtml
(#894); keyed <For each key> reconciliation via an optional key prop
(ADR-0124, #890), with displaced entries disposed on duplicate keys
(#911); isSafeUrl decodes : and _blank links get rel=opener
neutralized (#911); guarded RenderHooks throw semantics documented
(#911); the DSD layout-fix flush is chunked across frames (#896); SSR/CSR
prop collection unified and the OpenElement base class split into
collaborators (#903, #904, #906, #900).OpenElementError (#898, #899).docs:truth and the AutoFlow gates (#893, #911).release.ts split, Web/ES/Deno std API convergence, unused import-map
entries dropped); e2e made network-independent via host-resolver-rules.Simplification and consumer-packaging train: the packed-package consumer path is unblocked and dead surface is swept repo-wide.
toString() (#868); jsonc parsing
delegated to @std/jsonc (#870).Standards-as-seams train + TP-6 freeze preparation (16 issues: #603–#610, #856–#860, #863–#867; ADR-0123).
open:ready
fires for every strategy bucket (#605); island scheduling has a single
owner (#606).renderIntent.mode is honestly 'static' | 'dynamic' with the 'auto'
alias removed (#609).start, fixtures and Nitro (#858); nitro-mount is a
near pass-through on Nitro v3's native Request (#857); cli/preview
merged into cli/start --mode=preview (#859).application/problem+json, including the CSRF 403 (#863).open-input becomes a real native-form citizen
(ElementInternals pilot, #864); open-dropdown moves to the Popover API
with CSS anchor positioning (#865).Round-6 full-spectrum audit remediation train: all 112 findings from the
2026-08-02 round-6 audit (issues #810–#852) are fixed; the report lives at
docs/audit/2026-08-02-round6-audit.md.
redirect() from a post-action loader
re-run no longer wipes page data — the action path mirrors the #802
navigation guard (#810); the SPA client router matches Hono-style
:param{.+} catch-all patterns (#812); malformed percent-encoded URLs are
a defined 400 instead of a hung request (#823).OPEN_ELEMENT_DISABLE_CSRF=1 as the
documented opt-out (#811).headerNav config actually renders (#829).verify-package-configs performs a real version
check (#824); stripComments is string-literal aware (#826); mojibake and
stale-claim pattern lists single-sourced (#827, #838); the five-package
roster derived from one constant (#828).data-ssr-props
single-sourced (additive DATA_SSR_PROPS export, #836); dead exports and
config swept across all five packages (#833, #834, #845–#852).Round-5 full-spectrum audit remediation train: all 78 findings from the
2026-08-01 round-5 audit (issues #730–#809) are fixed; the report lives at
docs/audit/2026-08-01-round5-audit.md.
validateSafeUrl tab/newline bypass closed (module-script
data: XSS, #761); desktop examples bind loopback only — the reader's
LAN-reachable arbitrary file read and remote kill are gone (#777);
Mastodon example no longer caches API errors as data (#775).useLoaderData<T>() honestly types
T | undefined (#763); theme broadcast no longer clobbers host-owned
data-theme (#773); disconnect→reconnect no longer resets non-reflected
prop state (#772); JSX callback ref is consumed (#756); open-button
anchor-mode disabled sync works both ways (#757); open-input/
open-badge observe dynamic attribute changes (#769, #770); blog locale
fallback direction fixed (#759); three empty-shell site-ui components
registered (#758); page-rail scroll-spy active state applies (#779)..githooks executable again so local gates cannot be
silently skipped (#760); duplicated tools helpers single-sourced; ~40 dead
exports / dead files swept.PREVIOUS_PACKAGE_VERSION so the next bump cannot self-collide (#727);
bump-version.ts documents its half-bump role (#687); the dead JSR publish
channel is removed (#746).update() routes re-render errors to
onRenderError (#662); SPA loader failures take the __openElementError
channel (#676) and SPA loader/action honor redirect()/notFound() with
real navigation (#731); ui double-escaping removed (#726), open-tabs
accessibility rewrite (#666) and instance-unique ARIA ids (#745), open-dialog
SSR open sync (#667); generated-data writes fail closed in build mode
(#671); getStaticPaths errors honor dynamicRouteFailure: 'fail' (#672);
the start CLI static server is shared with the CI fixture and its
request-time response unwrap is fixed (#732); adapter-vite declares its
typescript dependency so published consumers resolve (#725).internal/content/mdx/ directory, protocol seam shells, codegen dead
re-exports and the JSR machinery are deleted (#694/#697/#741/#746);
zero-consumer exports dropped across app/element/ui (#740/#743/#747);
www guide pages share one shell and dead islands/icons/blog content are
removed (#749/#748); audit reports archive under docs/audit/ with the
citation guard rescanning them (#751).@openelement/app
drops i18nStaticPaths/switchLocale/AppIslandOptions and the
OpenElementRouteNode re-export from ./model (#743);
@openelement/ui/open-props-tokens no longer exports OPEN_PROPS_TOKEN_CSS
(#747); the generated ui manifest corrects open-tabs slots to
['tab','panel'] (#744). @openelement/element gains @experimental
exports: ISR cache types (#729) and the third-party client runtime
hydrateOpenElement/disposeOpenElement (#740).docs/integrations/ is
covered by a package-surface truth gate (#737); the Fresh guide is rewritten
against the real example (#728); the ISR KV adapter contract samples compile
(#729).<open-button> now binds _handleClick as an arrow-field so the
framework's raw addEventListener wires this to the component instance —
shadow-DOM submit events now reach the outer form (#637). Request-scoped
context is passed explicitly through render/hydrate entry points (#632/#644);
logger _warned moved to render scope (#643); app reuses
@openelement/element's createLogger (#636).app re-export layer (#634); dropped a dead
router data-context module; trimmed unused exports across packages/*
(#633/#635/#642/#643/#644).@openelement/element, app, adapter-vite, ui, create) are published to npm
with dist-tag alpha=0.42.0-alpha.9.v0.42.0-alpha.8 was cut as a
mechanical version bump with a git tag and GitHub release, but the npm publish
step failed and the packages were never published. It is a version hole:
git tag v0.42.0-alpha.8 and the GitHub release exist, but
@openelement/*@0.42.0-alpha.8 is absent from the registry and no AutoFlow3
closure was written.0.42.0-alpha.9, the first fully npm-published
0.42.0 prerelease. Retained as-is per docs/release/v0.42.0-alpha.8.md
(no closure by design). See that note for the post-mortem.0.42.0-alpha.6 → 0.42.0-alpha.7,
status completed. All five packages published to npm with dist-tag alpha;
a full AutoFlow3 evidence chain was recorded (verify source version → main CI
→ artifact gate → publish npm → verify npm versions/dist-tags → consumer
smoke → third-party Web Component smoke → stage/commit evidence → tag → push
→ GitHub release).e0e46281; publish run 30431305870. /@fs/ Windows verification
status tracked in docs/current/HYDRATION_CONTRACT.md (Known limitations).docs/audit/2026-07-28-alpha5-round2-review.md,
issues #576–#593) is closed — five high-severity defects in the morph
client plus the protocol and evidence tail.<form action> now wins over
the page URL on enhanced submits (#576); the popstate guard survives
reloads and bfcache restores (#578); morphed-in islands show the
server render (manual DSD instantiation before insertion, #579);
morph matching is an ordered walk with exact deletion and relocation —
reordered id-keyed lists keep order AND island state (#580); nested
DSD compares normalized on both sides (#582); forms inside
late-hydrating islands get the enhancement listener (#584); a
cancelable open:action-error hook precedes the network-failure
reload (#585); morph fallbacks log a reason in dev (#589).hasEnhancedForms follows relative imports, so an enhanced
form inside a shared component no longer loses the enhancement layer
silently (#577).submit event is not composed and page content lives inside
page-element DSD shadow roots, so the document-level listener never saw
enhanced forms. The client is rewritten around shadow-root submit
interception and shadow-content morphing; the island-survival claims
are now mechanically true (errata appended to the alpha.3/alpha.4
notes).actions;
named-action dispatch is own-key gated; one x-openelement-action
header (true = ActionResult JSON, enhance = HTML morph) with
Vary; an action returning a Response is a contract violation; the
default PRG strips the ?/name marker; every 3xx coerces to 303 on
POST and redirect() validates its status; fetch callers always
receive ActionResult JSON (404/500 included, production-scrubbed);
request-time responses carry Cache-Control: no-store; POST takes the
same error boundary as GET; action POSTs get a 10 MB body limit;
non-GET/POST methods answer 405.data-open-region targeting with
navigation fallback, id-keyed + lookahead identity matching, popstate
reload, open:action-failure restored (cancelable), submitter
name/value preserved in the enhanced body, 500/cross-origin responses
navigate instead of morphing, double-submit guard, fragment
preservation, <details>/media state protection. The full survival
matrix is documented in docs/current/MORPH_CONTRACT.md.check:static-output-freeze); a dev(hono) vs
build(Nitro) parity contract test boots both real servers; the perf
baseline records its environment
(docs/release/v0.42.0-alpha.5-performance.json).customElements polyfill — now shipped to the dev entry);
[...path] request-time routes; zero-island apps with enhanced forms
(the enhancement layer is emitted only when enhanced forms exist, so
island-only sites keep their lean bundle); the starter's /contact
route (it was never shipped in TEMPLATE_FILES) now builds and is
POST-smoked in consumer CI; SPA vs request-time loader/action types
and docs are honest; a CSRF threat-model page ships in the guide./register) and valibot
(/subscribe) run inside fixture actions with 422/303 asserted in three
engines — docs/integrations/validation.md. better-auth and Drizzle
recipes are published as doc-level (honestly marked unverified):
docs/integrations/better-auth.md, docs/integrations/drizzle.md.create starter gains a request-time /contact route exercising
the full loop (rendering: 'dynamic' + action + data-open-enhance),
so starter consumers get the loop out of the box.PACKAGE_SURFACE.md records the 0.42 line additions as unfrozen with
their freeze target.docs/release/v0.42.0-alpha.4-performance.json).data-open-enhance)
now 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.data-open-preserve exempts any subtree; the island client
script is never re-executed by a morph.data-open-region container limits the morph to the
matching region when present.rendering: 'dynamic' routes.fail(4xx, data) returns take the 422 re-render channel with the echo;
successful mutations answer 303 (PRG) — never a 200 render; redirects
thrown from actions coerce to 303; POST without an action is a definedformaction='?/name'
(export const actions = { name(ctx) {...} }); unknown names are a
defined 404.x-openelement-action header) receive the ActionResult
discriminated union (failure/redirect/error); the island client
entry enhances data-open-enhance forms with the same protocol and no
DOM surgery — unhydrated islands are untouched, failure falls back to
the native 422 render unless the page handles open:action-failure.javaScriptEnabled: false and on the JS
enhancement path — Chromium, Firefox and WebKit, 33/33.renderIntent.mode was inert metadata before this release.rendering: 'dynamic' routes skip prerendering, dynamic expansion and
i18n locale prerendering; they are served per request by the generated
dist/server/index.js (nitro-mount over the same SSR bundle), with
dist/server/server-manifest.json recording the partition.mode: 'dynamic' fails the build.requestTimeRoutes; pure-static projects emit
no new artifacts (byte-identical public output proven against the 0.41.2
build: zero HTML/JS/CSS differences).packages/adapter-vite/__fixtures__/request-time/ proves loader data
varies per request and islands hydrate identically to static pages
(Chromium, Firefox and WebKit, 12/12).__headExtras is not defined codegen bug for projects without head extras is fixed too.Loader/LoaderContext/Action/
ActionContext) are unchanged — they already shipped; this alpha wires
the rendering-mode semantics around them.10038c4d).tools/project-constants.ts, and the www truth gate fails a release
until the new release theme is written (the 0.41.1 bump had shipped
alpha.19's theme under the v0.41.1 timeline entry).dist/.openElement magic strings converge on shared
internal/paths.ts constants; tools/lib/fs.ts gains shared
readText/readJson used across ten tooling files.CONTRIBUTING.md records the no-sloppy-imports rationale; stale merged
branches were pruned.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.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/build and sitemap instead (breaking; see the migration guide).docs/release/v0.41.0-migration.md and the site's /guide/migration page.--btn-radius, --badge-radius,
--ui-control-radius: --radius-round → --radius-1, 6px) — visual
breaking change; update screenshots and custom control CSS.x.y.z versions
(prerelease-only before), and the version guards stay honest on a stable
current line.default: true desync),
the popstate redirect-then-block URL fork, and For drift-token separator
collisions; the reflect browser spec joins the Firefox/WebKit smoke gates.SafeHtml/UnsafeHtml/StyleSheetRule leave the root for
real, and the internal open-element-render/open-element-hydration
subpaths are pruned from element exports (breaking type-surface changes;
see the GitHub prerelease notes for migration).HYDRATION_STRATEGIES and the routeInfo contract, sinks
the release executor into release.ts with orchestration tests, and
preserves curated release-note sections so migration records stay in the
durable record.globalThis.Deno/destructuring/npm: patterns.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.'warn'); failed pages never ship
nor enter the ISR manifest; static non-200 outcomes are summarized.renderIntent.streaming,
converges fmt/lint exclusions, and retires stale files and config entries.RenderError diagnostics; the unread hydrationHints
contract is removed.clientOnlyTags chain and dead build-context fields.ACTIVE_EXECUTION_VERSION.publish-existing evidence: records the true previous package
line, rewrites GitHub release notes from completed evidence, and
generates the release closure record automatically.@openelement/element root
to @openelement/element/build-utils, stops exporting internal router
types from the @openelement/app root, and aligns PACKAGE_SURFACE.md
with a machine-checked exports inventory. Breaking surface removals:
element root build utilities (migrate to ./build-utils), app root
RouteConfig/RouterInstance/RouterMode types, adapter-vite
ExternalManifest type and SsgPageOutput.hydrationHints.notFound() thrown from
a page element's render now propagates through the DSD render chain as
protocol control flow (duck-typed alongside redirect) so the request-time
server entry answers 404 (#922). The starter blog route now signals
not-found this way instead of rendering a 200 "Post not found" page.Show/For branches; hydration validates marker counts and branch tokens
and falls back to client re-render on mismatch.observedAttributes at class-definition time so
attribute-to-signal synchronization works in real browsers..tsx/.jsx island chunks, and normalizes Windows
drive-letter island paths (#460).latest dist-tag invariant for prereleases in the publish
tooling and release verifier.alpha dist-tag with post-publish
Deno, Node ESM, Nitro and third-party Web Component smoke evidence.defineLayout alias; use defineElement with the same
arguments for layout elements.Audit-remediation foundation release: closes the 13 high-severity findings
from the 0.41.0-alpha12 full code audit (issues #424–#428). No in-repo
release note was written at release time; the retrospective gap note is
docs/release/v0.41.0-alpha.12.md.
signal-context infinite loop
and ErrorBoundary retry.process.cwd() compatibility.docs/release/v0.41.0-alpha.11.md).