solid2-agent-kit 0.14.0 → 0.16.0

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.
@@ -42,7 +42,8 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
42
42
  before a flush, use the setter updater (`setCount((c) => c + 1)`); `setCount(count() + 1)`
43
43
  reads the last *committed* value and drops the other staged writes. To store a *function*
44
44
  in a signal, wrap it: `setHandler(() => myHandler)` — `setHandler(myHandler)` treats it
45
- as an updater.
45
+ as an updater. The initial value too: `createSignal(() => myHandler)` —
46
+ `createSignal(myHandler)` is the derived form (it calls `myHandler` and stores the result).
46
47
  5. `class`, not `className`; `for`, not `htmlFor`. `class` accepts strings, conditional
47
48
  objects, and nested arrays: `class={["btn", { active: selected() }]}`. Put *conditional*
48
49
  names in an object — `class={`btn ${on() ? "on" : ""}`}`, `clsx(...)`, and
@@ -60,13 +61,15 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
60
61
  that writes the event object; wrap it: `onClick={() => setCount((c) => c + 1)}`.
61
62
  Debounce at the handler (`onInput={debounce(...)}`), never as an effect copying
62
63
  one signal into another.
63
- 7. **Lists use `<For>`**, never `{list().map(...)}` in reactive JSX and never `key` props.
64
+ 7. **Lists use `<For>`**, never `{list().map(...)}` in reactive JSX and never `key` props (only `@solidjs/meta` tags take `key`, as the tag's identity).
64
65
  `{todos().map((t) => <Row todo={t} />)}` *renders*, then recreates every row on each
65
66
  update. Row identity: default = item reference; `keyed={(item) => item.id}` = key function
66
67
  (child receives item as an accessor); `keyed={false}` = positional. **Rows from
67
68
  server/refetched data (fresh object references on every update — fetch results,
68
69
  subscription payloads) must use a key function on a stable id**, or every update
69
- recreates every row (dev + attribution flags the rebuilds as `[UNSTABLE_LIST_IDENTITY]`). Reference keying is for local arrays whose item identities are
70
+ recreates every row (dev + attribution flags the rebuilds as `[UNSTABLE_LIST_IDENTITY]`).
71
+ Usually better still: load them into a function-form store (rule 20), whose proxies keep
72
+ identity per id, and keep default keying. Reference keying is for local arrays whose item identities are
70
73
  stable (e.g. store rows). Match the child signature to the mode: default item is the
71
74
  **raw** value (`todo.title`); `keyed={(t) => t.id}` / `keyed={false}` pass an
72
75
  **accessor** (`todo().title`). Mixing those still “runs” (you render a function, or
@@ -75,9 +78,16 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
75
78
  fed to `<For>` (that rebuilds the window array every time).
76
79
  8. **Effects have two phases**: `createEffect(compute, apply)`. All reactive reads go in
77
80
  `compute`; its return value feeds `apply`, which does imperative work and may return a
78
- cleanup. Single-argument `createEffect(fn)` is an error in Solid 2. Do not substitute
79
- `createTrackedEffect` for that — it is an advanced one-callback form that cannot nest
80
- primitives. Skip the initial run with `{ defer: true }`. Most React `useEffect` code should not become an effect at all — see the skill.
81
+ cleanup. `apply` returns a cleanup function or nothing — **write it with a block body**:
82
+ `(t) => { document.title = t; }`, never `(t) => (document.title = t)` or
83
+ `(v) => setX(v)` (an assignment or setter returns its value; dev throws
84
+ `invalid cleanup value`, production fails on the next run, and either halts the whole
85
+ reactive system). An `apply` that only calls a local setter is still rule 4 with a
86
+ block body — make it a derivation. Register an effect's cleanup by returning it
87
+ from `apply` — `onCleanup` there never runs (`[NO_OWNER_CLEANUP]`).
88
+ Single-argument `createEffect(fn)` is an error in Solid 2. Do not substitute
89
+ `createTrackedEffect` for that — it is `@deprecated` since 2.0.0-rc.9 (kept only for
90
+ 1.x migration). Skip the initial run with `{ defer: true }`. Most React `useEffect` code should not become an effect at all — see the skill.
81
91
  9. **Stores update by mutating a draft**: `setStore(draft => { draft.user.name = "Ada" })`.
82
92
  Never rebuild with spreads — that destroys property-level subscriptions. The return
83
93
  form is for shapes where mutation is awkward — most commonly removal
@@ -86,6 +96,15 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
86
96
  `reconcile` or the derived form (dev + attribution flags the store-level waste as
87
97
  `[IMMUTABLE_UPDATE_IN_STORE]`). Stores are
88
98
  **property reads** (`todos.length`, `todo.title`), not accessors — never `todos()`.
99
+ The setter takes only a function: `setState("a", v)` and the 1.x shallow merge
100
+ `setState({ a: 1 })` throw `fn is not a function`, and an object *returned* from the
101
+ callback replaces the whole store (keys it omits are deleted).
102
+ The store itself is read-only: `todos.push(x)`, `state.count++`, or
103
+ `todo.done = !todo.done` outside a setter is **silently ignored** (no throw, no
104
+ warning, and TypeScript accepts it) — every write goes through the setter's draft.
105
+ `Map`, `Set`, and `Date` are stored as-is, not proxied: `draft.selected.add(id)`
106
+ changes the object but notifies nobody. Model sets as `Record<id, true>` (or an
107
+ array), or assign a new instance in the setter (`draft.tags = new Set([...draft.tags, t])`).
89
108
  The setter callback is a **synchronous** transaction: `setStore(async (draft) => …)`
90
109
  throws `[ASYNC_STORE_SETTER]` in dev (writes after the first `await` were silently
91
110
  lost before). `await` first, then call the setter (inside an `action`, `yield` first).
@@ -103,13 +122,17 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
103
122
  `<Child user={user()} />` then `{props.user.name}` under the child's
104
123
  `<Loading>` is the colorless form. `const u = user()` at the parent *is* a
105
124
  read and throws/snapshots there. Pass the memo itself only when the child
106
- must `refresh()` that source — `isPending(() => props.user)` works on the
125
+ must `refresh()` that source — type that prop `SourceAccessor<User>` (`Accessor<User>`
126
+ is TS2345 at `refresh`) — `isPending(() => props.user)` works on the
107
127
  value. Read every reactive input **before the first
108
128
  `await`** — post-`await` reads do not subscribe, and in production the computation can
109
129
  sit pending with no retry. No `useEffect` + `setState` fetching, no `createResource`.
110
130
  `<Loading>` wraps the data slot, not page chrome. After first paint it keeps content
111
131
  during refetch (`isPending` for the indicator). Use `on={id()}` (the *value*, not the
112
- accessor) only when that identity change should show the fallback again. Do not start `fetch` (or any request) at component-body top
132
+ accessor) only when that identity change should show the fallback again. `on` compares
133
+ with `!==` (several keys: one string such as `` `${a()}/${b()}` ``, never a fresh array)
134
+ and takes effect only when no reader outside the boundary waits on the same write
135
+ (that is how one slow panel stops holding the page). Do not start `fetch` (or any request) at component-body top
113
136
  level — that runs once at mount and is not a reactive source. Do not `try/catch` `NotReadyError` around a read, and do not
114
137
  use `loadingValue` / `seedLoadingValue` as the default first-flight UI — those skip
115
138
  `<Loading>`. `{latest(() => x())}` is a preview, not the visible answer, and not a
@@ -143,9 +166,16 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
143
166
  `createContext<T>()` without a default throws when read outside a provider (good) — use
144
167
  that for any reactive payload. `createContext("light")` is only for a primitive static
145
168
  fallback; a dummy default on a store/signal context *runs* and then silently no-ops
146
- without a provider. Truly app-wide singletons (theme, session, locale) are a
147
- **module-level** signal/store — Context is subtree scoping, not a React-style
148
- app store. Module state is shared across SSR requests.
169
+ without a provider. **Shared state lives in a provider, including app-wide state**
170
+ (theme, session, cart, locale): a factory such as `createCart()` called by a provider
171
+ component placed at the root of `App`, read through `createContext<T>()` (no default)
172
+ and a `useCart()` hook. Module scope is for constants and the `createContext` call itself —
173
+ never `export const [session, setSession] = createStore(...)`. Module state has no
174
+ owner, and under `ssr: true` one module instance serves every request, so one
175
+ visitor's state renders into another's HTML (`[SERVER_WRITE]`); a module-level
176
+ store derived from a server function throws `Cannot call server function outside
177
+ of a request` at import. A client-only build happens to work, until the day SSR is
178
+ turned on.
149
179
  12. Refs: `let el!: HTMLDivElement` + `ref={el}` or `ref={(node) => (el = node)}`, or forward
150
180
  `props.ref`. Compose with arrays: `ref={[props.ref, (node) => (el = node)]}`. No
151
181
  `useRef`/`.current`. Ref callbacks run untracked and **without an owner**; their
@@ -155,12 +185,18 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
155
185
  `onSettled(() => { ...; return cleanup })` — `onCleanup` is for custom
156
186
  primitives and captured owners, not component bodies. Each `onSettled`
157
187
  registers a **single** fire (reads inside are untracked); ongoing
158
- imperative work is `createEffect`.
188
+ imperative work is `createEffect`, created in the component body — not inside
189
+ the callback. The `onSettled` callback is a restricted scope: `onCleanup`
190
+ (`[CLEANUP_IN_FORBIDDEN_SCOPE]`), creating a memo or effect
191
+ (`[PRIMITIVE_IN_FORBIDDEN_SCOPE]`), and `flush()` all throw there in dev and halt
192
+ the reactive system. A 1.x `onMount(() => { …; onCleanup(…) })` becomes
193
+ `onSettled(() => { const id = setInterval(tick, 1000); return () => clearInterval(id); })`.
159
194
  13. **Writes are staged** and commit on the next microtask. Event handlers need nothing
160
195
  special; tests and imperative integration code must call `flush()` before observing
161
196
  updated *synchronous* state or DOM. Waiting on an async memo is
162
197
  `await resolve(() => value())` (or Testing Library async queries), not `flush()`.
163
- Never call `flush()` inside an `action`.
198
+ Never call `flush()` inside an `action`, an `onSettled` callback (throws in dev), or an
199
+ effect callback (a no-op, `[FLUSH_IN_EFFECT_CALLBACK]`).
164
200
  14. Do not port these React tools — they have no Solid equivalent because the problems they
165
201
  solve don't exist: `useCallback`, `React.memo`, `forwardRef`, `useSyncExternalStore`,
166
202
  dependency arrays, `startTransition`/`useTransition` (updates are held and coordinated
@@ -170,8 +206,9 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
170
206
  JSX (`<Show>`, ternary, `<Switch>`/`<Match>`). Early returns on genuinely non-reactive
171
207
  values (build-time config, missing env) are fine. `<Switch>` is first-wins: later
172
208
  truthy `<Match>`es are skipped, not “all matching branches”.
173
- 16. Prefer `textContent` for text-only content. Use `innerHTML` only for trusted or
174
- sanitized markup — never interpolate user input into it, never React's
209
+ 16. Plain text is ordinary JSX children (`<p>{message()}</p>`); `textContent` is for an
210
+ element whose entire content is one text value, never together with children. Use
211
+ `innerHTML` only for trusted or sanitized markup — never interpolate user input into it, never React's
175
212
  `dangerouslySetInnerHTML`, and never combine `innerHTML` with JSX children (they fight
176
213
  over the same contents).
177
214
  17. **Never narrow a reactive read with a non-null assertion** (`error()!.message`,
@@ -190,17 +227,23 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
190
227
  by `<Errored>`; speculative value = `latest()`. `<Errored>` is graph status, not a
191
228
  terminal React ErrorBoundary: it heals when the source succeeds again (refresh,
192
229
  live reconnect, input change). `reset` retries the collected *sources*, not a UI
193
- remount. Per-row mutation failures belong in an errors map the projection folds
194
- in (survives the optimistic overlay), not in `<Errored>` around the whole list.
230
+ remount. `<Errored>` catches failing *reads* and renders, never a mutation: an
231
+ error that escapes an `action` discards its optimistic writes and rejects the
232
+ promise the call returned — no boundary sees it, and uncaught it is an unhandled
233
+ rejection with the UI silently reverted. Catch expected failures inside the action
234
+ (per-row: an errors map the projection folds in, which survives the optimistic
235
+ overlay; otherwise a toast), and `.catch` the call in the handler for the rest.
195
236
  Error monitoring is a hook, not a side effect in the fallback: the boundary-caught
196
237
  failures no global handler sees reach `configureClientErrors({ onError })` from
197
- `solid-js` (or `render(..., { onError })` per root) and `configureServerErrors({ onError })`
238
+ `solid-js` (or per root: `render(() => <App />, root, undefined, { onError })` — options
239
+ are the 4th argument, the 3rd is `init` — and `hydrate(fn, root, { onError })`) and `configureServerErrors({ onError })`
198
240
  from `@solidjs/web` — do not call `captureException` inside `<Errored fallback>`.
199
241
  The server hook's return value replaces the sanitized error on the wire: return
200
242
  nothing or a reference id, never the error itself (message/stack/secrets would
201
243
  reach the browser).
202
244
  Making a client store "real" is additive: same setters, wrap mutations in
203
- `action`, swap `createOptimisticStore` and a file of server functions — do
245
+ `action`, swap in the **function form** `createOptimisticStore(() => api.list(), [])`
246
+ (with `refresh` after the `yield`) and a file of server functions — do
204
247
  not rewrite `App.tsx` with loading/error branches.
205
248
  No request counters or `AbortController`s for dedup/cancellation — superseded
206
249
  answers drop automatically. Throw unusable responses; never `{ success: false }`
@@ -214,6 +257,9 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
214
257
  On a derived store, `refresh(store)` re-runs the derivation (`fn`); refreshing a
215
258
  plain signal accessor is a no-op. `refresh` returns a promise for the target's next
216
259
  settled state (`await refresh(user)`, or `yield refresh(x)` inside an action).
260
+ Polling is `onSettled(() => { const t = setInterval(() => refresh(stats), 30_000); return () => clearInterval(t); })`.
261
+ Two components that each create the same memo make two requests (no automatic
262
+ dedup) — create it once and pass it down or through context (router: `query`).
217
263
  These are three different APIs: core `refresh(source)` reruns a reactive source;
218
264
  router `revalidate(getUser.key)` invalidates the query cache; server-function
219
265
  `return reload({ revalidate: "todos" })` asks the integration to refresh cached
@@ -224,16 +270,27 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
224
270
  apply `reconcile(fresh, "id")(draft.slot)` inside a draft setter. Plain wholesale
225
271
  assignment (`draft.todos = fresh`) renders correctly but notifies every subscriber
226
272
  under the path and destroys row identity — same class of problem as unkeyed `<For>`.
273
+ `createProjection` returns the store itself, not a tuple (`const [rows] = createProjection(…)`
274
+ type-checks and yields row 0). `reconcile` onto a single-object store whose `id`
275
+ changes throws in dev (`Cannot reconcile states with different identity`) — switch records
276
+ with the function form (`createStore(() => getUser(id()), seed)`) or a returned object.
227
277
  21. **Solid inputs do not rewind the DOM.** `value={v()}` writes only when `v` changes; a
228
278
  React-style controlled input that ignores invalid keystrokes silently breaks. To reject
229
279
  input, write back explicitly:
230
280
  `onInput={(e) => { if (valid(e.currentTarget.value)) setV(e.currentTarget.value); else e.currentTarget.value = v(); }}`.
231
281
  22. Miscellaneous defaults: SSR-stable element ids come from `createUniqueId()` (never
232
282
  `Math.random()` or hardcoded duplicates); pass store data to `structuredClone` /
233
- `postMessage` / logs via `snapshot(store)` (proxies fail or leak reactivity); render
283
+ `postMessage` / logs via `snapshot(store)` (proxies fail or leak reactivity) — a
284
+ view, not a copy: unchanged subtrees are the store's own objects, so mutating it (a
285
+ library's `arr.sort()`) silently changes the store; copy with `structuredClone(snapshot(store))`; render
234
286
  modals/tooltips/overlays through `<Portal>` from `@solidjs/web`; mutations whose writes
235
287
  cross an async gap default to `action` + `createOptimistic`/`createOptimisticStore` —
236
- the sync mutation *is* the prediction (an overlay, discarded on settle); write the
288
+ the sync mutation *is* the prediction (an overlay, discarded on settle). Durable data
289
+ therefore lives in the function form (`createOptimisticStore(() => api.list(), [])`,
290
+ or one that reads a separate `createStore`); the value form
291
+ `createOptimisticStore([])` / `createOptimistic(false)` is a pure overlay with no
292
+ durable layer — action writes vanish on settle (a saved row disappears) and writes
293
+ outside an action do not stick — so use it only for in-flight flags. Write the
237
294
  body as a generator (`function*` / `async function*`) suspending on `yield`, never a
238
295
  plain `async` function (`solid2-kit check` flags that). After a bare `await`, put a
239
296
  bare `yield` before the next write **and** before anything that creates a reader
@@ -256,7 +313,12 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
256
313
  after touching `package.json` / tsconfig / root configs; it fails on React and Solid 1.x
257
314
  wiring. Server vs browser:
258
315
  `isServer` / `isDev` from `@solidjs/web`, or `clientOnly(() => import("./Widget"))` for
259
- browser-only components — never `typeof window` as the SSR boundary. Default
316
+ browser-only components — never `typeof window` as the SSR boundary. `isServer` picks
317
+ code paths, never markup: a JSX branch on it (or on `Date.now()` / `Math.random()`) is
318
+ a hydration mismatch. On the server the component body and memo/effect *computes*
319
+ run; effect `apply`, `onSettled`, and handlers do not. A `clientOnly` placeholder is
320
+ its `fallback` prop (`<Chart fallback={<Skeleton />} />`, rendered by SSR), not a
321
+ `<Loading>` around it; named exports take `{ export: "Chart" }`. Default
260
322
  `clientOnly` starts loading at declaration; `{ lazy: true }` defers until first
261
323
  render. `NoHydration` / `Hydration` split hydration *ownership*; they do not choose
262
324
  visible content — `clientOnly` is for components that must never run on the server.
@@ -282,7 +344,8 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
282
344
  fallback body), not event-time mutations. `JSON.stringify(store)`
283
345
  / `structuredClone(store)` without `snapshot(store)` can throw or leak proxies. Async SSR
284
346
  without `<Loading>` *works* but blocks the HTML shell until every read settles — wrap
285
- reads, or stream with `renderToStream`. `pipe` / `pipeTo` / `readable` each consume
347
+ reads, or stream with `renderToStream`. A read that must be in the first HTML (SEO,
348
+ link previews) takes `{ deferStream: true }` on its memo; keep the boundary. `pipe` / `pipeTo` / `readable` each consume
286
349
  a stream render — use exactly one. `createRoot` is for tests, libraries, and
287
350
  non-render entry points; inside a component let `render` / the component owner
288
351
  own the scope. Async reads inside `<Portal>` start on the client — hoist the
@@ -309,29 +372,47 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
309
372
  `revalidate(getUser.key)`. Do not wrap mutations in `query` (an undeclared server
310
373
  function becomes GET). `Router.paths` is not the current location (`useLocation` /
311
374
  `useParams`); search params via `useSearchParams` (merging setter; typed with a route
312
- `search` schema + path node). A route `preload` result is `props.data`. One router instance —
375
+ `search` schema + path node). A route `preload` *starts* reads and returns nothing —
376
+ `preload: ({ params }) => void getProduct(params.id)` (wrap the route in `defineRoute`
377
+ to type `params`) — and the page reads the same query in a memo:
378
+ `createMemo(() => getProduct(props.params.id))`. `props.data` is whatever `preload`
379
+ returned when the route first matched: it does not follow param changes (and an async
380
+ `preload` makes it a Promise), so return a value only for data that stays fixed while
381
+ the route is matched. One router instance —
313
382
  Solid Router does not support nested `<Router>` / nested `createRouter`.
314
383
  In-app navigation is `useNavigate` or `<a href={Router.paths...}>`, not
315
384
  `window.location` / `history.pushState`. Link state is automatic
316
385
  (`aria-current` / `data-active` / `data-pending` + CSS; `useLinkState` /
317
386
  `useIsRouting` in JSX) — never hand-rolled `location.pathname` comparisons. Trusted identity is
318
- `getRequestEvent()`, never a caller-supplied user id. Unscripted forms use
319
- the function `.url` (or a router `action`), not a hand-built `/_server/`
320
- path. Client history adapters do not select the SSR URL — pass
387
+ `getRequestEvent()`, never a caller-supplied user id. Unscripted (no-JS) POST
388
+ forms use a router `action` over a `(form: FormData)` server function (the post
389
+ arrives as one `FormData`; other signatures make `action={save}` TS2322) —
390
+ `<form action={save} method="post">` — not the
391
+ function's `.url` and not a hand-built `/_server/` path: a bare `"use server"`
392
+ function has no typed `.url` (TS2339). `.url` is typed only on `GET()` / `live()`
393
+ references, for `method="get"` search forms. Client history adapters do not select the SSR URL — pass
321
394
  `<Router url={request.url}>` (or rely on the request event). Secrets belong in
322
395
  `virtual:env/server`, never the `client` env map / `import.meta.env`. Document head is
323
396
  `<Title>` / `<Meta>` from `@solidjs/meta` with **no** `MetaProvider`. A static
324
397
  `<title>` in the document shell is the fallback; do not hardcode other tags
325
398
  (`<meta name="description">`) that Solid Meta should manage.
326
- Return `respond(value, { status, headers })` from a `"use server"` function, not
327
- `Response.json` — a raw `Response` is HTTP-handler control flow; scripted
328
- callers should receive the value. Production sanitizes every thrown value that
399
+ Success metadata is `return respond(value, { status: 201, headers })` from a
400
+ `"use server"` function, not `Response.json` — a raw `Response` is HTTP-handler control
401
+ flow; scripted callers should receive the value. A *returned* `respond()` resolves the
402
+ call whatever its status, so a failure is `throw respond(body, { status: 400 })`. Production sanitizes every thrown value that
329
403
  crosses the wire — server-function throws **and** SSR render errors (an `<Errored>`
330
404
  fallback printing `err().message` shows `"Internal Server Error"` in a production
331
405
  server render) — so intentional client-facing failures are `markSafeError(...)` or
332
406
  `throw respond(body, { status })`. Reads are function calls too (`await getUser(id)`
333
- on a `GET()`-declared read; the method mirrors the operation). Return or throw a
334
- redirect (`if (!session?.userId) throw redirect("/sign-in")`); `redirect()` /
407
+ on a `GET()`-declared read; the method mirrors the operation). `redirect()` /
408
+ `reload()` are signals for an integration: Solid Router's `action` / `query` and the
409
+ no-JS form post apply them. A direct caller — a core `action`, a handler, a plain
410
+ memo — never gets your data: it receives the raw `Response` as the resolved value
411
+ (typed as your data, no navigation); only when a mounted router's single-flight
412
+ hook intercepts a direct POST call does it navigate, and the call then resolves to
413
+ `null`. So guards `throw redirect("/sign-in")` only when router `action` /
414
+ `query` call the function; without the router, `throw respond(null, { status: 401 })`
415
+ (the call rejects) or return a value and navigate in the caller. `redirect()` /
335
416
  `respond()` take `revalidate` like `reload()`. Cache GET reads with
336
417
  `Cache-Control` on that response (the default is `no-store`), not a hand-rolled
337
418
  Solid cache. HTTP does not enforce TypeScript —
@@ -346,14 +427,18 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
346
427
  display). Do not write subscribe/unsubscribe in the component around `live()`.
347
428
  When the app has a server bundle (`ssr` or server functions), production is
348
429
  `handleRequest(request)` from the built `dist/server/server.js` (or its
349
- Fetchable `fetch`); platform Vite plugins adopt that handler (Node: the
350
- template `server.js`). Client-only start mode is static `dist/client` —
430
+ Fetchable `fetch`); platform Vite plugins adopt that handler. Node: set
431
+ `start: { node: true }` and run `node dist/server/node.js` (serves `dist/client`,
432
+ reads `PORT` / `HOST`; its exported `listener` mounts in an existing Express /
433
+ Fastify server) — `server.js` is only the handler module. Client-only start mode is static `dist/client` —
351
434
  there is no handler to wrap. Request middleware is
352
435
  `start: { middleware: "./src/middleware.ts" }`, not Express `app.use`. Do not return
353
436
  components from `"use server"` unless the project already enabled the
354
- experimental `serverFunctions.components` flag. JSON-encodable server-function arguments
355
- only, unless `enableRichArguments()` was called once in the client entry
356
- (`Date` / `Map` / `Set` throw without it). Declare live reads as `live(GET(fn))` —
437
+ experimental `serverFunctions.components` flag. Server-function arguments are JSON:
438
+ send `Date` / `Map` / `Set` as ISO strings / arrays (they throw otherwise; one `File` /
439
+ `Blob` / `FormData` argument travels natively). `enableRichArguments()` from
440
+ `@solidjs/web/server-functions/rich-args` (at `src/App.tsx` module scope) lifts that,
441
+ but in rc.9 importing it fails `vite build` (`"./client" is not exported`). Declare live reads as `live(GET(fn))` —
357
442
  `live()` outermost. A plain `async function*` server function is an event stream on
358
443
  one connection (no reconnect when it drops); `live()` is one value that changes over
359
444
  time and reconnects. `live()` connection state is
@@ -384,7 +469,7 @@ directory (default `src/`).
384
469
  | `solid/event-handlers` | still valid | rule 6 |
385
470
  | `solid/jsx-no-undef`, `jsx-no-duplicate-props` | covered by TypeScript | project typecheck |
386
471
  | `solid/prefer-classlist` | obsolete — `classList` removed | `solid2-kit check` bans `classList` |
387
- | `solid/no-unknown-namespaces` | obsolete — `on:`/`use:`/`attr:` removed in 2.0 | banned-API table + `solid2-kit check` |
472
+ | `solid/no-unknown-namespaces` | obsolete — `on:`/`use:`/`attr:` removed in 2.0 (`prop:` remains) | banned-API table + `solid2-kit check` |
388
473
  | `solid/no-proxy-apis` | obsolete — Solid 2 requires Proxy | not carried |
389
474
 
390
475
  ## Banned Solid 1.x APIs (Solid 2 replacements)
@@ -392,18 +477,18 @@ directory (default `src/`).
392
477
  | Never write (Solid 1.x) | Write instead (Solid 2) |
393
478
  |---|---|
394
479
  | `import ... from "solid-js/store"` or `"solid-js/web"` | stores/`merge`/`omit` from `"solid-js"`; `render`/`hydrate`/`Portal`/`Dynamic` from `"@solidjs/web"` |
395
- | `import type { JSX } from "solid-js"` / `JSX.Element` as the children type | `solid-js` exports no `JSX` namespace: children/returns are `Element` from `"solid-js"`; DOM-specific `JSX` types (`JSX.IntrinsicElements`, `JSX.CSSProperties`) from `"@solidjs/web"` |
480
+ | `import type { JSX } from "solid-js"` | `solid-js` exports no `JSX` namespace: markup types are `JSX.Element` from `"@solidjs/web"` (with `JSX.IntrinsicElements`, `JSX.CSSProperties`) or `Element` from `"solid-js"` — import the latter as `type Element as SolidElement` where the DOM `Element` is also used, or it shadows it |
396
481
  | `createResource` | async `createMemo` + `<Loading>`/`<Errored>`; `refresh()`, `latest()`, `isPending()` |
397
- | `createEffect(fn)` (one arg), `on(...)` | `createEffect(compute, apply)`; deps belong in `compute` |
398
- | `onMount` | `onSettled` (return cleanup from its callback) |
482
+ | `createEffect(fn)` (one arg), `on(...)`, `createTrackedEffect` (deprecated in 2.0) | `createEffect(compute, apply)`; deps belong in `compute`; one-time DOM work is `onSettled` |
483
+ | `onMount` | `onSettled(() => { ...; return cleanup })` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the callback |
399
484
  | `batch(...)` | delete it — writes auto-batch; `flush()` only to observe synchronously |
400
485
  | `<Suspense>`, `<ErrorBoundary>`, `<SuspenseList>` | `<Loading>`, `<Errored>` (fallback gets an error *accessor*), `<Reveal>` |
401
486
  | `<Index>` | `<For keyed={false}>` |
402
487
  | `<Ctx.Provider value={...}>` | `<Ctx value={...}>` |
403
- | `setState("a", "b", value)` path setters, `produce(...)` | draft setter: `setState(draft => { ... })` |
488
+ | `setState("a", "b", value)` path setters, `setState({ a })` merges, `produce(...)` | draft setter: `setState(draft => { ... })` (the others throw `fn is not a function`) |
404
489
  | `mergeProps` / `splitProps` / `unwrap` | `merge` / `omit` / `snapshot` |
405
490
  | `classList={...}` | `class` object/array form |
406
- | `createMutable`, `modifyMutable` | `createStore` + draft setters |
491
+ | `createMutable`, `modifyMutable` | `createStore` + draft setters (direct writes to the store are silently ignored) |
407
492
  | `createComputed`, `createSelector`, `createDeferred` | `createMemo`, `createProjection`, external scheduling |
408
493
  | `startTransition`, `useTransition` | automatic held updates + `isPending` |
409
494
  | `onError` / `catchError` | `<Errored>` or effect bundle `{ effect, error }` |
