test(ui): @openelement/ui v0.44 dogfood qualification fixture + CI gate (#1226, B2.1) - #1298
Merged
Conversation
added 2 commits
September 4, 2026 04:55
…on (#1226) - open-dialog: the compiled bool sink can remove the inner dialog's `open` attribute before the component's sync runs; per the HTML spec, removing the attribute from a modal dialog leaves it :modal and close() is then a no-op, so the modal session never exits the top layer. The close branch now restores the attribute before close(). Also, the native close echo of a programmatic close no longer re-dispatches open-dialog-close. - open-dialog: :state(open)/:state(closed) never applied in real browsers — the kernel attaches ElementInternals only for form-associated hosts, so the dialog attaches its own once via the instance-state module. - open-dropdown: native popover focus return only works when the previously focused element shares the popover's tree; on real pages (host inside a page shadow root) focus dropped to <body> on dismiss. The trigger path now records the composed focused element at open time and restores it on close only when the platform failed to. - open-input: formDisabledCallback mirrored onto the host `disabled` attribute, which the platform counts toward disabledness — a fieldset-disabled control could never be re-enabled. It now mirrors onto the (reflect: false) property. - element: renderDsd threads the compiled delegatesFocus static into the DSD template as shadowrootdelegatesfocus; without it, claimed shadow roots silently lost focus delegation (SSR/CSR parity). Covered by new unit pins in packages/ui/__tests__/components.test.ts and packages/element/__tests__/compiled-server/compiled-composition.test.ts, plus the ui-dogfood browser evidence in the follow-up commit.
… + CI gate (#1226) Adds packages/adapter-vite/__fixtures__/ui-dogfood: an app consuming @openelement/ui as an external consumer (packageIslands via the WC Package Protocol manifest, plain page markup, no fixture-private shims). Every route is static prerendered, so each page exercises the real compile -> SSR/DSD -> serve -> hydrate path. Playwright evidence (Chromium + Firefox + WebKit): - open-dialog: SSR-open attribute -> top-layer modal choreography, trigger open, focus containment, Escape close, focus return, single-event close, :state(open)/:state(closed). - open-tabs: WAI-ARIA roles/states/relations on live light-DOM children, roving tabindex, click selection, ArrowLeft/Right wrap + Home/End with focus following selection, reconnect/dispose effect teardown and re-sync. - open-dropdown: anchor-pair assignment, trigger toggle with the pointerdown guard, Escape/outside-click light dismiss, focus return. - open-input/open-button: FormData participation (incl. activation-time initial value), valueMissing blocking, formResetCallback, formDisabledCallback + fieldset re-enable, delegatesFocus, per-input events. - boundaries: shadow-open/light/shadow-closed contracts side by side, incl. a no-JS render pass. - claim: customElements.define-wrapped node-identity proof that hydration claims the parsed DSD/light DOM instead of re-rendering. CI wiring: fixture:ui-dogfood:{build,e2e,e2e:browsers,gate} deno tasks plus an autoflow policy gate (ci + release tiers) triggered by packages/ui, packages/element, adapter-vite src/fixtures, and deno.json — the same three-browser CI job pattern as fixture:request-time:gate.
Contributor
|
APIError: Insufficient Balance |
The fixture-local deno.json excludes e2e/ from fixture-rooted invocations (same pattern as the request-time fixture), but repo-root deno fmt/lint walk the whole tree under root config. The specs were only ever checked fixture-locally, so they drifted; run 33808083057 caught it.
Deploying openelement with
|
| Latest commit: |
557e7b3
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://c53bb04d.lessjs.pages.dev |
| Branch Preview URL: | https://1226-ui-dogfood.lessjs.pages.dev |
Contributor
|
APIError: Insufficient Balance |
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
#1226 (stage #1288, Beta.2): qualify
@openelement/uias a real v0.44 dogfood — an external consumer of the qualified framework. The listed complex primitives (dialog, dropdown, tabs, inputs/forms, focus, keyboard, ARIA, state, boundaries, SSR/DSD, claim, reconnect/dispose) had only Deno unit tests against uncompiled sources with stubbed internals — no evidence they work on the real compile → SSR/DSD → serve → browser path. The existing suites could not see whole classes of platform defects (and indeed hid four of them).Owner
Beta.2 UI dogfood qualification (#1226). One bounded slice: a consumer-style fixture + browser evidence + CI gating, plus minimal defect fixes uncovered by that evidence. Formal broader UI qualification stays Beta.3 per the stage ruling.
Before
packages/ui/__tests__exercised imperative methods against fake HTMLElements; compiled-sink behavior was covered only indirectly.After
New fixture
packages/adapter-vite/__fixtures__/ui-dogfood/: a plain app consuming@openelement/uiexactly like an external consumer (packageIslands: ['@openelement/ui']via the WC Package Protocol manifest, no fixture-private shims, no app shell). All routes are static prerendered, so every page exercises compile → SSR/DSD → static serve → island hydration. 25 Playwright specs run on Chromium, Firefox and WebKit (75 test executions).CI gating (hard requirement):
fixture:ui-dogfood:build|e2e|e2e:browsers|gatedeno tasks + a new AutoFlow policy gatefixture:ui-dogfood:gate(ci + release tiers), triggered bypackages/ui/**,packages/element/**,packages/adapter-vite/(src|__fixtures__)/**,deno.json— the same three-browser job pattern asfixture:request-time:gate(theautoflow-cijob already installs all three engines).Per-primitive coverage matrix
e2e/ui-dialog.spec.ts: SSR-open→ top-layer:modalat hydration (#1030 choreography), trigger open, focus containment cycle, Escape close, focus return to trigger, exactly-oneopen-dialog-close,:state(open/closed);e2e/ui-ssr.spec.ts: DSD +shadowrootdelegatesfocus+ slotted trigger markupe2e/ui-dropdown.spec.ts: per-instance anchor pair, pointerdown/click toggle guard, Escape + outside-click light dismiss, focus returne2e/ui-tabs.spec.ts: role=tab/tablist/tabpanel, aria-selected/controls/labelledby pairing, roving tabindex, click selection, ArrowLeft/Right wrap + Home/End, focus follows selectione2e/ui-form.spec.ts: FormData participation incl. activation-time initial value sync,valueMissingblocks native submission,formResetCallback,formDisabledCallback+ fieldset re-enable, disabled drops out of FormData, open-button composed submitdelegatesFocuslands host focus on the inner control:state(open/closed)on dialog,:state(disabled)on input, reflected host attributese2e/ui-boundaries.spec.ts: open (ui primitives expose shadowRoot), light + closed (consumer-authoreddogfood-light/dogfood-closed: inlinedata-oe-lightoutput vsshadowrootmode="closed"encapsulation), plus a no-JS render passe2e/ui-ssr.spec.ts: request-level DSD markup assertions per page; no-JS context proves full render without scriptsui-boundaries.spec.tsclaim test: acustomElements.definewrapper captures the browser-parsed DSD/light-DOM nodes pre-upgrade; post-hydration the same node references must be live (fresh re-render would replace them)ui-tabs.spec.ts: detach → effect observably off (stale ARIA after property write) → reconnect re-syncs exactly once, no duplicated click wiringDefects found and fixed (commit a295c9e, each minimal + separately justified)
<dialog>'sopenattribute on the same signal before the component sync runs; per the HTML spec, removing the attribute from a modal dialog leaves it:modalandclose()is then a no-op (verified against native platform behavior). The close branch now restores the attribute beforeclose(). Also fixed: the native close echo of a programmatic close re-dispatchedopen-dialog-close(now guarded).:state(open)/:state(closed)never applied in real browsers. The kernel attaches ElementInternals only for form-associated hosts; the dialog now attaches its own internals once (instance-state module). Unit tests had stubbed_internals, hiding this.<body>on dismiss. The trigger path records the composed focused element at open time and restores it on close only when the platform failed to (verified against native light-DOM behavior, which keeps working untouched).formDisabledCallbackmirrored onto the hostdisabledattribute, which the platform counts toward FACE disabledness — the state change never fired again (reproduced with a hand-written FACE). It now mirrors onto thereflect: falseproperty.delegatesFocuswas dropped from DSD serialization (framework, SSR/CSR parity).renderDsdnow threads the compileddelegatesFocusstatic into the template asshadowrootdelegatesfocus; without it, claimed shadow roots lost focus delegation. (packages/element/src/public-runtime.ts— not an ADR-0122 frozen path;freeze:semantics:checkpasses.)Unit pins added: dialog close ordering + close-echo guard + input disabled mirroring (
packages/ui/__tests__/components.test.ts), DSD delegatesFocus threading (packages/element/__tests__/compiled-server/compiled-composition.test.ts).Why-not-second-owner
Defect fixes stayed in the same PR deliberately: each is a few lines, each is proven by the fixture evidence added here (the e2e goes red without them), and splitting them would land known-broken primitives on dev behind a dogfood flag that says otherwise. No ADR-0122 frozen paths were touched, so no ADR-0151 amendment citation was required.
Evidence (all local runs reproduced as CI gates)
deno task fixture:ui-dogfood:gate→ exit 0, 75 passed (25 tests × Chromium/Firefox/WebKit)deno task test→ exit 0 (1792 + 150 passed, 0 failed)deno task pack:dry-run→ exit 0;package-artifacts:check→ PASS (inside autoflow:ci)deno task autoflow:ci(full local CI replica, real run): every gate PASS excepttest:e2e, which fails only on the 4 mobile visual-baseline screenshots (CJK font rasterizer drift, ratio 0.06 vs 0.05 allowance) — verified pre-existing on clean origin/dev on this macOS host (same 4 fail identically from a pristine worktree build); Linux CI uses the canonical baselines.fixture:ui-dogfood:gateruns and PASSes inside autoflow:ci; dry-run selection lists it for this diff.deno fmt --check/deno lint/deno task typecheck→ clean; fixture-local fmt/lint/check clean (the vite.configesbuild.jsxtypecheck note matches the request-time fixture's pre-existing pattern and is not CI-gated).Scope
In: new fixture, e2e specs, deno tasks, one autoflow policy gate, the five defect fixes with unit pins. Out: packages/ui redesign, new components, Beta.3 formal qualification breadth, www changes. Note: the fixture defaults to e2e port 4197 (4190 is occupied/misbehaving on some dev hosts).
Risk
Low: additive fixture + test-only wiring; the production-code delta is five small, targeted fixes each with browser + unit evidence. Watch-items: visual-baseline host drift (pre-existing), and the new gate adds ~30s to the CI matrix on ui/element/adapter changes.
Closes #1226 (all listed semantics covered or explicitly gapped above with Beta.3 pointers).