solid2-agent-kit 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Supported agents: **Cursor** and **Claude Code**.
|
|
|
16
16
|
| Always-applied hard rules | `.cursor/rules/solid-2.mdc` (glob-attached to `*.tsx`/`*.jsx`) | managed block in `CLAUDE.md` | 23 hard rules + banned Solid 1.x API table + eslint-plugin-solid lint-heritage table. Injected without the agent having to decide to read anything |
|
|
17
17
|
| Agent skill | `.cursor/skills/solid-2/` | `.claude/skills/solid-2/` | Execution-model primer, decision tables, canonical patterns, a “still runs / write the other form” table, review checklist, official-doc URL index |
|
|
18
18
|
| Shared agent context | managed block in `AGENTS.md` | same (Claude Code users can reference it from `CLAUDE.md`) | Core principles and pointers, always in context |
|
|
19
|
-
| Mechanical gate | `solid2-kit check` | same | Whole-file regex guard over `src/**/*.{ts,tsx}`: fails on props destructuring (including multi-line signatures), `{ ...props }` rest copies, `{list().map(...)}` in JSX, React imports/hooks/JSX props/`React.lazy`, Solid 1.x imports/APIs/components/JSX namespaces, `vite-plugin-solid`, old-router `<Route>`/`<HashRouter>`, `MetaProvider`, `Context.Provider`, camelCase style keys, `key` props, `value()!` hand-narrowing, hand-rolled loading signals,
|
|
19
|
+
| Mechanical gate | `solid2-kit check` | same | Whole-file regex guard over `src/**/*.{ts,tsx}`: fails on props destructuring (including multi-line signatures), `{ ...props }` rest copies, `{list().map(...)}` in JSX, React imports/hooks/JSX props/`React.lazy`/`useOptimistic`/`onDoubleClick`, Solid 1.x imports/APIs/components/JSX namespaces, `vite-plugin-solid`, old-router `<Route>`/`<A>`/`<HashRouter>`/`<FileRoutes>`, `MetaProvider`, `Context.Provider`, camelCase style keys, `key` props, `value()!` hand-narrowing, hand-rolled loading signals, `=== undefined` readiness branches, SolidStart leftovers (`@solidjs/start`, `h3`, `createAsync`, `useSubmission`, `"use client"`), Next.js imports/`useRouter`/`typeof window`, `history.pushState`, and `render(<App />)` (pass a function) |
|
|
20
20
|
|
|
21
21
|
Managed blocks are delimited with `<!-- solid2-agent-kit:*:start/end -->` markers; everything
|
|
22
22
|
outside them is yours. Kit-owned files (`solid-2.mdc`, the skill directories) are overwritten
|
|
@@ -24,8 +24,8 @@ on sync.
|
|
|
24
24
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
27
|
-
GitHub is the canonical distribution channel (
|
|
28
|
-
|
|
27
|
+
GitHub is the canonical distribution channel (consumers pin a commit through their
|
|
28
|
+
lockfile); the package is also published to npm as `solid2-agent-kit`:
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
31
|
# install guidance for both Cursor and Claude Code (default)
|
|
@@ -55,8 +55,8 @@ resolve the project-local bin, so the bare name is safe there):
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Update later with `bun update solid2-agent-kit` (or the npm/pnpm equivalent) followed by
|
|
58
|
-
`npx solid2-agent-kit sync`. Outside a project that has the kit installed,
|
|
59
|
-
`npx github:...` form —
|
|
58
|
+
`npx solid2-agent-kit sync`. Outside a project that has the kit installed, prefer the
|
|
59
|
+
`npx github:...` form — it always tracks this repo directly.
|
|
60
60
|
|
|
61
61
|
## Why not eslint-plugin-solid?
|
|
62
62
|
|
|
@@ -96,6 +96,14 @@ scripts/check-docs-drift.mjs weekly docs cross-check
|
|
|
96
96
|
tests/ mechanical-gate fixtures (`npm test`)
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
## Publishing (npm)
|
|
100
|
+
|
|
101
|
+
Bump `version` in `package.json` on `main` and CI publishes automatically
|
|
102
|
+
(`.github/workflows/publish.yml`) — it skips when the version already exists on npm.
|
|
103
|
+
Auth is npm Trusted Publishing (OIDC), so no NPM_TOKEN secret exists or is needed.
|
|
104
|
+
One-time setup on npm: package → Settings → Trusted Publisher → GitHub Actions, owner
|
|
105
|
+
`lightsound`, repo `solid2-agent-kit`, workflow `publish.yml`, environment left empty.
|
|
106
|
+
|
|
99
107
|
## License
|
|
100
108
|
|
|
101
109
|
MIT
|
package/bin/solid2-kit.mjs
CHANGED
|
@@ -177,7 +177,7 @@ const CHECKS = [
|
|
|
177
177
|
{
|
|
178
178
|
id: 'react-hook',
|
|
179
179
|
pattern:
|
|
180
|
-
/(?<![.\w])use(?:State|Effect|Memo|Ref|Callback|Reducer|LayoutEffect|Transition|SyncExternalStore|ImperativeHandle|Id|DeferredValue|InsertionEffect)\s*\(/g,
|
|
180
|
+
/(?<![.\w])use(?:State|Effect|Memo|Ref|Callback|Reducer|LayoutEffect|Transition|SyncExternalStore|ImperativeHandle|Id|DeferredValue|InsertionEffect|Optimistic|ActionState|FormStatus)\s*\(/g,
|
|
181
181
|
message: 'React hook. See the primitive mapping in the solid-2 rules.',
|
|
182
182
|
},
|
|
183
183
|
{
|
|
@@ -281,15 +281,68 @@ const CHECKS = [
|
|
|
281
281
|
},
|
|
282
282
|
{
|
|
283
283
|
id: 'solid1-router',
|
|
284
|
-
pattern: /<(?:HashRouter|MemoryRouter|Route|Navigate)\b/g,
|
|
284
|
+
pattern: /<(?:HashRouter|MemoryRouter|Route|Navigate|A|FileRoutes|StartClient|StartServer)\b/g,
|
|
285
285
|
message:
|
|
286
|
-
'Solid Router 0.x/1.x JSX. Define routes with createRouter({ routes }) and plain <a href={Router.paths...}>.',
|
|
286
|
+
'Solid Router 0.x/1.x or SolidStart JSX. Define routes with createRouter({ routes }) / fileRoutes(pageRoutes) and plain <a href={Router.paths...}>.',
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
id: 'meta-provider',
|
|
290
290
|
pattern: /<MetaProvider\b/g,
|
|
291
291
|
message: 'Solid Meta 1.x has no provider. Render <Title>/<Meta>/<Link> from "@solidjs/meta" anywhere.',
|
|
292
292
|
},
|
|
293
|
+
{
|
|
294
|
+
id: 'solidstart-import',
|
|
295
|
+
pattern: /from\s+['"](?:@solidjs\/start|vinxi|h3|nitropack)(?:['"]|\/)/g,
|
|
296
|
+
message:
|
|
297
|
+
'SolidStart leftover. Import GET from "@solidjs/web/server-functions"; routes are createRouter / fileRoutes(pageRoutes), not @solidjs/start, vinxi, h3, or nitropack.',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: 'solidstart-api',
|
|
301
|
+
pattern: /(?<![.\w])(?:createAsync|createAsyncStore|useSubmission|defineEventHandler)\s*\(/g,
|
|
302
|
+
message:
|
|
303
|
+
'SolidStart / Router 0.x leftover. Read query() through createMemo(() => getUser(id())); settled results via useSubmissions (plural); replace cache()/json() with query/respond; rewrite H3 handlers against Request/Response.',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 'use-client',
|
|
307
|
+
pattern: /['"]use client['"]/g,
|
|
308
|
+
message: 'React/Next client directive. Solid has no "use client"; server functions use "use server" only.',
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: 'next-import',
|
|
312
|
+
pattern: /from\s+['"]next(?:['"]|\/)/g,
|
|
313
|
+
message: 'Next.js import in a Solid 2.0 codebase. Use @solidjs/router, @solidjs/meta, and @solidjs/web.',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: 'render-jsx-element',
|
|
317
|
+
pattern: /\b(?:render|hydrate|renderToString|renderToStream)\(\s*</g,
|
|
318
|
+
message:
|
|
319
|
+
'Pass a function to render/hydrate/renderToString/renderToStream so Solid creates the root first: render(() => <App />, root).',
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: 'typeof-window',
|
|
323
|
+
pattern: /typeof\s+window\b/g,
|
|
324
|
+
message:
|
|
325
|
+
'`typeof window` is not the SSR boundary. Use isServer / isDev from "@solidjs/web", or clientOnly(() => import("./Widget")).',
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: 'next-nav',
|
|
329
|
+
pattern:
|
|
330
|
+
/(?<![.\w])(?:useRouter|usePathname|revalidatePath|revalidateTag|notFound|hydrateRoot)\s*\(/g,
|
|
331
|
+
message:
|
|
332
|
+
'Next.js / React DOM leftover. In-app navigation is useNavigate / <a href={Router.paths...}>; 404 is httpStatus(404); hydrate is hydrate(() => <App />, root).',
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
id: 'react-dom-event',
|
|
336
|
+
pattern: /\b(?:onDoubleClick|onKeyPress|suppressHydrationWarning)=/g,
|
|
337
|
+
message:
|
|
338
|
+
'React DOM prop. Native dblclick is onDblClick; keypress is onKeyDown; Solid has no suppressHydrationWarning.',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 'history-nav',
|
|
342
|
+
pattern: /\b(?:history\.pushState|history\.replaceState|window\.location\.href\s*=)/g,
|
|
343
|
+
message:
|
|
344
|
+
'Bypass of the router. In-app navigation is useNavigate() or <a href={Router.paths...}>.',
|
|
345
|
+
},
|
|
293
346
|
{
|
|
294
347
|
// A zero-arg call compared against undefined/null is a manual async
|
|
295
348
|
// readiness branch (`data() === undefined ? ... : ...`).
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
- All TSX in this project is Solid 2.0. Components run **once**; reactivity flows through signals/stores to JSX. React patterns (props destructuring, `className`, per-keystroke `onChange`, state-synced-by-effect, `.map()` lists) and Solid 1.x APIs (`createResource`, `onMount`, `solid-js/store`, `Suspense`, path setters) are bugs here.
|
|
4
4
|
- Before writing TSX, read the `solid-2` skill (`.cursor/skills/solid-2/SKILL.md` for Cursor, `.claude/skills/solid-2/SKILL.md` for Claude Code): patterns, decision tables, official doc URLs. Hard rules auto-attach from `.cursor/rules/solid-2.mdc` in Cursor and live in a managed block in `CLAUDE.md` for Claude Code.
|
|
5
5
|
- After editing TSX, run the `solid2-kit check` mechanical gate — it fails on React/Solid 1.x tokens and props destructuring. Use the project's `lint:solid` script if one exists; otherwise `npx solid2-agent-kit check` (with the kit as a devDependency) or `npx github:lightsound/solid2-agent-kit check`. Do not install `eslint-plugin-solid` (built for Solid 1.x; misreads Solid 2 idioms).
|
|
6
|
-
- Solid 2.0 shipped recently and breaks with 1.x — never trust pre-2.0 Solid docs, tutorials, or training knowledge. Verify APIs against https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt (markdown mirror of https://v2.solidjs.com/, which blocks non-browser fetchers). If the project has `@solidjs/router`, server functions, or `@solidjs/meta`, use those official pages (see the skill's official-docs index) — not React Router, Next.js, SolidStart 1.x, or Solid Router 0.x/1.x JSX `<Route>`/`<A>`.
|
|
6
|
+
- Solid 2.0 shipped recently and breaks with 1.x — never trust pre-2.0 Solid docs, tutorials, or training knowledge. Verify APIs against https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt (markdown mirror of https://v2.solidjs.com/, which blocks non-browser fetchers). If the project has `@solidjs/router`, server functions, or `@solidjs/meta`, use those official pages (see the skill's official-docs index) — not React Router, Next.js, SolidStart 1.x, or Solid Router 0.x/1.x JSX `<Route>`/`<A>`. Core `action` from `solid-js` is a generator transaction; router `action`/`query` from `@solidjs/router` are POST forms and a read cache. Pass a function to `render`/`hydrate`: `render(() => <App />, root)`. Component teardown is `onSettled`, not `onCleanup`. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`.
|
|
@@ -33,8 +33,9 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
33
33
|
as an updater.
|
|
34
34
|
5. `class`, not `className`; `for`, not `htmlFor`. `class` accepts strings, conditional
|
|
35
35
|
objects, and nested arrays: `class={["btn", { active: selected() }]}`. Put *conditional*
|
|
36
|
-
names in an object — `class={`btn ${on() ? "on" : ""}`}`
|
|
37
|
-
|
|
36
|
+
names in an object — `class={`btn ${on() ? "on" : ""}`}`, `clsx(...)`, and
|
|
37
|
+
`.filter(Boolean).join(" ")` all work but rebuild the whole string, so Solid cannot
|
|
38
|
+
add/remove one token. `style` objects use CSS property names
|
|
38
39
|
(`"background-color"`, not `backgroundColor`) and numbers do NOT auto-append `px`
|
|
39
40
|
(`width: `${n}px``).
|
|
40
41
|
6. **`onInput` for per-keystroke handling.** Solid's `onChange` is the native change event
|
|
@@ -77,23 +78,35 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
77
78
|
accessor) only when that identity change should show the fallback again. Pass an async
|
|
78
79
|
memo to a child as the **accessor** (`user={user}`), and read `props.user()` under that
|
|
79
80
|
child's `<Loading>` — `user={user()}` throws not-ready at the parent, so the child's
|
|
80
|
-
boundary never sees it.
|
|
81
|
+
boundary never sees it. Do not `try/catch` `NotReadyError` around a read, and do not
|
|
82
|
+
use `loadingValue` / `seedLoadingValue` as the default first-flight UI — those skip
|
|
83
|
+
`<Loading>`. `{latest(() => x())}` is a preview, not the visible answer (`isPending`
|
|
84
|
+
is the indicator). "Prop with local edits" = writable derivation:
|
|
81
85
|
`createSignal(() => props.value)` or `createStore(() => props.value, fallback)`.
|
|
86
|
+
`createOptimistic` is for an in-flight mutation, not a local editing session.
|
|
82
87
|
11. Context: the context object is the provider — `<MyContext value={{ theme, setTheme }}>`
|
|
83
88
|
(no `.Provider`). Pass accessors/setters/stores through context, never snapshot values.
|
|
84
89
|
`createContext<T>()` without a default throws when read outside a provider (good) — use
|
|
85
90
|
that for any reactive payload. `createContext("light")` is only for a primitive static
|
|
86
91
|
fallback; a dummy default on a store/signal context *runs* and then silently no-ops
|
|
87
|
-
without a provider.
|
|
92
|
+
without a provider. Truly app-wide singletons (theme, session, locale) are a
|
|
93
|
+
**module-level** signal/store — Context is subtree scoping, not a React-style
|
|
94
|
+
app store. Module state is shared across SSR requests.
|
|
88
95
|
12. Refs: `let el!: HTMLDivElement` + `ref={el}` or `ref={(node) => (el = node)}`, or forward
|
|
89
96
|
`props.ref`. Compose with arrays: `ref={[props.ref, (node) => (el = node)]}`. No
|
|
90
97
|
`useRef`/`.current`. Ref callbacks run untracked and **without an owner**; their
|
|
91
98
|
return values are ignored. Do not create effects or register cleanup inside a ref
|
|
92
99
|
callback. For reusable directives, create owned primitives (`onSettled`) in a
|
|
93
|
-
factory and return only the element callback.
|
|
100
|
+
factory and return only the element callback. Component setup/teardown is
|
|
101
|
+
`onSettled(() => { ...; return cleanup })` — `onCleanup` is for custom
|
|
102
|
+
primitives and captured owners, not component bodies. Each `onSettled`
|
|
103
|
+
registers a **single** fire (reads inside are untracked); ongoing
|
|
104
|
+
imperative work is `createEffect`.
|
|
94
105
|
13. **Writes are staged** and commit on the next microtask. Event handlers need nothing
|
|
95
106
|
special; tests and imperative integration code must call `flush()` before observing
|
|
96
|
-
updated state or DOM.
|
|
107
|
+
updated *synchronous* state or DOM. Waiting on an async memo is
|
|
108
|
+
`await resolve(() => value())` (or Testing Library async queries), not `flush()`.
|
|
109
|
+
Never call `flush()` inside an `action`.
|
|
97
110
|
14. Do not port these React tools — they have no Solid equivalent because the problems they
|
|
98
111
|
solve don't exist: `useCallback`, `React.memo`, `forwardRef`, `useSyncExternalStore`,
|
|
99
112
|
dependency arrays, `startTransition`/`useTransition` (updates are held and coordinated
|
|
@@ -101,7 +114,8 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
101
114
|
15. **Components return once.** Never early-return or conditionally return based on reactive
|
|
102
115
|
values — the branch is chosen once at setup and frozen forever. Put conditionals inside
|
|
103
116
|
JSX (`<Show>`, ternary, `<Switch>`/`<Match>`). Early returns on genuinely non-reactive
|
|
104
|
-
values (build-time config, missing env) are fine.
|
|
117
|
+
values (build-time config, missing env) are fine. `<Switch>` is first-wins: later
|
|
118
|
+
truthy `<Match>`es are skipped, not “all matching branches”.
|
|
105
119
|
16. Prefer `textContent` for text-only content. Use `innerHTML` only for trusted or
|
|
106
120
|
sanitized markup — never interpolate user input into it, never React's
|
|
107
121
|
`dangerouslySetInnerHTML`, and never combine `innerHTML` with JSX children (they fight
|
|
@@ -111,6 +125,8 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
111
125
|
**narrowed accessor**, so no `!` is needed:
|
|
112
126
|
`<Show when={error()}>{(err) => <p>{err().message}</p>}</Show>`. For non-reactive
|
|
113
127
|
zero-arg calls, use an explicit guard variable instead of `!`.
|
|
128
|
+
Do not add `keyed` by default (the React `key` reflex) — default `Show`/`Match`
|
|
129
|
+
keep children mounted; `keyed` remounts when identity changes.
|
|
114
130
|
Mechanically enforced by `solid2-kit check`.
|
|
115
131
|
18. **Never hand-roll async UI state.** No `[loading, setLoading]` signals, no
|
|
116
132
|
`data() === undefined` readiness branches, no `{ data, error }` signal pairs from
|
|
@@ -143,10 +159,25 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
143
159
|
state after mutations. Compiler: `"jsxImportSource": "@solidjs/web"` (not `"solid-js"`);
|
|
144
160
|
Vite plugin is `@solidjs/vite-plugin` (not `vite-plugin-solid`). Server vs browser:
|
|
145
161
|
`isServer` / `isDev` from `@solidjs/web`, or `clientOnly(() => import("./Widget"))` for
|
|
146
|
-
browser-only components — never `typeof window` as the SSR boundary.
|
|
162
|
+
browser-only components — never `typeof window` as the SSR boundary. Default
|
|
163
|
+
`clientOnly` starts loading at declaration; `{ lazy: true }` defers until first
|
|
164
|
+
render. `NoHydration` / `Hydration` split hydration *ownership*; they do not choose
|
|
165
|
+
visible content — `clientOnly` is for components that must never run on the server.
|
|
166
|
+
Pass a **function** to `render` / `hydrate` / `renderToString` / `renderToStream`
|
|
167
|
+
(`render(() => <App />, root)`), never `render(<App />, root)`. `hydrate` when the
|
|
168
|
+
container already holds SSR HTML; `render` for an empty mount. `renderToString`
|
|
169
|
+
emits `<Loading>` fallbacks; async or lazy-route trees need
|
|
170
|
+
`await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`).
|
|
171
|
+
When the app owns the document, emit `<HydrationScript />` once before app markup;
|
|
172
|
+
extra roots get a distinct `renderId`. Import `GET` from
|
|
173
|
+
`@solidjs/web/server-functions`, never `@solidjs/start`. `httpStatus` /
|
|
174
|
+
`httpHeader` are scope *declarations* (call them bare in a component or
|
|
175
|
+
fallback body), not event-time mutations. `JSON.stringify(store)`
|
|
147
176
|
/ `structuredClone(store)` without `snapshot(store)` can throw or leak proxies. Async SSR
|
|
148
177
|
without `<Loading>` *works* but blocks the HTML shell until every read settles — wrap
|
|
149
178
|
reads, or stream with `renderToStream`.
|
|
179
|
+
Navigation-shaped updates (a setter, then async computeds holding previous values)
|
|
180
|
+
do not need core `action` — reach for it only when writes happen *after* async work.
|
|
150
181
|
23. **Composition.** Pass `props.children` through when you only render them. Inspect or
|
|
151
182
|
iterate children with `children(() => props.children)` (then `.toArray()`), never a
|
|
152
183
|
setup-time snapshot. Code-split with `lazy(() => import("./X"))` under `<Loading>` —
|
|
@@ -155,7 +186,20 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
155
186
|
because the named export is not a call-site literal. Select a component from reactive
|
|
156
187
|
state with `dynamic(() => ...)` from `@solidjs/web`
|
|
157
188
|
(stable identity). If the project has `@solidjs/router`, routes are `createRouter({ routes })`
|
|
158
|
-
at module scope — not JSX `<Route>` / `<A>` / `<HashRouter>`.
|
|
189
|
+
at module scope — not JSX `<Route>` / `<A>` / `<HashRouter>`. That package's `action` /
|
|
190
|
+
`query` are URL-addressable POST forms and a read cache — not core `action` /
|
|
191
|
+
`refresh` from `solid-js`. Router mutations: `<form action={save} method="post">`
|
|
192
|
+
(POST only; `.with(id)` binds args into the URL). `useAction` is JS-only. Cache
|
|
193
|
+
reads: `query(fn, "name")` then `createMemo(() => getUser(id()))`; after a mutation
|
|
194
|
+
`revalidate(getUser.key)`. Do not wrap mutations in `query` (an undeclared server
|
|
195
|
+
function becomes GET). `Router.paths` is not the current location (`useLocation` /
|
|
196
|
+
`useParams`); a route `preload` result is `props.data`. One router instance —
|
|
197
|
+
Solid Router does not support nested `<Router>` / nested `createRouter`.
|
|
198
|
+
In-app navigation is `useNavigate` or `<a href={Router.paths...}>`, not
|
|
199
|
+
`window.location` / `history.pushState`. Trusted identity is
|
|
200
|
+
`getRequestEvent()`, never a caller-supplied user id. Unscripted forms use
|
|
201
|
+
the function `.url` (or a router `action`), not a hand-built `/_server/`
|
|
202
|
+
path. Document head is
|
|
159
203
|
`<Title>` / `<Meta>` from `@solidjs/meta` with **no** `MetaProvider`.
|
|
160
204
|
|
|
161
205
|
## Lint heritage: eslint-plugin-solid (Solid 1.x) intents carried into this file
|
|
@@ -208,7 +252,10 @@ directory (default `src/`).
|
|
|
208
252
|
| `renderToStringAsync(...)` | `await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`) |
|
|
209
253
|
| `clearDelegatedEvents` | delete — delegated listeners are scoped to each render root |
|
|
210
254
|
| `vite-plugin-solid` / `jsxImportSource: "solid-js"` | `@solidjs/vite-plugin` / `"jsxImportSource": "@solidjs/web"` |
|
|
211
|
-
| JSX `<Route>` / `<A>` / `<HashRouter>` / `<Navigate>` | `createRouter({ routes })`, plain `<a href={Router.paths...}>`, `hashHistory()` |
|
|
255
|
+
| JSX `<Route>` / `<A>` / `<HashRouter>` / `<Navigate>` / `<FileRoutes>` | `createRouter({ routes })`, `fileRoutes(pageRoutes)`, plain `<a href={Router.paths...}>`, `hashHistory()` |
|
|
256
|
+
| `createAsync` / `createAsyncStore` / `useSubmission` / router `json()` / `cache()` | `createMemo(() => getUser(id()))`, `useSubmissions`, `respond()` from `@solidjs/web`, `query` |
|
|
257
|
+
| `import ... from "@solidjs/start"` / `vinxi` / `h3` / `"use client"` | `GET` from `@solidjs/web/server-functions`; Solid has no `"use client"` |
|
|
258
|
+
| `render(<App />, root)` | `render(() => <App />, root)` (same for `hydrate` / `renderToString` / `renderToStream`) |
|
|
212
259
|
| `<MetaProvider>` | no provider — render `<Title>` / `<Meta>` / `<Link>` from `@solidjs/meta` anywhere |
|
|
213
260
|
| `use:directive`, `on:`/`oncapture:`, `attr:`/`bool:`, `/*@once*/` | `ref` callbacks, camelCase event props, standard attributes, keep values reactive |
|
|
214
261
|
| `resource.loading` / `resource.error` | `<Loading>` boundary / `<Errored>` boundary |
|
|
@@ -68,7 +68,7 @@ Most React `useEffect` code should NOT become `createEffect`:
|
|
|
68
68
|
| Code-split a component | `lazy(() => import("./X"))` read under `<Loading>` |
|
|
69
69
|
| Named export from a lazy module | `lazy(() => import("./pages"), { export: "About" })` |
|
|
70
70
|
| Pick a component/tag from reactive state | `dynamic(() => ...)` from `@solidjs/web` (stable identity) |
|
|
71
|
-
| Browser-only widget (charts, maps, `window`) | `clientOnly(() => import("./Chart"))` from `@solidjs/web` |
|
|
71
|
+
| Browser-only widget (charts, maps, `window`) | `clientOnly(() => import("./Chart"))` from `@solidjs/web` (`{ lazy: true }` defers the import until first render) |
|
|
72
72
|
| Server vs browser branch | `isServer` / `isDev` from `@solidjs/web` (build-time constants), not `typeof window` |
|
|
73
73
|
| SSR-stable `id` / `for` / `aria-*` pairing | `createUniqueId()` |
|
|
74
74
|
|
|
@@ -149,7 +149,8 @@ the component must **inspect or iterate** them. It returns an accessor with
|
|
|
149
149
|
`.toArray()`. The helper call belongs in the component body; the read of
|
|
150
150
|
`props.children` is inside the accessor (a tracking scope), so this is not a
|
|
151
151
|
setup-time snapshot. Do not assign `const kids = props.children` and do not
|
|
152
|
-
treat resolved children as a reactive data list for `<For
|
|
152
|
+
treat resolved children as a reactive data list for `<For>` —
|
|
153
|
+
render `{resolved.toArray()}`, not `<For each={resolved()}>`:
|
|
153
154
|
|
|
154
155
|
```tsx
|
|
155
156
|
import { children, type ParentProps } from 'solid-js';
|
|
@@ -197,7 +198,8 @@ const Result = dynamic(() => (detailed() ? Detailed : Compact));
|
|
|
197
198
|
|
|
198
199
|
`dynamic()` returns a **stable** component whose source can be a component, an
|
|
199
200
|
intrinsic tag name, a promise, or empty. Prefer it over swapping a component
|
|
200
|
-
variable in JSX
|
|
201
|
+
variable in JSX (`const View = tab() ? A : B` freezes the choice at setup).
|
|
202
|
+
`<Dynamic>` is the JSX spelling of the same primitive.
|
|
201
203
|
|
|
202
204
|
### Two-phase effect (imperative boundary only)
|
|
203
205
|
|
|
@@ -287,7 +289,8 @@ function createSubscriptionQuery<T>(
|
|
|
287
289
|
const queue: T[] = [];
|
|
288
290
|
let failure: unknown;
|
|
289
291
|
let wake = () => {};
|
|
290
|
-
//
|
|
292
|
+
// Custom primitive: unsubscribe is tied to the caller's owner via onCleanup.
|
|
293
|
+
// Component bodies use onSettled and return cleanup instead.
|
|
291
294
|
onCleanup(subscribe(
|
|
292
295
|
(value) => { queue.push(value); wake(); },
|
|
293
296
|
(error) => { failure = error; wake(); },
|
|
@@ -320,11 +323,23 @@ const addTodo = action(function* (todo: Todo) {
|
|
|
320
323
|
|
|
321
324
|
Ordinary signal/store writes inside an action are held until it settles. Never call
|
|
322
325
|
`flush()` inside an action. Invoke actions from handlers, not from component/computation bodies.
|
|
326
|
+
Do not set a `submitted` signal and watch it from an effect — that work belongs in the
|
|
327
|
+
handler or the `action`.
|
|
328
|
+
|
|
329
|
+
`createOptimistic` / `createOptimisticStore` are for a tentative value during an
|
|
330
|
+
active mutation, not a local editing session (use a writable derivation or a plain
|
|
331
|
+
store for that).
|
|
323
332
|
|
|
324
333
|
**`yield`, not a bare `await`, is the transaction boundary.** `await api.save()` then
|
|
325
334
|
`setTodos(...)` *runs*, but the write commits immediately and optimistic rollback is lost.
|
|
326
335
|
Either `yield saveTodo(todo)` or, when you need a typed result, `const saved = await api.save(); yield; setTodos(...)`.
|
|
327
336
|
|
|
337
|
+
Navigation-shaped updates do **not** need core `action`. A plain setter is enough:
|
|
338
|
+
reads pull the async, and downstream async computeds hold previous values until the
|
|
339
|
+
new ones are ready (`isPending` / `latest`). Reach for `action` from `solid-js` only
|
|
340
|
+
when writes happen *after* async work. Router form `action` from `@solidjs/router` is
|
|
341
|
+
a different API (URL + POST) — see [App stack](#app-stack-only-if-the-project-has-these-packages).
|
|
342
|
+
|
|
328
343
|
### Lists: `<For>` child signatures per keying mode
|
|
329
344
|
|
|
330
345
|
```tsx
|
|
@@ -380,7 +395,8 @@ Plain ternaries remain fine when no narrowing is involved (`{open() ? <A /> : <B
|
|
|
380
395
|
Default `Show` keeps children mounted across truthy changes. Add `keyed` to remount when the
|
|
381
396
|
value's identity changes (child then receives the raw value, like React's `key` reset).
|
|
382
397
|
Prefer the default; use `keyed` only when internal state must reset. Multi-branch:
|
|
383
|
-
`<Switch>` / `<Match
|
|
398
|
+
`<Switch>` / `<Match>` — **first truthy `<Match>` wins**; later matches are skipped,
|
|
399
|
+
even if they are also truthy.
|
|
384
400
|
|
|
385
401
|
Components must **return once**: never early-return based on a reactive value — the branch
|
|
386
402
|
is picked at setup and frozen. Early returns on non-reactive values (build-time config, a
|
|
@@ -426,7 +442,9 @@ function ThemeButton() {
|
|
|
426
442
|
|
|
427
443
|
Pass accessors/setters/stores through context — never `value={theme()}` (a dead snapshot).
|
|
428
444
|
No `useMemo` for the value object, no context splitting: the object is created once and
|
|
429
|
-
fine-grained updates flow through the signals inside it.
|
|
445
|
+
fine-grained updates flow through the signals inside it. Context is **subtree scoping**.
|
|
446
|
+
A true app-wide singleton (theme, session, locale) is a module-level signal/store —
|
|
447
|
+
do not invent a root provider for that. Module state is shared across SSR requests.
|
|
430
448
|
|
|
431
449
|
### Refs
|
|
432
450
|
|
|
@@ -479,7 +497,9 @@ expect(count()).toBe(2);
|
|
|
479
497
|
```
|
|
480
498
|
|
|
481
499
|
Wait for an async expression to settle in tests: `await resolve(() => value())`.
|
|
482
|
-
|
|
500
|
+
`flush()` only drains staged **synchronous** writes — it is not a stand-in for waiting
|
|
501
|
+
on a pending resource. `onSettled` is also a **single** fire (untracked): it is not
|
|
502
|
+
`onMount` that re-runs, and it is not an effect. Run tests in dev mode first — Solid 2 emits diagnostics for top-level reactive reads,
|
|
483
503
|
writes from owned scopes, and async reads outside `<Loading>`. Fix them; don't suppress.
|
|
484
504
|
|
|
485
505
|
Component tests use `@solidjs/testing-library`. Pass a **function** to `render` so
|
|
@@ -554,10 +574,69 @@ export default function App() {
|
|
|
554
574
|
```
|
|
555
575
|
|
|
556
576
|
Create the instance at **module scope**. Nested layouts are `children` arrays
|
|
557
|
-
on the route objects, not nested `<Route>` / nested routers.
|
|
577
|
+
on the route objects, not nested `<Route>` / nested routers. Solid Router does
|
|
578
|
+
**not** support nested `<Router>` instances — compose one route tree (or a lazy
|
|
579
|
+
`children` thunk). Navigate with
|
|
558
580
|
plain `<a href={Router.paths.about}>` (or `useNavigate`); there is no `<A>` /
|
|
559
|
-
`<Navigate>` / `<HashRouter
|
|
560
|
-
|
|
581
|
+
`<Navigate>` / `<HashRouter>` / `<FileRoutes />`. Do not assign
|
|
582
|
+
`window.location.href` or call `history.pushState` for in-app navigation.
|
|
583
|
+
Session location is `useLocation` / `useParams`,
|
|
584
|
+
not `Router.paths`. A route `preload` result is `props.data` on the matched
|
|
585
|
+
component (and the factory `preload` result is the root render prop's `props.data`).
|
|
586
|
+
Link warming: `preload="false"` skips that link's *data* preload; `preloadLinks: false`
|
|
587
|
+
disables automatic link preloading. Intent values are `"initial"` / `"navigate"` /
|
|
588
|
+
`"native"` / `"preload"`. One router per app.
|
|
589
|
+
|
|
590
|
+
**Two different `action`s.** Core `action` from `solid-js` is a generator transaction
|
|
591
|
+
(optimistic writes that span an async gap). Router `action` from `@solidjs/router` is
|
|
592
|
+
a URL-addressable POST mutation (`useSubmissions`, `<form action={save} method="post">`).
|
|
593
|
+
Do not import one and use it as the other.
|
|
594
|
+
|
|
595
|
+
```tsx
|
|
596
|
+
import { action } from '@solidjs/router';
|
|
597
|
+
import { redirect } from '@solidjs/web';
|
|
598
|
+
|
|
599
|
+
const save = action(async (form: FormData) => {
|
|
600
|
+
await saveProfile(form);
|
|
601
|
+
return redirect('/account');
|
|
602
|
+
}, 'update-profile');
|
|
603
|
+
|
|
604
|
+
<form action={save} method="post">
|
|
605
|
+
<input name="displayName" />
|
|
606
|
+
<button type="submit">Save</button>
|
|
607
|
+
</form>
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Only POST forms are accepted. Bind extra args with `.with(id)` (they go in the
|
|
611
|
+
action URL). `onSubmit={(e) => { e.preventDefault(); fetch(...) }}` *runs* and
|
|
612
|
+
drops the no-JS form fallback. `useAction` is JS-only for the same reason.
|
|
613
|
+
|
|
614
|
+
**`query` is the read cache** (optional, only with the router). Wrap a read,
|
|
615
|
+
give it a stable name, and consume it through a Solid async primitive:
|
|
616
|
+
|
|
617
|
+
```tsx
|
|
618
|
+
import { createMemo } from 'solid-js';
|
|
619
|
+
import { query, revalidate } from '@solidjs/router';
|
|
620
|
+
|
|
621
|
+
export const getUser = query(async (id: string) => {
|
|
622
|
+
const response = await fetch(`/api/users/${id}`);
|
|
623
|
+
return response.json();
|
|
624
|
+
}, 'users');
|
|
625
|
+
|
|
626
|
+
const user = createMemo(() => getUser(params.id));
|
|
627
|
+
// after a mutation:
|
|
628
|
+
revalidate(getUser.key);
|
|
629
|
+
revalidate(getUser.keyFor('42'));
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
A bare `createMemo(async () => fetchUser(id()))` still works; it just has no
|
|
633
|
+
shared cache, no preload reuse, and no `revalidate`. Do not wrap mutations in
|
|
634
|
+
`query` — wrapping an undeclared server function makes it GET. `query.get(key)`
|
|
635
|
+
throws if there is no entry; `query.set` does not accept a promise. Cache is
|
|
636
|
+
request-scoped on the server and application-scoped in the browser. After a
|
|
637
|
+
router mutation, `revalidate(...)` — not core `refresh()`. Replace SolidStart /
|
|
638
|
+
Router 0.x leftovers: no `createAsync`, `useSubmission` (singular), `cache()`,
|
|
639
|
+
router `json()`, or `<FileRoutes />` (`fileRoutes(pageRoutes)` instead).
|
|
561
640
|
|
|
562
641
|
### Server functions — `"use server"`
|
|
563
642
|
|
|
@@ -570,16 +649,22 @@ export async function findUser(id: string) {
|
|
|
570
649
|
```
|
|
571
650
|
|
|
572
651
|
A function-level server function cannot close over component locals — pass
|
|
573
|
-
values as arguments. Treat every argument as untrusted.
|
|
652
|
+
values as arguments. Treat every argument as untrusted. Read trusted identity
|
|
653
|
+
from `getRequestEvent()`, never from a caller-supplied user id. During SSR the same
|
|
574
654
|
reference runs in-process; in the browser it is HTTP. Mutations that should
|
|
575
655
|
revalidate: `redirect` / `reload` from `@solidjs/web` (see the server-function
|
|
576
656
|
guides). For 404 during SSR: `httpStatus(404)` from `@solidjs/web`.
|
|
577
657
|
`throw new Error("…")` is stripped to `"Internal Server Error"` in production —
|
|
578
658
|
use `markSafeError` or `respond(..., { status })` for intentional client-facing
|
|
579
|
-
failures. `GET()` is only for idempotent reads (URLs leak into logs/history)
|
|
659
|
+
failures. `GET()` is only for idempotent reads (URLs leak into logs/history);
|
|
660
|
+
import it from `@solidjs/web/server-functions`, never `@solidjs/start`.
|
|
580
661
|
`live()` yields **current state** (each yield replaces the last); do not treat
|
|
581
662
|
it as an append-only event log, and hoist the memo so consumers share one
|
|
582
663
|
connection.
|
|
664
|
+
Unscripted forms: `<form method="post" action={createTodo.url}>` (the reference
|
|
665
|
+
`.url`), not a client `preventDefault` + `fetch`, and not a hand-built
|
|
666
|
+
`/_server/` URL. GET forms (`method="get" action={search.url}`) are only for
|
|
667
|
+
idempotent search — do not use a GET form for a mutation.
|
|
583
668
|
|
|
584
669
|
### Document head — no MetaProvider
|
|
585
670
|
|
|
@@ -604,7 +689,7 @@ how to reuse. Prefer the form on the right.
|
|
|
604
689
|
|---|---|
|
|
605
690
|
| `{todos().map((t) => <Row todo={t} />)}` | `<For each={todos()} keyed={(t) => t.id}>` |
|
|
606
691
|
| `<For each={todos().map(t => t)}>` | derive first, then `<For each={visible()}>` |
|
|
607
|
-
| `class={`btn ${on() ? "on" : ""}`}` | `class={["btn", { on: on() }]}` |
|
|
692
|
+
| `class={`btn ${on() ? "on" : ""}`}` / `clsx("btn", on() && "on")` / `.filter(Boolean).join(" ")` | `class={["btn", { on: on() }]}` |
|
|
608
693
|
| `style={{ width: 80 }}` (no unit) | `style={{ width: `${80}px` }}` |
|
|
609
694
|
| `const rest = { ...props }` | `const rest = omit(props, "label")` |
|
|
610
695
|
| `const user = createMemo(async () => { await fetch(...); return id(); })` | read `id()` **before** `await` |
|
|
@@ -632,6 +717,42 @@ how to reuse. Prefer the form on the right.
|
|
|
632
717
|
| `live()` yields as an event log to append | each yield **replaces** the current answer; yield current state first |
|
|
633
718
|
| `<article innerHTML={html()}>…children…</article>` | `innerHTML` **or** children, not both |
|
|
634
719
|
| `fallback={(error) => <p>{error.message}</p>}` | `error` is an accessor: `error().message` (or `String(error())`) |
|
|
720
|
+
| `import { action } from "solid-js"` on a `<form>` | `import { action } from "@solidjs/router"` + `<form action={save} method="post">` (core `action` is a generator transaction, not a form URL) |
|
|
721
|
+
| `createMemo(async () => fetchUser(id()))` when `@solidjs/router` is present | `query(fetchUser, "users")` + `createMemo(() => getUser(id()))`; after mutations `revalidate(getUser.key)`, not core `refresh()` |
|
|
722
|
+
| `onSubmit={(e) => { e.preventDefault(); fetch(...) }}` for a router/server action | `<form action={save} method="post">` (POST only; `.with(id)` binds args into the URL). `useAction` is JS-only |
|
|
723
|
+
| `query(saveUser, "users")` wrapping a mutation | don't wrap mutations in `query` — an undeclared server function becomes GET |
|
|
724
|
+
| `render(<App />, root)` / Testing Library `render(<Counter />)` | `render(() => <App />, root)` — pass a function so Solid creates the root first |
|
|
725
|
+
| `render` onto SSR HTML / `hydrate` into an empty node | `hydrate(() => <App />, root)` when HTML already exists; `render` for an empty mount |
|
|
726
|
+
| `renderToString(() => <App />)` for an async or lazy-route tree | `await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`). String render emits `<Loading>` fallbacks |
|
|
727
|
+
| `clientOnly(() => import("./Map"))` for a rarely shown widget | same + `{ lazy: true }` so the import waits until first render (default starts at declaration) |
|
|
728
|
+
| `<NoHydration><Chart /></NoHydration>` to skip SSR of a widget | `clientOnly(() => import("./Chart"))` — `NoHydration`/`Hydration` split ownership, they do not choose visible content |
|
|
729
|
+
| `await flush()` / `flush()` to wait on a pending memo | `await resolve(() => value())` (or Testing Library async queries); `flush()` only drains staged *sync* writes |
|
|
730
|
+
| `action(function* () { setX(v); })` around a navigation-shaped setter | plain setter; async computeds hold previous values. Core `action` only when writes happen *after* async work |
|
|
731
|
+
| `createContext` + a root provider for a true app singleton | module-level signal/store (SSR: shared across requests). Context is subtree scoping |
|
|
732
|
+
| `import { GET } from "@solidjs/start"` / `createAsync` / `useSubmission` / `<FileRoutes />` / `"use client"` | `GET` from `@solidjs/web/server-functions`; `createMemo(() => getUser(id()))`; `useSubmissions`; `fileRoutes(pageRoutes)`; Solid has no `"use client"` |
|
|
733
|
+
| `<Show keyed when={user()}>` by default | default `Show` (keeps children mounted); `keyed` only when internal state must reset |
|
|
734
|
+
| overlapping truthy `<Match>`es all expected to render | first truthy `<Match>` wins; later matches are skipped |
|
|
735
|
+
| missing `<HydrationScript />` / one script for many roots | once, before app markup, when the app owns the document; distinct `renderId` per extra root |
|
|
736
|
+
| `Router.paths` as the current URL; calling `preload()` and using the return as props | `useLocation` / `useParams`; factory/route `preload` result is `props.data` |
|
|
737
|
+
| `query.get(key)` with no guaranteed entry; `query.set(key, promise)` | `query.get` throws if missing; `query.set` does not accept a promise |
|
|
738
|
+
| `onCleanup(() => ...)` in a component body | `onSettled(() => { ...; return cleanup })` — `onCleanup` is for custom primitives |
|
|
739
|
+
| `onSettled` with reactive reads expecting re-runs | each call is a **single** fire; reads are untracked. Ongoing work is `createEffect` |
|
|
740
|
+
| `<Router>` nested inside `<Router>` | one instance; nest `children` arrays (or a lazy children thunk) |
|
|
741
|
+
| `const View = tab() ? A : B; return <View />` | `const View = dynamic(() => (tab() ? A : B))` — the ternary at setup freezes the choice |
|
|
742
|
+
| `<For each={resolved()}>` after `children()` | `{resolved.toArray()}` — resolved children are not a reactive data list |
|
|
743
|
+
| `<input onChange={...}>` for keystrokes | `onInput` — native `onChange` is blur/commit |
|
|
744
|
+
| `createOptimistic` for a local edit draft | writable derivation / plain store. Optimistic is for an in-flight mutation |
|
|
745
|
+
| `createMemo(async fn, { loadingValue })` as the default first-flight UI | `<Loading>` for first flight; `loadingValue` is an escape hatch |
|
|
746
|
+
| `setSubmitted(true)` + an effect that watches it | do the work in the handler or an `action` |
|
|
747
|
+
| `findUser(userId)` with the id from the client as identity | `getRequestEvent()!.locals.userId` |
|
|
748
|
+
| `action={"/_server/" + id}` in app JSX | `action={createTodo.url}` or a router `action` |
|
|
749
|
+
| `<form method="get" action={update.url}>` for a mutation | GET forms only for idempotent search; mutations are POST |
|
|
750
|
+
| `window.location.href = ...` / `history.pushState` | `useNavigate()` or `<a href={Router.paths...}>` |
|
|
751
|
+
| `httpStatus(404)` in a click handler | call it bare in the component / error-fallback body (a scope declaration, not a mutation) |
|
|
752
|
+
| `try { user() } catch (e) { /* NotReadyError */ }` | `<Loading>` — app code should not catch `NotReadyError` |
|
|
753
|
+
| `{latest(() => results())}` as the visible list | settled read + `isPending()` for the indicator; `latest` is for previews |
|
|
754
|
+
| `typeof window !== "undefined"` | `isServer` / `isDev` from `@solidjs/web` |
|
|
755
|
+
| `Object.assign({}, props)` / expecting `merge` to skip `undefined` like 1.x `mergeProps` | `omit` / `merge` — explicit `undefined` **overrides** (Object.assign) |
|
|
635
756
|
|
|
636
757
|
```tsx
|
|
637
758
|
// WRONG — post-await read never subscribes; production can hang pending
|
|
@@ -684,6 +805,43 @@ function Page() {
|
|
|
684
805
|
}
|
|
685
806
|
```
|
|
686
807
|
|
|
808
|
+
```tsx
|
|
809
|
+
// WRONG — two different `action`s; the core one is not a form URL
|
|
810
|
+
import { action } from 'solid-js';
|
|
811
|
+
const save = action(async function* (form: FormData) {
|
|
812
|
+
yield persist(form);
|
|
813
|
+
});
|
|
814
|
+
<form onSubmit={(e) => { e.preventDefault(); void save(new FormData(e.currentTarget)); }}>
|
|
815
|
+
<button type="submit">Save</button>
|
|
816
|
+
</form>
|
|
817
|
+
|
|
818
|
+
// CORRECT — router action is POST-addressable (only if `@solidjs/router` is in the project)
|
|
819
|
+
import { action } from '@solidjs/router';
|
|
820
|
+
const save = action(async (form: FormData) => persist(form), 'save');
|
|
821
|
+
<form action={save} method="post">
|
|
822
|
+
<button type="submit">Save</button>
|
|
823
|
+
</form>
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
```tsx
|
|
827
|
+
// WRONG — evaluating JSX before the root exists
|
|
828
|
+
import { render } from '@solidjs/web';
|
|
829
|
+
render(<App />, document.getElementById('app')!);
|
|
830
|
+
|
|
831
|
+
// CORRECT
|
|
832
|
+
render(() => <App />, document.getElementById('app')!);
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
```tsx
|
|
836
|
+
// WRONG — tab() is read once at setup; View never changes
|
|
837
|
+
const View = tab() ? Detailed : Compact;
|
|
838
|
+
return <View value="now" />;
|
|
839
|
+
|
|
840
|
+
// CORRECT
|
|
841
|
+
const View = dynamic(() => (tab() ? Detailed : Compact));
|
|
842
|
+
return <View value="now" />;
|
|
843
|
+
```
|
|
844
|
+
|
|
687
845
|
## Checking the official docs
|
|
688
846
|
|
|
689
847
|
Solid 2.0 docs: https://v2.solidjs.com/ — the site blocks non-browser fetchers, so from an
|
|
@@ -725,7 +883,14 @@ always-applied rules installed alongside this skill.
|
|
|
725
883
|
- [ ] Inspect children with `children()`; code-split with `lazy` + `<Loading>`; reactive
|
|
726
884
|
component choice with `dynamic()`. No `React.lazy`, no effects inside ref callbacks.
|
|
727
885
|
- [ ] Browser-only code uses `isServer` / `clientOnly`, not `typeof window`.
|
|
886
|
+
Component teardown is `onSettled` (return cleanup), not `onCleanup`.
|
|
728
887
|
- [ ] If the project has a router: `createRouter({ routes })`, not JSX `<Route>` / `<A>`.
|
|
888
|
+
One instance, no nested `<Router>`. Navigate with `useNavigate` / `<a href>`, not
|
|
889
|
+
`window.location`. Router `action`/`query` come from `@solidjs/router` (POST forms + cache), not
|
|
890
|
+
core `action`/`refresh`. Forms: `<form action={save} method="post">`.
|
|
891
|
+
- [ ] `render(() => <App />, root)` — a function, not `render(<App />)`. `hydrate` when
|
|
892
|
+
HTML already exists. Stream async/lazy trees (`await renderToStream(...)`).
|
|
729
893
|
- [ ] `jsxImportSource` is `@solidjs/web`; Vite plugin is `@solidjs/vite-plugin`.
|
|
894
|
+
No `@solidjs/start`, `vinxi`, `"use client"`, or Next.js imports.
|
|
730
895
|
- [ ] Single return per component; no early returns on reactive conditions.
|
|
731
896
|
- [ ] `solid2-kit check` and the project's typecheck pass.
|
|
@@ -98,6 +98,7 @@ read these before writing route JSX.
|
|
|
98
98
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/lifecycle-actions/affects.md
|
|
99
99
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/lifecycle-actions/on-settled.md
|
|
100
100
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/lifecycle-actions/refresh.md
|
|
101
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/specialized-reactivity/on-cleanup.md
|
|
101
102
|
|
|
102
103
|
## Reference: components and JSX (solid-js)
|
|
103
104
|
|
|
@@ -113,6 +114,7 @@ read these before writing route JSX.
|
|
|
113
114
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/components-context/children.md
|
|
114
115
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/components-context/lazy.md
|
|
115
116
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/components-context/create-unique-id.md
|
|
117
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/manual-hydration/no-hydration.md
|
|
116
118
|
|
|
117
119
|
## Reference: @solidjs/web
|
|
118
120
|
|
|
@@ -135,6 +137,7 @@ read these before writing route JSX.
|
|
|
135
137
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/jsx-properties/text-content.md
|
|
136
138
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/jsx-properties/inner-html.md
|
|
137
139
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/server-functions.md
|
|
140
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/server-functions/get.md
|
|
138
141
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response.md
|
|
139
142
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response/get-request-event.md
|
|
140
143
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response/redirect.md
|
|
@@ -145,6 +148,7 @@ read these before writing route JSX.
|
|
|
145
148
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-router/router-factory.md
|
|
146
149
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-router/navigation.md
|
|
147
150
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-router/data.md
|
|
151
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-router/filesystem.md
|
|
148
152
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-meta/title.md
|
|
149
153
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-meta/meta.md
|
|
150
154
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-meta/link.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid2-agent-kit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Teach AI coding agents (Cursor, Claude Code) to write correct Solid 2.0 instead of React or Solid 1.x — always-applied rules, an agent skill, and a mechanical pattern gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|