Skip to content

feat(solid): Solid 2 integration - #32

Open
ivanbanov wants to merge 22 commits into
mainfrom
solid
Open

feat(solid): Solid 2 integration#32
ivanbanov wants to merge 22 commits into
mainfrom
solid

Conversation

@ivanbanov

Copy link
Copy Markdown
Member

Summary

Adds @dunky.dev/state-machine-solid — a first-class Solid bindings target (not a React re-export), plus a sandbox/solid cmdk demo and a docs page.

The bridge is implemented with Solid's own reactivity:

  • useMachine mirrors the connector snapshot into a createStore via reconcile → fine-grained updates (reading api.isOpen in JSX tracks only that field; no useSyncExternalStore)
  • lifecycle via onMount/onCleanup; props kept fresh with a tracked setProps effect; each ComponentEffect runs as its own dep-tracked createEffect
  • useSelector returns a Solid accessor
  • normalize → Solid DOM props (onInput, onDblClick, tabindex); mergeProps → Solid class concat + single-object style merge

The same connect + machine config run unchanged across React, Solid, React Native, and OpenTUI.

Also in this PR

  • tsconfig refactor into a tsconfig/ folder (base / react / solid / all) so JSX is a per-project concern now that the repo has both React and Solid JSX.
  • sandbox/solid — a Solid renderer for the shared @sandbox/cmdk-core palette, mirroring sandbox/react.
  • Docs page libs/solid + sidebar entry; changeset.

Test plan

  • pnpm typecheck (tsc -b tsconfig/all.json) clean
  • 352 tests pass (45 new Solid tests)
  • all 7 packages build (publint clean)
  • lint + format clean
  • sandbox/solid builds and verified live in-browser: open, type-to-filter, arrow-nav, Enter-to-select (fires onSelect + closes), ⌘K shortcut, Escape-to-close — all working

Note: one bridge-correctness fix — the connector must be seeded with a plain { ...props } snapshot, never the live Solid props proxy, or its value-dedup compares the proxy against itself and never wakes.

🚧 WIP — opening early for review of the bridge approach.

🤖 Generated with Claude Code

ivanbanov and others added 2 commits June 23, 2026 21:58
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>
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>
@ivanbanov ivanbanov added the 🚧 WIP This will not be worked on label Jun 23, 2026
@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

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

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

ivanbanov and others added 5 commits August 17, 2026 22:59
Resolves tsconfig.json in favor of the tsconfig/ split and renames the
react/native/opentui path aliases to main's substrate-prefix convention.

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

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>
…et 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>
- 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>
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>
ivanbanov and others added 2 commits August 18, 2026 00:00
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>
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>
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>
@ivanbanov ivanbanov changed the title feat(solid): Solid integration feat(solid): Solid 2 integration Aug 18, 2026
@ivanbanov ivanbanov removed the 🚧 WIP This will not be worked on label Aug 18, 2026
Signed-off-by: Ivan Banov <ivanbanov@gmail.com>
Signed-off-by: Ivan Banov <ivanbanov@gmail.com>
Signed-off-by: Ivan Banov <ivanbanov@gmail.com>
…emos

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.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ivanbanov and others added 2 commits August 18, 2026 17:56
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>
ivanbanov added a commit that referenced this pull request Aug 18, 2026
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>
ivanbanov added a commit that referenced this pull request Aug 18, 2026
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>
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 added a commit that referenced this pull request Aug 18, 2026
* fix(react): bind the adapted payloads' preventDefault to its event

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>

* refactor(sandbox): move the react demo styles into a shared stylesheet

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>

---------

Co-authored-by: Claude Fable 5 <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