Skip to content

fix(react): preventDefault.bind in the normalize - #64

Merged
ivanbanov merged 2 commits into
mainfrom
react
Aug 18, 2026
Merged

fix(react): preventDefault.bind in the normalize#64
ivanbanov merged 2 commits into
mainfrom
react

Conversation

@ivanbanov

Copy link
Copy Markdown
Member

Extracts everything react-related from the solid branch (#32) so that PR stays solid-only:

  • fix(react) — the payload adapters handed out a detached native preventDefault, which throws "illegal invocation" when called; they now bind it to its event. Regression-tested.
  • refactor(sandbox) — the react demo's inline style objects move into sandbox/shared/src/styles.css and the markup renders classes. The solid demo on feat(solid): Solid 2 integration #32 imports the same file, so the two web sandboxes share one look.

The files are byte-identical with the solid branch, so rebasing #32 onto main after this merges drops them from its diff cleanly.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
dunky-state-machine Skipped Skipped Aug 18, 2026 8:24pm

ivanbanov and others added 2 commits August 18, 2026 22:24
A detached native preventDefault throws "illegal invocation" — the payload
adapters now hand out a closure that calls it with the event as `this`.
Extracted from the solid branch (#32), where it landed as part of e51bea0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sandbox/shared/src/styles.css replaces the inline style objects — the react
demo renders classes, and the solid demo (#32) imports the same file, so the
two web sandboxes share one look. Kept byte-identical with the solid branch
for a clean rebase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ivanbanov added a commit that referenced this pull request Aug 18, 2026
Same behavior as the arrow-closure wrapper, one line — mirrors the react
normalizer on #64.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ivanbanov ivanbanov changed the title fix(react): react-side changes extracted from the solid branch fix(react): preventDefault.bind in the normalize Aug 18, 2026
@ivanbanov
ivanbanov merged commit 6f566bf into main Aug 18, 2026
7 of 8 checks passed
@ivanbanov
ivanbanov deleted the react branch August 18, 2026 20:28
ivanbanov added a commit that referenced this pull request Aug 21, 2026
* feat(solid): add @dunky.dev/state-machine-solid integration

A first-class Solid bindings target (not a React re-export): useMachine
mirrors the connector snapshot into a createStore via reconcile for
fine-grained updates, runs the lifecycle through onMount/onCleanup, keeps
props fresh with a tracked setProps effect, and runs each ComponentEffect
as its own dep-tracked createEffect. useSelector returns a Solid accessor.
normalize maps the agnostic bindings to Solid DOM props (onInput,
onDblClick, tabindex) and mergeProps applies Solid's class concat +
single-object style merge.

Also split the tsconfig setup into a tsconfig/ folder (base/react/solid/all)
so JSX is a per-project concern, since the repo now has both React and
Solid JSX. Wires Solid into tsdown, the vitest solid project, docs, and a
changeset.

Co-authored-by: Claude <noreply@anthropic.com>

* feat(sandbox): add sandbox/solid cmdk demo

A Solid renderer for the shared ⌘K command-palette machine, mirroring
sandbox/react. Drives the same @sandbox/cmdk-core machine + connect through
the Solid bridge: api is a fine-grained store read directly in JSX, Show/For
for control flow, a createEffect for focus-on-open, and the same cmdkShortcut
ComponentEffect tuple the React sandbox uses. Vite + vite-plugin-solid,
aliasing the workspace TS sources.

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(solid): adopt the substrate-prefix name and manifest conventions

Renames the package to @dunky.dev/solid-state-machine (the convention from
#43) across the manifest, changelog, tsconfig path alias, sandbox, and
changeset. Conforms the manifest to main: ship src/ in the published files
(#53), pin internal workspace deps exact (#59), and add the bindings devDep
the translation contract needs. The changeset now also states the version
policy: solid-js ^1.6 today; Solid 2.0 lands as a separate major once stable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(solid): adopt the bindings translation contract and current target APIs

Brings the Solid target up to main's contract changes:

- normalize maps are vocabulary-typed (HandlerTargets/AttrTargets) and
  exported, with keyed lookups — a typo or unknown key is now a compile
  error (#57/#60). The header carries the non-mechanical translation
  rationale ACCESSIBILITY.md asks for (focusable -> tabindex 0/-1,
  disabled -> aria-disabled per APG).
- the shared describeVocabularyAccounting fixture runs against the solid
  maps — the last target missing the conformance suite.
- ComponentEffects is folded into ComponentEffect; the effects param is a
  plain ComponentEffect[] (#49).
- mergeProps is generic over the consumer's props, cast-free at call
  sites, with the expectTypeOf regression test (#51).
- tests import the published entry (@dunky.dev/solid-state-machine)
  instead of ../src, matching every other target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sandbox): harden the solid demo and align the demo copy

- dedupe solid-js in the solid sandbox's vite config: the package alias
  points inside packages/solid, which carries its own solid-js devDep, so
  version skew would load two runtimes and silently kill reactivity.
- clear the input ref on the Show branch's disposal — the closed palette
  kept a detached <input> alive until the next open.
- sandbox README now tells the four-substrates story: solid in the tree
  and run instructions, and the lifecycle-hook claim rewritten (three
  targets share the React hook; Solid brings its own bridge).
- react demo copy mentions the Solid version, its page title
  disambiguates (cmdk - React), and a user-visible Palette typo is fixed
  in both apps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: add solid to every target enumeration and fix the solid docs page

Solid existed in no doc a contributor or consumer reads first:

- root README target diagram gains the Solid box; AGENTS.md,
  ARCHITECTURE.md, and the ACCESSIBILITY.md hidden fan-out example now
  enumerate all four targets.
- the solid docs page examples were silently broken: string-shorthand
  transitions the core no-ops, and a bare config object instead of
  setup.infer().createMachine — both now mirror the react page. Also:
  Show instead of the React && idiom, the mergeProps import line, the
  stale mapping table replaced with the source link (the 521440e
  convention), cross-target links, and the solid-js version-support note.
- api/effects.mdx no longer states the React-only hooks rule as
  universal and points at the Solid bridge alongside React Native.
- the package README gains the Quick start, the flow diagram, the
  current heading conventions, and the Solid version support section
  (^1.6 now; 2.0 as a separate major once stable, with the migration
  mapped).
- drop a trailing comma in the root tsconfig left by the merge
  resolution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(react,solid): bind the adapted payloads' preventDefault to its event

normalize() copied the native event's preventDefault onto the
ChangePayload/WheelPayload detached, so calling it threw Illegal
invocation. The payload now carries a closure bound to the event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(solid): run ComponentEffect bodies untracked

A prop the effect body merely read became a hidden dependency and
re-ran the effect. The authored deps list is now the whole re-run
contract, matching the React target's dep array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(solid): target Solid 2.0 as the first-class peer

Port the bridge to the 2.0 primitives: root-level createStore/reconcile,
two-phase createEffect(compute, apply), onSettled for the lifecycle, and
@solidjs/web for JSX. Peer range is ^2.0.0-rc.0; 1.x is unsupported (2.0
removed the surface the bridge stands on, so the majors are version-split
like the rest of the Solid ecosystem).

Work around a solid-js 2.0.0-rc.0 bug: reconcile corrupts a store node when
it replaces a function-valued property, halting reactivity on the next
tracked read. The bridge reconciles a view holding the previous function
identities and writes the fresh ones through plain draft assignments, all in
one commit. Regression-tested; remove once fixed upstream.

The solid vitest project and tsconfig now live in packages/solid so the root
workspace carries no Solid dependencies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update app.tsx

Signed-off-by: Ivan Banov <ivanbanov@gmail.com>

* Update app.tsx

Signed-off-by: Ivan Banov <ivanbanov@gmail.com>

* Update README.md

Signed-off-by: Ivan Banov <ivanbanov@gmail.com>

* refactor(sandbox): share one stylesheet between the React and Solid demos

Both apps rendered the same command palette with duplicated inline style
objects. Move the shared look into sandbox/shared/styles.css and swap
style={} for class/className — each app now supplies only markup, so a
future third DOM sandbox has one file to add, not another copy of the
same styles.

Trims the Solid docs page of comments that just restated the adjacent
code.

* style(sandbox): rejoin the lead line oxfmt wraps

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(sandbox): move the shared stylesheet into shared/src

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(solid): stringify boolean aria-* values in normalize

Solid 2.0 renders a boolean attribute as presence/absence, so
aria-expanded={false} disappeared and aria-modal={true} rendered empty.
ARIA states are literal "true"/"false" tokens — serialize them explicitly.
Found by the solid-dialog binding in dunky-dev/ui#44.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(solid): shorten the reconcile-workaround comment

* refactor(solid): rebind preventDefault with bind

Same behavior as the arrow-closure wrapper, one line — mirrors the react
normalizer on #64.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(solid): target solid-js 2.0.0-rc.1 and drop the reconcile workaround

rc.1 fixes the store corruption when reconcile replaces a function-valued
property, so the function-leaf detour (stableFunctionView /
restoreFunctionLeaves) is gone — the bridge reconciles the snapshot
directly. Peer range moves to ^2.0.0-rc.1; the regression test that guarded
the detour now passes against the plain path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(solid): close the bridge's behavioral gaps found in review

- useSelector: pin the compute-form seed (a selected function comes back
  by identity, never invoked — the createSignal function-arg hazard) and
  that a disposed owner stops evaluating its selector (onCleanup(off)).
- useMachine: pin the stop half of the lifecycle (reactions stop firing
  after unmount), that a ComponentEffect's cleanup runs BEFORE its re-run
  on a dep change, and the negative half of the fine-grained claim (an
  unrelated field change does not wake a reader).
- mergeProps: pin the undefined-consumer early return and the documented
  string-style fall-through to library-wins.

Both regression guards were mutation-verified: reverting the seed to the
value form and dropping onCleanup(off) each fail exactly their new test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(dom): extract the shared DOM translation into @dunky.dev/state-machine-dom

The aria-* attribute projection and the payload adapters were byte-identical
in the React and Solid normalizers, and had already drifted once (the
preventDefault bind fix landed in one but not the other). They now live once
in packages/dom; each target keeps only what genuinely differs — its handler
prop names (onChange/onDoubleClick vs onInput/onDblClick), the focusable →
tabindex casing, and its value serialization (React passes ARIA booleans
through, Solid stringifies them).

Payload construction is pinned once in the dom package's tests; the target
suites keep a wiring proof each (normalize wraps the handler with its
adapter) plus the vocabulary accounting. No consumer-facing API change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Signed-off-by: Ivan Banov <ivanbanov@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant