Skip to content

Roll back signals and batching and reimplement them #113

Description

@LeaVerou

#91 and #102 caused several regressions (and uncovered some existing bugs). Some of which we fixed with subsequent PRs, but generally piling fixes on top of existing broken code tends to produce worse results and higher complexity than going back, fixing the spec, and reimplementing.

So I think we need to bite the bullet and do just that.

What makes the rollback tricky is that once we discovered the regressions, we added tests for them. These need to stay. FakeElement too (or use a library that does it properly). But simply keeping all the tests from the rewrite is a no-go, since many depend on internal implementation details of the rewrite (a problem in itself).
Also, there are pre-existing bugs we discovered and fixed since then.

I propose this plan:

  • We roll back to e344b1f (last commit before signals)
  • Let's create a list of requirements here for behavior we want to preserve and ask Claude to create tests for them
  • Fix any pre-existing bugs, and add tests to prevent regressions
  • Then ask Claude to implement coalescing first ([Props] Bunch up property changes #51) then signals ([Props] Use signals #79). I'll edit the issue descriptions to include more details, so we can point Claude to the issues directly.

Bugs to fix before implementing signals

We should also add tests for these behaviors.

Behavioral baseline (tests to add before reimplementing)

First, we should find a library instead of implementing FakeElement ourselves and add it as a dev dependency. This cannot be a new problem.

These should all pass on e344b1f. They're the regression net protecting consumer-observable behavior through the signals refactor.

Basic API (mostly already present in test/Prop.js constructor() group — keep what's there, expand only where gaps exist):

  • Construction normalizes type, default, reflect, etc.
  • reflect: true | false | "name" | { from, to } parses correctly into fromAttribute / toAttribute.
  • Props.observedAttributes derives from reflecting props.
  • Props.add(name, spec) and Props.add({...}) both work.

Sync IDL semantics to prevent #111

  • el.x = v; el.x returns v synchronously.
  • el.x = 1; el.x = 2; el.x = undefined fires three propchange events in order, synchronously
  • After el.x = v, reading any prop derived from x (via get, convert, or function default) returns the new derived value on the same turn.
  • After el.setAttribute("x", "foo"), el.x === "foo" synchronously.

Defaults:

Reflection:

  • Property write reflects to the attribute.
  • Pre-set attribute coerces into the typed property on mount.
  • reflect: { from, to } honors asymmetric attribute names.

Computed props (get):

  • get returns the derived value on read.
  • Writing a tracked dep re-runs get and fires propchange for the derived prop.

Other:

  • A value set as a data property on an element before its prototype accessor exists is preserved, goes through parse correctly, and any props depending on it have correct values
  • Setting different values that are the same after conversion do not fire propchange

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions