#11-tags-T2d-interactive: 7 element prototypes (Dialog/Details/Template/DataList/Output/Progress/Meter)#181
Merged
Merged
Conversation
…/Progress/Meter prototype family 7 distinct HTMLElement subclass prototypes (no shared prototypes; each tag has its own dispatch arm in `tag_specific_t2d_prototype`): - HTMLDialogElement: open + returnValue + show/showModal/close, with IsModalDialog ECS marker, DialogReturnValue state, close-event firing via dispatch_simple_event(bubbles=false, cancelable=false), and InvalidStateError on already-modal show(). - HTMLDetailsElement: open boolean + name string reflect. ToggleEvent fire on open change deferred to `#11-tags-T2d-details-toggle-event` (paired with D-10 `#11-events-misc`). - HTMLTemplateElement: content [SameObject] DocumentFragment with lazy per-template allocation via EcsDom::create_document_fragment_with_owner. Parser-side fragment population deferred to `#11-template-parser-content` (paired with html5ever replacement, Phase 5). - HTMLDataListElement: options [SameObject] HTMLCollection (reuses CollectionFilter::Options). `<input>.list` back-ref deferred to `#11-tags-T2d-input-list`. - HTMLOutputElement: htmlFor [SameObject, PutForwards=value] DOMTokenList via NEW DomTokenListSource::OutputHtmlFor variant (5th source) + output_html_for_wrappers cache; form/name/type='output'/labels stub + ConstraintValidation mixin install + value/defaultValue state machine (OutputValueOverride / OutputDefaultValue ECS, snapshot on value-mode entry, form-reset round-trip via reset_form extension). - HTMLProgressElement: value/max double IDL with clamping + position computed (-1 indeterminate else clamp(value,0,max)/max) + labels stub. - HTMLMeterElement: value/min/max/low/high/optimum (all double IDL) per HTML §4.10.15 actual-value algorithm + labels stub. Infra adds: - 4 NEW engine-indep ECS components in elidex-ecs::components: IsModalDialog, DialogReturnValue(String), OutputDefaultValue(String), OutputValueOverride(Option<String>). - NEW engine-indep helper parse_double_or_default(raw, default) in elidex-dom-api::element::numeric_reflect (HTML §2.4.4.3 floating-point parse) + 12 unit tests. - NEW VM-side helper coerce_double_idl_arg in vm/host/idl_coerce.rs (sibling to coerce_long_idl_arg, routes through coerce::to_number, rejects non-finite per WebIDL §3.10.5 restricted double). - 3 NEW [SameObject] wrapper caches: template_content_wrappers / datalist_options_wrappers / output_html_for_wrappers (Entity-keyed, mark-via-owner GC + Vm::unbind clear per lesson #195). - 1 NEW DOMTokenList source variant OutputHtmlFor + 10 engine-indep handler instances + 10 registry entries. - ConstraintValidation mixin install on HTMLOutputElement.prototype (extends is_constraint_validation_host_tag with "output"). - elidex-form::reset_form extension: clears OutputValueOverride and re-renders text content from OutputDefaultValue on form reset. Self-review IMPs applied pre-push: - IMP-1: output value setter snapshots descendant text into OutputDefaultValue on first value-mode entry (preserves implicit default for later form reset). - IMP-2: coerce_double_idl_arg rejects NaN / ±Infinity per WebIDL §3.10.5 restricted double (HTML §4.10.14/§4.10.15 use plain `double`). - IMP-3: meter value default is 0 then clamped to [min, max] (not defaulting straight to min, which diverged when min < 0 or min > 0). - meter high actual-high algorithm clamps to [low, max] (promotes when high < low) per HTML §4.10.15 step 3. 3 NEW + 3 re-noted = 6 deferred slots (within ≤3 NEW cap). 8815 workspace tests pass (vs 8727 baseline = +88: 78 T2d integration tests + 10 parse_double_or_default unit tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds the T2d “interactive” HTML element bundle to the JS VM by introducing per-tag HTMLElement subclass prototypes for <dialog>, <details>, <template>, <datalist>, <output>, <progress>, and <meter>, along with supporting DOMTokenList plumbing, numeric parsing/coercion helpers, ECS state, GC rooting, and tests.
Changes:
- Register 7 new HTML element prototypes in the VM (plus dispatch integration and GC prototype rooting).
- Add new engine-independent helpers/state:
parse_double_or_default, restricted-doublearg coercion, and new ECS components for<dialog>/<output>state. - Add
[SameObject]wrapper identity caches for<template>.content,<datalist>.options, and<output>.htmlFor, plus extensive VM integration tests.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| crates/script/elidex-js/src/vm/vm_api.rs | Clears new T2d [SameObject] wrapper caches on Vm::unbind() to avoid cross-DOM aliasing. |
| crates/script/elidex-js/src/vm/tests/tests_html_t2d_protos.rs | Adds test coverage for the 7 new prototypes and their key IDL surfaces/behaviors. |
| crates/script/elidex-js/src/vm/tests/mod.rs | Registers the new T2d test module. |
| crates/script/elidex-js/src/vm/object_kind.rs | Extends DomTokenListSource with OutputHtmlFor for <output>.htmlFor. |
| crates/script/elidex-js/src/vm/mod.rs | Adds prototype slots + wrapper caches to VmInner for the T2d bundle. |
| crates/script/elidex-js/src/vm/init.rs | Initializes new VmInner fields for T2d prototypes and caches. |
| crates/script/elidex-js/src/vm/host/validity_state.rs | Extends constraint-validation host tag checks to include <output>. |
| crates/script/elidex-js/src/vm/host/mod.rs | Wires new per-tag prototype modules into the host module tree. |
| crates/script/elidex-js/src/vm/host/idl_coerce.rs | Adds coerce_double_idl_arg implementing restricted-double WebIDL conversion. |
| crates/script/elidex-js/src/vm/host/html_template_proto.rs | Implements HTMLTemplateElement.prototype with lazy [SameObject] content. |
| crates/script/elidex-js/src/vm/host/html_progress_proto.rs | Implements HTMLProgressElement.prototype (value/max/position/labels). |
| crates/script/elidex-js/src/vm/host/html_output_proto.rs | Implements HTMLOutputElement.prototype including htmlFor, value/defaultValue state, and labels stub. |
| crates/script/elidex-js/src/vm/host/html_meter_proto.rs | Implements HTMLMeterElement.prototype numeric reflect/clamping + labels stub. |
| crates/script/elidex-js/src/vm/host/html_dialog_proto.rs | Implements HTMLDialogElement.prototype (open/returnValue/show/showModal/close). |
| crates/script/elidex-js/src/vm/host/html_details_proto.rs | Implements HTMLDetailsElement.prototype (open/name) with ToggleEvent deferred. |
| crates/script/elidex-js/src/vm/host/html_datalist_proto.rs | Implements HTMLDataListElement.prototype with [SameObject] options collection. |
| crates/script/elidex-js/src/vm/host/elements.rs | Extends element→prototype dispatch to include T2d tags. |
| crates/script/elidex-js/src/vm/host/class_list.rs | Adds VM-side DOMTokenList allocation + dispatch keys for <output>.htmlFor. |
| crates/script/elidex-js/src/vm/globals.rs | Registers T2d prototypes and installs ConstraintValidation mixin on <output>. |
| crates/script/elidex-js/src/vm/gc/roots.rs | Adds GC rooting for new prototype slots and new [SameObject] wrapper caches. |
| crates/script/elidex-js/src/vm/gc/collect.rs | Extends prototype root array (121→128) and prunes new wrapper caches during sweep. |
| crates/dom/elidex-form/src/submit.rs | Extends form reset algorithm with <output> reset hook (value-mode clearing). |
| crates/dom/elidex-dom-api/src/registry.rs | Registers outputHtmlFor.* DOMTokenList handlers in the DOM registry. |
| crates/dom/elidex-dom-api/src/lib.rs | Re-exports OUTPUT_HTML_FOR_* handler symbols. |
| crates/dom/elidex-dom-api/src/element/numeric_reflect.rs | Adds parse_double_or_default + unit tests for HTML floating-point parsing. |
| crates/dom/elidex-dom-api/src/class_list.rs | Adds engine-independent handler instances for outputHtmlFor.* token list ops. |
| crates/core/elidex-ecs/src/lib.rs | Re-exports new ECS components for dialog/output state. |
| crates/core/elidex-ecs/src/components.rs | Introduces IsModalDialog, DialogReturnValue, OutputDefaultValue, OutputValueOverride ECS components. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…t on form reset `reset_output_value_mode` previously wiped descendant children of every `<output>` reachable from the form during reset, including elements that had never entered value mode (no `OutputValueOverride` ever set). Per HTML §4.10.13 the descendant text content of a default-mode `<output>` IS the default value, so unconditional rewriting clobbered the implicit default whenever no explicit `OutputDefaultValue` snapshot existed. Gate the children replacement on `remove_one::<OutputValueOverride>` returning `Ok` — i.e. the element was actually in value mode. When the override is absent (pristine default mode), leave the descendant tree untouched. Adds regression test `output_form_reset_preserves_pristine_default_text` covering `<output>5</output>` — pre-fix this would surface as `value === ""` post-reset; post-fix `value === defaultValue === '5'`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…isation through ES Number::ToString
`<progress>.value` / `<progress>.max` / `<meter>` numeric setters were
serialising the coerced `f64` via Rust's `format!("{n}")` before
writing the content attribute. Rust's `Display` differs from
ECMAScript Number::ToString (ES2020 §7.1.12) at `-0` (Rust → "-0",
ES → "0") and across exponent edge cases, so reflected attribute
values diverged from the value the same JS would have observed
through `String(n)`.
Route through `coerce::to_string(ctx.vm, JsValue::Number(n))` instead;
the helper invokes the canonical ES Number-to-string algorithm and
returns a `StringId` directly, so the rewrite is also one fewer
intern call per setter.
3 sites total — confirmed exhaustive via
`grep 'format!("{n}")' crates/script/elidex-js/src/vm/host/`. Doc
comments updated to match the new path.
Adds two regression tests (`progress_value_setter_serialises_negative_zero_as_zero`,
`meter_value_setter_serialises_negative_zero_as_zero`) — pre-fix would
serialise as `"-0"`, post-fix as `"0"`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Forwards setter in module doc The pre-fix module doc claimed the `[PutForwards=value]` rewrite was handled by the engine assignment path and that the getter "only needs to surface" the cached DOMTokenList wrapper, but the file actually ships an explicit `output_set_html_for` accessor that ToString-coerces the assigned value and writes the `for` content attribute directly (mirroring the `<link>.sizes` precedent). Update the prose to describe the actual implementation so readers don't search for a non-existent engine-side rewrite hook. Doc-only fix; behaviour and tests unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mControlKind, not TagType `reset_form` already iterates only entities that carry `FormControlState`, so the `<output>` distinguisher can read `fcs.kind == FormControlKind::Output` directly instead of running an independent `TagType` lookup + ASCII-CI string compare per control. This drops one ECS lookup per control on the reset hot path and keeps the per-kind dispatch consistent with the `FormControlState::from_element` classification (the source of truth for "this entity is an `<output>` form-control" anywhere else in elidex-form). The classification read is captured *before* the mutable `FormControlState` borrow so the get/get_mut borrow scopes don't overlap; removed the now-unused `is_output_element` helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 11, 2026
send
added a commit
that referenced
this pull request
May 17, 2026
…solution (HTML §4.10.5.1.16) (#195) * #11-tags-T2d-input-list: implement <input>.list IDREF → <datalist> resolution (HTML §4.10.5.1.16) Closes the §H-7k T2d defer slot. The previous stub returned `null` unconditionally because the T2d datalist surface was not yet shipped; PR #181 landed `<datalist>` infrastructure, so the IDREF back-ref is now implementable. Engine-indep resolver `resolve_input_list` lives in `elidex-form/src/input.rs` sibling to `resolve_label_for` (HTML §4.10.4) — same shape: read attr, walk input's tree root-inclusive, filter-during-walk for first matching datalist. VM-side getter is pure marshalling: brand check via `require_input_receiver`, call resolver, wrap via `wrap_entity_or_null`. - Tree scope honors shadow boundaries — nested shadow subtrees within the same root are excluded per spec "same tree" wording. Cross-tree resolution tracked at `#11-form-elements-cross-tree`. - Tag match ASCII-CI (HTML §3.2.6.5); id match case-sensitive (§6.13.2). - Filter-during-walk: a `<div id="x">` appearing earlier in tree order before `<datalist id="x">` does NOT poison the lookup (spec: "first element of type HTMLDataListElement"). - Identity-stable across reads via `create_element_wrapper`'s per-entity cache — HTML §4.10.5.1.16 IDL is not `[SameObject]` but matches Chrome / Firefox behaviour. - HTML-namespace filter currently omitted because `TagType` implies element-hood in elidex's HTML-only model; defer slot `#11-element-namespace-tracking` tracks the tightening. - 12 engine-indep tests + 6 VM tests cover spec edge cases (no-attr / empty / id-not-exist / target-not-datalist / matched / duplicate-id tree-order / case-sensitive / non-datalist-then-datalist / detached / self-ref / no-whitespace-trim / empty-id-skip-then-match / identity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#11-tags-T2d-input-list): PR195 Copilot R1 — gate <input>.list on input type applicability (HTML §4.10.5.1.16) Per WHATWG HTML §4.10.5.1.16: the `list` IDL attribute does not apply to input types `hidden` / `checkbox` / `radio` / `file` / `submit` / `image` / `reset` / `button` / `password`. The getter must return null for these types even when a matching `<datalist>` exists in the same tree. - Add `FormControlKind::list_applies()` predicate (sibling to `readonly_applies` shape) listing the allow-listed kinds. - Add `input_list_applies_to_type(dom, entity)` helper with the `FormControlState.kind` → raw `type` attribute fallback pattern from `label.rs::is_labelable_element`. - Gate `resolve_input_list` on it (early null before any IDREF walk). - 9 new engine-indep regression tests covering each excluded type plus the FormControlState-path and ASCII-CI attr-path coverage. - 1 new VM test looping over all 8 excluded types. Image type is documented as a pre-existing FormControlKind coverage gap (falls back to TextInput via `from_type_str`) — out of scope here. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#11-tags-T2d-input-list): PR195 Copilot R2 — content-attr-direct applicability check, includes image type (HTML §4.10.5.1.16) Three R2 findings, single root cause: `input_list_applies_to_type` preferred the cached `FormControlState.kind` over the live `type` content attribute, and routed the exclusion check through `FormControlKind::from_type_str` which collapses `"image"` onto `TextInput`. Two interlocking bugs: 1. `setAttribute('type', 'hidden')` (or `image` / `checkbox` / ...) only mutates `Attributes`; `state.kind` lags until a type-change sync pass. Preferring the cached kind let stale state mask the mutation and incorrectly resolve a datalist. 2. `<input type=image>` falls back to `TextInput` via `from_type_str`, which was on the allow-list — wrongly admitting image. Fix: rewrite `input_list_applies_to_type` to read the `type` content attribute directly (spec source of truth per HTML §4.10.5.1.16) and match against the literal exclusion set { hidden, checkbox, radio, file, submit, image, reset, button, password }. Dropped the orphan `FormControlKind::list_applies` predicate (its sole caller was the removed code path; the from_type_str/image gap made it unmaintainable as a public predicate). - Stale-state regression test (`resolve_input_list_ignores_stale_form_control_state_when_attr_excludes`) attaches a `TextInput`-kind state to an `<input type=hidden>` and asserts the attribute wins. - Dedicated `resolve_input_list_returns_none_for_image_type` test. - `image` added to the existing button-types loop + the VM inapplicable-types loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#11-tags-T2d-input-list): PR195 Copilot R3 — split input.rs tests to sibling input_tests.rs (1000-line convention) R3 finding: `input.rs` reached 1075 LoC, violating the project's ~1000-line file convention documented in `cleanup-1000-line-files-tranche-3-complete.md`. Move the entire `#[cfg(test)] mod tests` body to a new sibling file `input_tests.rs` and include it via the established `#[cfg(test)] #[path = "input_tests.rs"] mod tests;` pattern (mirror of `lib.rs:743` precedent). Post-split: `input.rs` is 404 LoC (production-only), `input_tests.rs` is 671 LoC (test-only). Test count and contents unchanged — 55 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
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.
Summary
D-7 of M4-12 §D — slot `#11-tags-T2d-interactive`. 7 distinct `HTMLElement` subclass prototypes added (no shared prototypes; each tag has its own dispatch arm in `tag_specific_t2d_prototype`).
Per-element surface
Details
` — `open` boolean reflect + `name` string reflect. ToggleEvent fire deferred to `Bump the rust-dependencies group across 1 directory with 16 updates #11-tags-T2d-details-toggle-event`.Infra additions
Self-review IMPs applied pre-push
Defer ledger (3 NEW + 3 re-noted = 6 entries; ≤3 NEW cap exactly)
Details
.open` setter must fire ToggleEventImplementation-timing summary: §E-2 round-bound 2/6, demand-driven calendar 4/6, carry-forward 0/6 (`deprecated-attr-sweep` not listed per `policy-deprecated-and-annex-b-out-of-scope.md` — deprecated HTML attrs are out-of-scope per `docs/design/ja/14-script-engines-webapi.md`, NOT defer slots).
Test plan
Details
` open setter does NOT fire ToggleEvent (defer-proof, full prototype-chain absence walk per lesson chore: codify Design philosophy + add /elidex-review pre-push skill #204).🤖 Generated with Claude Code