@@ -413,19 +498,19 @@ directory (default `src/`).
413
498
  | `clearDelegatedEvents` | delete — delegated listeners are scoped to each render root |
414
499
  | `vite-plugin-solid` / `jsxImportSource: "solid-js"` | `@solidjs/vite-plugin` / `"jsxImportSource": "@solidjs/web"` |
415
500
  | JSX `<Route>` / `<A>` / `<HashRouter>` / `<Navigate>` / `<FileRoutes>` | `createRouter({ routes })`, `fileRoutes(pageRoutes)` (`fileRoutes` from `@solidjs/router/fs`, `pageRoutes` from `virtual:file-routes`), plain `<a href={Router.paths...}>`, `hashHistory()`; link state via automatic `aria-current` / `data-active` / `data-pending` + CSS (`useLinkState` in JSX) |
501
+ | `useCurrentMatches`, `MemoryRouter` / `createMemoryHistory`, `redirect` / `reload` from `@solidjs/router`, `indexArray` | `useRouteMatches`, `createRouter({ routes, history: memoryHistory("/x") })`, `redirect` / `reload` from `@solidjs/web`, `mapArray(list, fn, { keyed: false })` |
416
502
  | `createAsync` / `createAsyncStore` / `useSubmission` / router `json()` / `cache()` | `createMemo(() => getUser(id()))`, `useSubmissions`, `respond()` from `@solidjs/web`, `query` |
