Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ Current truth lives in:
Historical changelog details remain available through git history and release
evidence.

## 0.42.0-alpha.6

- Audit round 2 remediation (TP-5.6): the second, independent review of
the application loop
([`docs/audit/2026-07-28-alpha5-round2-review.md`](./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.
- Morph client correctness: an explicit `<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).
- Detection: `hasEnhancedForms` follows relative imports, so an enhanced
form inside a shared component no longer loses the enhancement layer
silently (#577).
- Protocol tail: malformed form bodies answer 400 on both channels
(#581); the redirect duck type honors the 3xx whitelist (#583);
405 responses carry no-store/Vary (#586).
- Evidence: parity contract extended to JSON 404 / contract-violation
500 / malformed-body 400 / 405 / the 303→GET chain (#587); local
playwright retries are zero (#590); the alpha.2 note gets the
enhancement-inert erratum (#591).

## 0.42.0-alpha.5

- Audit round 1 remediation (TP-5.5, ADR-0121): the first implementation
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Elements are the durable application contract; JSX and Basic Element are the
authoring layer; Declarative Shadow DOM is the default server representation;
interactive regions upgrade selectively.

Published package line: `0.42.0-alpha.5` (`v0.42.0-alpha.5`) — the stable five-package
Published package line: `0.42.0-alpha.6` (`v0.42.0-alpha.6`) — the stable five-package
release under ADR-0119's scoped interface freeze; the abandoned beta naming
is not an active line.

Expand Down
2 changes: 1 addition & 1 deletion README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Elements 是可长期保存的应用组件模型;JSX 与 Basic Element 是作者层;
Declarative Shadow DOM 是默认服务端表示;交互区域按需升级。

已发布包线为 `0.42.0-alpha.5`(`v0.42.0-alpha.5`)——ADR-0119 范围化接口冻结下的
已发布包线为 `0.42.0-alpha.6`(`v0.42.0-alpha.6`)——ADR-0119 范围化接口冻结下的
stable 五包版本;已放弃的 beta 命名不再是当前版本线。

## 当前产品
Expand Down
208 changes: 208 additions & 0 deletions docs/audit/2026-07-28-alpha5-round2-review.md

Large diffs are not rendered by default.

34 changes: 23 additions & 11 deletions docs/current/MORPH_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ carries a host's shadow content in its `<template shadowrootmode>` child).
never `pushState`).
4. A `422` dispatches a cancelable `open:action-failure` `CustomEvent` on
the form (`detail: { status, form, response }`) before morphing;
`preventDefault()` skips the default morph.
5. Success updates the URL with `history.pushState`; `popstate` reloads
the restored URL (no client-side state cache in 0.42). The local
fragment is preserved when the target is the same page.
`preventDefault()` skips the default morph. A network/fetch failure
dispatches a cancelable `open:action-error` (`detail: { error, form }`)
before the reload fallback; `preventDefault()` skips the reload.
5. Success updates the URL with `history.pushState`; `popstate` (and
bfcache `pageshow` restores) reload the restored URL when the session
has enhanced-navigated (a `sessionStorage` marker — no client-side
state cache in 0.42). The local fragment is preserved when the target
is the same page.
6. A second submit while one is in flight is ignored; cross-form
responses are ordered by sequence.

Expand All @@ -52,15 +56,23 @@ never touched.

## Identity and survival rules

- **Matching**: within a parent, children with an `id` match by `id`
(tag must match); the rest match structurally (node type + tag) with a
bounded lookahead, so insertions/removals between matched anchors are
preserved. Rows in dynamic lists should carry stable `id`s.
- **Matching**: an ordered walk. Old children are indexed by `id` (id'd
nodes are consumed only by an id match); each new child in order matches
by id else structurally ahead of the reference point; the match is
morphed and MOVED into position (moves preserve shadow roots and state);
unmatched new nodes are inserted in place; only never-matched old nodes
are removed. Reorders therefore keep both order and state, and rows in
dynamic lists should carry stable `id`s.
- **DSD instantiation**: nodes inserted by a morph (new subtrees,
replacements) have their `<template shadowrootmode>` instantiated
manually before insertion — the HTML parser is the only other place DSD
activates — so morphed-in islands show the server render, not a
client-initial one.
- **Island survival**: a hydrated island (live shadow root) survives when
its light-DOM surface serializes identically (`__islandIntact`):
attributes equal, and child nodes equal after skipping the DSD template
and whitespace-only text. Otherwise it is replaced and its state resets
by design.
attributes equal, and child nodes equal after recursively skipping DSD
templates and whitespace-only text on both sides. Otherwise it is
replaced and its state resets by design.
- **`data-open-preserve`**: exempts the whole subtree, checked before
island and attribute logic.
- **Scripts**: live `<script>` nodes are kept as-is (never re-executed;
Expand Down
33 changes: 30 additions & 3 deletions docs/current/VERSION_PLAN.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# v0.42.0 — WC Application Loop release plan

> Current source package line: `v0.42.0-alpha.5`\
> Current npm registry line: `v0.42.0-alpha.5`\
> Current source package line: `v0.42.0-alpha.6`\
> Current npm registry line: `v0.42.0-alpha.6`\
> Active release target: `v0.41.1`\
> Planning release target: `v0.42.0` (WC Application Loop)\
> Next release line: `v0.43.0` (Universal WC SSR)\
Expand Down Expand Up @@ -261,11 +261,38 @@ semantics beyond what the ADR-0120 amendment in step 1 records.
green; release-tier gates green; alpha.5 published with two-stage
evidence.

### TP-5.6 — `0.42.0-alpha.6` audit round 2 remediation (morph client correctness)

Goal: close the second, independent review round (issues #576–#593,
`docs/audit/2026-07-28-alpha5-round2-review.md`) — five high-severity
morph/enhancement defects and the protocol/test/evidence tail — without
expanding the 0.42 scope.

- 准入: TP-5.5 closed (alpha.5 published); the round-2 review report is
archived and its claims verified (done 2026-07-28).
- 执行步骤:
1. Morph client correctness: form-action URL resolution (#576),
sessionStorage popstate guard + bfcache pageshow (#578), manual DSD
instantiation before morph insertions (#579), ordered-walk
`__morphChildren` with exact deletion and relocation (#580),
recursive normalized island comparison (#582), late-hydration submit
rescan (#584), `open:action-error` hook (#585), diagnostics (#589),
submit-root pruning (#588).
2. Detection: import-following `hasEnhancedForms` scan (#577).
3. Protocol tail: 400 for malformed bodies (#581), redirect duck-type
whitelist + constructor tests (#583, #586), 405 no-store/Vary (#586).
4. Evidence: parity matrix extension (#587), local retries zeroed
(#590), alpha.2 enhancement-inert erratum (#591).
- 准出: every issue #576–#593 is closed with a code + test/gate proof;
the fixture suite passes on three engines with the new round-2 specs
included; release-tier gates green; alpha.6 published with two-stage
evidence; #592/#593 stay open as the TP-6 agenda they record.

### TP-6 — `0.42.0` stable decision

Goal: decide and ship the request-time freeze scope.

- 准入: TP-5.5 closed (alpha.5 published); the seven-day P0 watch on the
- 准入: TP-5.6 closed (alpha.6 published); the seven-day P0 watch on the
last alpha shows no P0.
- 执行步骤:
1. Stable-scope ADR: which request-time semantics freeze (expected: the
Expand Down
4 changes: 2 additions & 2 deletions docs/governance/PROJECT_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ complete because an issue, chat message, or SOP says it is complete. It is
complete only when the repository contains the decision, the execution package,
the implementation, and the gates that prove the claim.

Current execution anchor: published package line `v0.42.0-alpha.5`, completed
implementation anchor `v0.42.0-alpha.5`, and `0.42.0` WC Application Loop planning
Current execution anchor: published package line `v0.42.0-alpha.6`, completed
implementation anchor `v0.42.0-alpha.6`, and `0.42.0` WC Application Loop planning
under ADR-0120 and `docs/current/VERSION_PLAN.md`.
OpenElement is one Web Components-native,
static-first application framework: Basic Element is an authoring mode, not a
Expand Down
11 changes: 11 additions & 0 deletions docs/release/v0.42.0-alpha.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ should `return fail(422, data)` for validation errors and `redirect()`
(or nothing, for the default PRG) on success. The loader now runs after
the action on POST.

## Errata (2026-07-28, alpha.6)

- **The form-enhancement layer was inert at this tag**: the
document-level submit listener never fired for forms inside
page-element shadow roots (`submit` is not a composed event), so the
enhancement described above — including the `open:action-failure`
hook — never executed; every form submitted natively. Harmless by
design (the no-JS path is the baseline), but the enhancement claims
were not exercised. Same root cause as the alpha.3/alpha.4 errata;
fixed in `0.42.0-alpha.5` (ADR-0121).

---

AutoFlow3 patch release evidence: `publish-existing-v0.42.0-alpha.2-2026-07-27T07-43-59-379Z`.
Expand Down
67 changes: 67 additions & 0 deletions docs/release/v0.42.0-alpha.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# v0.42.0-alpha.6

## Audit round 2 remediation

The sixth alpha of the 0.42 line closes the second, independent review
of the WC Application Loop
([`docs/audit/2026-07-28-alpha5-round2-review.md`](https://github.com/open-element/openelement/blob/main/docs/audit/2026-07-28-alpha5-round2-review.md)
— TP-5.6, issues #576–#593). Five high-severity defects in the morph
client, plus the protocol and evidence tail.

- **`<form action>` wins on enhanced submits** (#576): the submitter's
`formAction` IDL value is only honored when a `formaction` attribute
exists — previously every enhanced submit posted to the current page,
diverging from the no-JS path.
- **History guard survives reloads** (#578): the enhanced-navigation
marker lives in `sessionStorage`, and bfcache `pageshow` restores are
covered too — back/forward after a reload no longer shows stale
content for the restored URL.
- **Morphed-in islands show the server render** (#579): DSD templates in
morph-inserted subtrees are instantiated manually before insertion
(the HTML parser is otherwise the only place DSD activates), so new
island instances hydrate the server's content instead of a
client-initial render.
- **Ordered morph matching** (#580): `__morphChildren` is an ordered
walk — id-keyed nodes relocate correctly (moves preserve state),
insertion batches of any size insert in place, and deletion is exact
(no lookahead window). Reordered id-keyed lists keep both order and
island state. Nested DSD compares normalized on both sides (#582).
- **Detection follows imports** (#577): an enhanced form inside a shared
component no longer loses the enhancement layer silently — the scanner
follows relative imports (bounded, cycle-safe; prose still never
triggers).
- **Protocol tail**: malformed form bodies answer 400 on both channels
(#581); the redirect duck type honors the 3xx whitelist (#583); 405
responses carry no-store/Vary (#586); `redirect()`'s constructor
validation has unit tests.
- **Client hooks and hygiene**: forms inside late-hydrating
(idle/visible) islands get the submit listener (#584); a cancelable
`open:action-error` event precedes the network-failure reload (#585);
morph fallbacks log a reason in dev (#589); detached shadow roots are
pruned from the listener registry (#588).
- **Evidence**: the dev/build parity contract now covers the JSON 404,
contract-violation 500, malformed-body 400, 405 and the 303→GET chain
(#587); local playwright retries are zero (#590); the alpha.2 note
gets the enhancement-inert erratum (#591).

## Verification

- Request-time fixture e2e: 49 tests × 3 engines = 147 passed
(Chromium, Firefox, WebKit) via `fixture:request-time:gate`.
- Unit suite green; parity contract 10/10 across the real dev (hono) and
built (Nitro) servers; scanner import-following covered by a unit
test.
- Reviewer's three recommended tests all exist and were
failing-then-passing: enhanced `<form action>` (#576), shared-component
enhancement (#577), morphed-in island server render (#579).

## Migrating from alpha.5

Nothing required. Apps that worked around #576 (explicit `form action`
with `data-open-enhance`) should verify the now-correct behavior; apps
relying on reload-on-popstate semantics see no change beyond bfcache
correctness.

---

`/@fs/` Windows verification status: see `docs/current/HYDRATION_CONTRACT.md` (Known limitations).
6 changes: 3 additions & 3 deletions docs/roadmap/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Execution and release state follow the
[`Project Workflow`](../governance/PROJECT_WORKFLOW.md).

> Source of truth for forward product planning.\
> Published package line: `v0.42.0-alpha.5`.\
> Active execution target: `v0.42.0-alpha.5`.\
> Published package line: `v0.42.0-alpha.6`.\
> Active execution target: `v0.42.0-alpha.6`.\
> Current implementation state: five-package convergence is published;
> alpha.17 closed the first audit remediation, alpha.18 completed the
> second audit sweep (ADR-0117), and alpha.19 completed the third (ADR-0118).\
Expand Down Expand Up @@ -116,7 +116,7 @@ making the standard Custom Element contract span both layers. See the official

## Current release state

`0.42.0-alpha.5` is the published package line. npm beta.1 through beta.3 are
`0.42.0-alpha.6` is the published package line. npm beta.1 through beta.3 are
immutable partial artifacts and remain withdrawn from the active release story.
The planned beta name was cancelled so the version label honestly reflects
that breaking architecture and interface changes are still allowed.
Expand Down
6 changes: 3 additions & 3 deletions docs/status/STATUS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# OpenElement Status

> Updated: 2026-07-27\
> Repository package line: `v0.42.0-alpha.5`\
> npm registry line: `v0.42.0-alpha.5`\
> Active release target: `v0.42.0-alpha.5`\
> Repository package line: `v0.42.0-alpha.6`\
> npm registry line: `v0.42.0-alpha.6`\
> Active release target: `v0.42.0-alpha.6`\
> Next release line: `v0.42.0`\
> Product graph: five packages\
> Current maturity stage: stable (0.41.x)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Shared component carrying the ONLY data-open-enhance literal in its tree
* (#577): routes that render this component contain no enhance attribute in
* their own source — the scanner must follow the import to ship the layer.
*/
export function SharedEnhancedForm() {
return (
<form method='post' action='/form' data-open-enhance>
<button id='shared-submit' type='submit'>Shared submit</button>
</form>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export function action(ctx: { formData: FormData }): never {
redirect(`/items?items=${encodeURIComponent('new,' + items)}`);
}

export const actions = {
reverse(ctx: { formData: FormData }): never {
const items = String(ctx.formData.get('items') ?? 'a,b').split(',');
redirect(`/items?items=${encodeURIComponent(items.reverse().join(','))}`);
},
};

const ItemsPage = definePage({
renderIntent: { mode: 'dynamic' },
head: { title: 'request-time fixture — items' },
Expand All @@ -27,6 +34,7 @@ const ItemsPage = definePage({
<form method='post' data-open-enhance>
<input type='hidden' name='items' value={data.items.join(',')} />
<button id='prepend' type='submit'>Prepend</button>
<button id='reverse' type='submit' formaction='?/reverse'>Reverse</button>
</form>
<ul>
{data.items.map((id) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ const PingPage = definePage({
</button>
<button id='mv307' type='submit' formaction='?/mv307'>Move</button>
</form>
{
/* #576: an explicit action attribute with an attribute-less
submitter — the enhanced POST must hit /form, not this page. */
}
<form method='post' action='/form' data-open-enhance>
<button id='to-form' type='submit'>Send to /form</button>
</form>
</main>
);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* /shared — a route whose enhanced form lives in an IMPORTED component
* (#577): this file contains no data-open-enhance literal, so the
* enhancement layer only ships because the scanner follows the import.
*/
import { definePage } from '@openelement/app';
import { SharedEnhancedForm } from '../components/shared-enhanced-form.tsx';

export const tagName = 'page-shared';

const SharedPage = definePage({
renderIntent: { mode: 'dynamic' },
head: { title: 'request-time fixture — shared' },
render() {
return (
<main>
<h1>shared component form</h1>
<SharedEnhancedForm />
</main>
);
},
});

customElements.define(tagName, SharedPage);
export default SharedPage;
Loading
Loading