feat(plugins): pluggable createT/Entity — plugin() + contributed methods + typing#67
Merged
Merged
Conversation
Adds the input-agnostic plugin scaffolding on top of solidjs-community#64's Pointer engine: - Context.owner + initializedPlugins - Plugin type; createT(catalogue, plugins) + <Entity plugins> composition - creation-gated plugin setup (initPlugins, src/plugin.ts) — off the per-attach scene-graph hot path (applySceneGraph stays byte-identical to solidjs-community#64) - public Pointer.dispatch(name) + Pointer/Plugin exports Perf-validated against three-bench (fractal-5x5 at parity). This is an interim seam; the solidjs-community#37 plugin API (inferred types, class-filter, meta.ctx) supersedes the Plugin shape next.
…ves per-element (gated)
…erred-typed props createT captures the plugin tuple type and threads it into Props<T, TPlugins> via PluginPropsOf, so a contributed method's first-param type becomes the element prop type. Generics ported from solidjs-community#37 with readonly-tuple constraints; the type-param default is the constraint (readonly Plugin[]) not [] so inference isn't pre-empted.
…n + readonly tuple Per-element typed contributed props now infer from the JSX plugins prop AND enforce (reject bogus props). Root cause of the prior failure (investigated empirically): - a const-inferred JSX array is a readonly tuple -> mutable Plugin[] constraint rejected it (=> readonly Plugin[] constraints throughout); - the recursive PluginMerge is unevaluable during inference (=> UnionToIntersection); - PluginPropsOf buried in Props's Overwrite defeats inference (=> Props stays plugin-free; PluginPropsOf is intersected directly at createT's proxy and <Entity>). createT namespace path keeps working via function-arg inference.
…ibuted props) DRYs the 'Props<T> & Partial<PluginPropsOf<InstanceOf<T>, TPlugins>>' intersection used by createT's element proxy and <Entity>. A plain top-level intersection alias — keeps PluginPropsOf un-buried so TPlugins stays inferable; verified inference + enforcement unchanged (lint:types clean, @ts-expect-error bogus-prop still holds).
BaseProps<T> = raw three-instance props; Props<T, TPlugins> = base + plugin-contributed props. lint:types clean, full suite + plugin inference/enforcement tests green.
…t for plugin code) - Data.ctx: the element's mount-site Context, set by useProps for plugged elements (at creation, not attach — contributed methods run during applyProp, before a top-level element attaches). Gated by plugins.length so no-plugin elements pay nothing. - Context.initializePlugin(token, fn): runs fn once per context (private dedup set), the home for a plugin's one-time per-context setup. Replaces the Phase-1 initializedPlugins field; removes the dead initPlugins (setup-object remnant). Plugin code reaches the store via getMeta(element).ctx.
No Phase-1 remnants remain (initPlugins/setup-object removed in PT7). plugin() is the public creator; Plugin type already exported; advanced Props types via the S3 namespace.
commit: |
This was referenced Jun 5, 2026
Open
bigmistqke
added a commit
that referenced
this pull request
Jun 6, 2026
…ride typing (#68) ## Summary The #66 events rewrite and the #67 plugin system both shipped without their doc updates — the site was describing an API that no longer exists and a feature that was never documented. This clears that debt, adds an interactive plugins tour chapter, and fixes one plugin-typing bug the demo surfaced. ## Stale docs corrected (#66) - **`events/overview`** — drops the `onMouse*` family (removed in #66; only `onPointer*` / `onClick` / `onWheel` remain) from the supported, stoppable, and non-stoppable lists and the event-applicability table. - **`utilities/raycasters`** — the page documented `EventRaycaster.update(event, context)`, which #66 deleted. Rewritten around the real `cast(registry, context)` interface, with a corrected custom-raycaster example and new sections for `ScreenRaycaster` and `ControllerRaycaster`. ## Plugins documented (#67) - **`utilities/plugin`** — new API page: what a plugin is, the three `plugin()` forms (global / class-filtered / type-guard), registration via `createT` and `<Entity plugins>`, and the typed contributed-prop guarantee. - **Tour chapter 09 "Plugins"** — builds a `lookAt` plugin from the "props set properties, they don't call methods" motivation, ending in a live demo: a field of cones that turn to face the pointer. Inserted before the WebGPU peek (renumbered 09 → 10 so the peek stays the finale); chapter 08's close now hands off to it. ## Library fix: contributed props override native members Writing the `lookAt` demo surfaced a bug in the #67 plugin typing. A plugin intercepts its prop at runtime (`applyProp` checks `pluginMethods` first), so a contributed prop should be able to reuse a native member's name — e.g. `lookAt` driving `Object3D.lookAt`. But `Props` *intersected* the contributed type with the base, producing `nativeMethod & V`, which no value satisfies, so `<T.Mesh lookAt={vec} />` failed to type-check even though it worked at runtime. `Props` now drops the contributed keys from `BaseProps` before adding them, so the contributed type *replaces* the native one — matching the runtime. Guarded so the common no-plugins case is untouched (the loose default `readonly Plugin[]` has `length: number`; only a real inferred tuple contributes override keys). Regression test added. This is what lets the tour demo use the natural `lookAt` prop name. ## Test plan - [x] `pnpm build` + `pnpm lint` (circular + eslint + types) green. - [x] Browser suite green, incl. a new test that a `Vector3` is assignable to a contributed `lookAt` prop. - [x] lookAt demo verified in-browser — cones point at the cursor and track it. - [x] Dev server: no more mid-session re-optimize / reload after the `optimizeDeps.include` change.
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.
Act 2 of 3 (follows #66, now merged). Adds the plugin seam that Act 3 (XR as a plugin) builds on. Squash-merges to one commit on
next-cleanup.Summary
Adds a typed plugin system so
createT/<Entity>can be extended with contributed props and methods, with no overhead when unused:plugin()— global / class-filter / type-guard forms, plus the type machinery.resolvePluginMethods+applyProprouting — contributed methods run instead of instance assignment;createTresolves plugins per element, gated byplugins.length(a no-plugin namespace never touches the path).Props<T, TPlugins>surfaces contributed methods as inferred-typed props; enforced<Entity plugins>viaUnionToIntersectionover a readonly tuple.Context.initializePlugin+meta.ctxgive plugin code its mount-site context.plugin()from the package entry.Commits (10)
minimal plugin seam foundation·plugin() forms + type machinery·resolvePluginMethods·applyProp routing + per-element resolve (gated)·Props<T, TPlugins>·typed+enforced <Entity plugins>·PropsWithPlugins helper·rename Props->BaseProps·Context.initializePlugin + meta.ctx·export plugin()Test plan