417
503
  | `import ... from "@solidjs/start"` / `vinxi` / `h3` / `"use client"` | `GET` from `@solidjs/web/server-functions`; Solid has no `"use client"` |
418
504
  | `render(<App />, root)` | `render(() => <App />, root)` (same for `hydrate` / `renderToString` / `renderToStream`) |
419
505
  | `<MetaProvider>` | no provider — render `<Title>` / `<Meta>` / `<Link>` from `@solidjs/meta` anywhere |
420
- | `use:directive`, `on:`/`oncapture:`, `attr:`/`bool:`, `/*@once*/` | `ref` callbacks, camelCase event props, standard attributes, keep values reactive |
506
+ | `use:directive`, `on:`/`oncapture:`, `attr:`/`bool:`, `/*@once*/` | `ref` callbacks, camelCase event props, standard attributes, keep values reactive; `prop:` remains for DOM properties (`prop:indeterminate`, objects/arrays on custom elements — plain props are attributes and stringify) |
421
507
  | `resource.loading` / `resource.error` | `<Loading>` boundary / `<Errored>` boundary |
422
508
 
423
509
  When unsure about any API, verify against the official Solid 2.0 docs — fetchable URLs are
424
510
  listed in `references/official-docs.md` next to the `solid-2` skill. Do not guess from
425
511
  Solid 1.x or React memory.
426
512
 
427
- `createTrackedEffect` and `createRenderEffect` exist in Solid 2 but are not defaults:
428
- use two-phase `createEffect` unless a single tracked callback is required. `storePath`
513
+ `createRenderEffect` exists in Solid 2 but is not a default. `storePath`
429
514
  (the 1.x path-setter migration helper) is no longer exported from `solid-js` as of
430
515
  2.0.0-rc.9 — convert path setters to draft setters instead of importing it from
431
516
  `@solidjs/signals`.