solid2-agent-kit 0.23.1 → 0.25.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.
package/bin/baseline.mjs
CHANGED
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
// pulls them in — so doctor also checks an undeclared install of any package
|
|
20
20
|
// here, which is where a lockfile holds an older compiler.
|
|
21
21
|
export const BASELINE = {
|
|
22
|
-
'solid-js': { line: 2, baseline: '2.0.0-rc.
|
|
23
|
-
'@solidjs/web': { line: 2, baseline: '2.0.0-rc.
|
|
24
|
-
'@solidjs/signals': { line: 2, baseline: '2.0.0-rc.
|
|
25
|
-
'@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.
|
|
26
|
-
'@solidjs/compiler': { line: 2, baseline: '2.0.0-rc.
|
|
27
|
-
'@solidjs/babel-plugin': { line: 2, baseline: '2.0.0-rc.
|
|
28
|
-
'@solidjs/router': { line: 2, baseline: '2.0.0-next.
|
|
29
|
-
'@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.
|
|
22
|
+
'solid-js': { line: 2, baseline: '2.0.0-rc.21' },
|
|
23
|
+
'@solidjs/web': { line: 2, baseline: '2.0.0-rc.21' },
|
|
24
|
+
'@solidjs/signals': { line: 2, baseline: '2.0.0-rc.21' },
|
|
25
|
+
'@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.21' },
|
|
26
|
+
'@solidjs/compiler': { line: 2, baseline: '2.0.0-rc.21' },
|
|
27
|
+
'@solidjs/babel-plugin': { line: 2, baseline: '2.0.0-rc.21' },
|
|
28
|
+
'@solidjs/router': { line: 2, baseline: '2.0.0-next.44' },
|
|
29
|
+
'@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.57' },
|
|
30
30
|
'@solidjs/testing-library': { line: 1, baseline: '1.0.0-rc.2' },
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -5,4 +5,4 @@
|
|
|
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`. An edit-time hook may also run this gate automatically after each file edit — treat its findings as errors to fix immediately, not warnings. Do not install `eslint-plugin-solid` (built for Solid 1.x; misreads Solid 2 idioms).
|
|
6
6
|
- After editing `package.json`, `tsconfig*.json`, or root config files (Vite, ESLint), run `solid2-kit doctor` — it fails on React/Solid 1.x wiring: `react`/`vite-plugin-solid`/`eslint-plugin-solid`/SolidStart dependencies, 1.x `solid-js` ranges, and `jsx`/`jsxImportSource` values other than `preserve`/`@solidjs/web`.
|
|
7
7
|
- The kit's guidance and gate files (`.cursor/rules/solid-2.mdc`, the `solid-2` skill directories, hook entries, managed blocks in this file and `CLAUDE.md`) are guardrails, not obstacles. Never edit, weaken, bypass, or delete them to make a failure go away — fix the flagged code. If a guardrail seems wrong, stop and tell the user.
|
|
8
|
-
- 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 `formAction`/`query` from `@solidjs/router` are POST forms and a read cache (router `action` is a deprecated alias of `formAction`, and `useAction` is deprecated: call the server function directly — a mounted router applies its revalidation and redirect on any call <!-- upstream:router-action-deprecated -->). React `useDeferredValue` is `createDeferred(fn)`: an async memo that lags but never leads <!-- upstream:create-deferred-export -->. Core `refresh(source)`, router `revalidate(key)`, and server-function `return reload(...)` are three different APIs. Pass the accessor to `isPending(user)`, not `isPending(user())`. Pass async values as values (`user={user()}`) — JSX props are lazy; types stay `User`, not `Promise<User>`. `<Loading>` wraps the read, not the memo. Nested fetches run in parallel (components do not suspend). `isPending` is a per-expression question (`isPending(() => props.story)` / `isPending(selectedId)`), not a global spinner; `latest(selectedId)` is opt-in so highlight and content stay consistent by default. `<Errored>` heals when the source succeeds. Pass a function to `render`/`hydrate`: `render(() => <App />, root)`. Component teardown is `onSettled`, not `onCleanup` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the `onSettled` callback in dev. Creating a memo/effect/root inside an effect's `apply` callback throws in dev too (`[PRIMITIVE_IN_EFFECT_CALLBACK]`) — create it in the body or compute phase. <!-- upstream:primitive-in-effect-callback --> Effect `apply` functions use block bodies (`(t) => { document.title = t; }`): any return value other than a cleanup function halts reactivity. Stores are written only through the setter's draft — writes to the store itself are ignored (dev warns `[STORE_WRITE_OUTSIDE_SETTER]` <!-- upstream:store-write-outside-setter -->), and `Map`/`Set`/`Date` inside a store are not tracked. Shared state, app-wide included, is created in a provider at the root of `App` (`createContext<T>()` + `useX()`), never a module-level signal/store (one instance serves every SSR request). Errors thrown out of an `action` never reach `<Errored>` — catch them in the action or `.catch` the call. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — markup types are `JSX.Element` from `@solidjs/web` or `Element` from `solid-js` (alias it `SolidElement` next to DOM code) <!-- upstream:jsx-namespace-absent -->. Reset-a-signal-on-change is a writable derivation (`createSignal(() => { source(); return initial; })`), never an effect calling the setter. Client mode mounts with `render()` into an empty body — no hydration mismatch exists, so never defer initial values to post-mount React-style (that can overwrite persisted state). Client → server is additive (same setters + `action` + server functions): do not rewrite App with loading/error branches, disable optimistic rows until ack, invent tRPC, or refetch in `hydrate`/`onSettled`. The graph continues across the wire (`live()` owns the client connection; `<Reveal>` not a delayed stream). A server bundle uses `handleRequest` / Fetchable `fetch`; client-only start is static `dist/client`. One browser-only value takes `ssrSource: "client"` on the memo/signal/derived store, not a `clientOnly` component split. Router link state is `aria-current`/`data-active` + CSS (`data-pending` is opt-in via `createRouter({ links: pendingLinks })`; `useLinkState`/`useIsRouting` in JSX) <!-- upstream:pending-links-opt-in -->; link preloading is opt-in too (`preloadLinks: intentPreload()` for hover/focus/touch; none by default; opt one link out with the string `preload="false"`) <!-- upstream:preload-links-opt-in -->; search params via `useSearchParams`; router forms validate on the server (`throw respond({issues},{400})`) with inline errors from `useSubmissions`. Confirmations on another channel — fire-and-forget sends, and mutations whose result is read through `live()` — wait with `yield until(predicate, {timeout})`, not `refresh()`. Declare live reads `live(fn)` — it implies `GET` (`live(GET(fn))` is the same) <!-- upstream:live-implies-get -->. Debounce at the handler; inputs feeding a request bind `latest(query)`. Dev diagnostics are errors: `[STRICT_READ_UNTRACKED]` means a read outside a tracking scope, `[ASYNC_STORE_SETTER]` means a store setter callback awaited (the draft is a synchronous transaction — await first, then write) <!-- upstream:async-store-setter-throws -->, a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]
|
|
8
|
+
- 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 `formAction`/`query` from `@solidjs/router` are POST forms and a read cache (router `action` is a deprecated alias of `formAction`, and `useAction` is deprecated: call the server function directly — a mounted router applies its revalidation and redirect on any call <!-- upstream:router-action-deprecated -->). React `useDeferredValue` is `createDeferred(fn)`: an async memo that lags but never leads <!-- upstream:create-deferred-export -->. Core `refresh(source)`, router `revalidate(key)`, and server-function `return reload(...)` are three different APIs. Pass the accessor to `isPending(user)`, not `isPending(user())`. Pass async values as values (`user={user()}`) — JSX props are lazy; types stay `User`, not `Promise<User>`. `<Loading>` wraps the read, not the memo. Nested fetches run in parallel (components do not suspend). `isPending` is a per-expression question (`isPending(() => props.story)` / `isPending(selectedId)`), not a global spinner; `latest(selectedId)` is opt-in so highlight and content stay consistent by default. `<Errored>` heals when the source succeeds. Pass a function to `render`/`hydrate`: `render(() => <App />, root)`. Component teardown is `onSettled`, not `onCleanup` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the `onSettled` callback in dev. Creating a memo/effect/root inside an effect's `apply` callback throws in dev too (`[PRIMITIVE_IN_EFFECT_CALLBACK]`) — create it in the body or compute phase. <!-- upstream:primitive-in-effect-callback --> Effect `apply` functions use block bodies (`(t) => { document.title = t; }`): any return value other than a cleanup function halts reactivity. Stores are written only through the setter's draft — writes to the store itself are ignored (dev warns `[STORE_WRITE_OUTSIDE_SETTER]` <!-- upstream:store-write-outside-setter -->), and `Map`/`Set`/`Date` inside a store are not tracked. Shared state, app-wide included, is created in a provider at the root of `App` (`createContext<T>()` + `useX()`), never a module-level signal/store (one instance serves every SSR request). Errors thrown out of an `action` never reach `<Errored>` — catch them in the action or `.catch` the call. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — markup types are `JSX.Element` from `@solidjs/web` or `Element` from `solid-js` (alias it `SolidElement` next to DOM code) <!-- upstream:jsx-namespace-absent -->. Reset-a-signal-on-change is a writable derivation (`createSignal(() => { source(); return initial; })`), never an effect calling the setter. Client mode mounts with `render()` into an empty body — no hydration mismatch exists, so never defer initial values to post-mount React-style (that can overwrite persisted state). Client → server is additive (same setters + `action` + server functions): do not rewrite App with loading/error branches, disable optimistic rows until ack, invent tRPC, or refetch in `hydrate`/`onSettled`. The graph continues across the wire (`live()` owns the client connection; `<Reveal>` not a delayed stream). A server bundle uses `handleRequest` / Fetchable `fetch`; client-only start is static `dist/client`. One browser-only value takes `ssrSource: "client"` on the memo/signal/derived store, not a `clientOnly` component split. Router link state is `aria-current`/`data-active` + CSS (`data-pending` is opt-in via `createRouter({ links: pendingLinks })`; `useLinkState`/`useIsRouting` in JSX) <!-- upstream:pending-links-opt-in -->; link preloading is opt-in too (`preloadLinks: intentPreload()` for hover/focus/touch; none by default; opt one link out with the string `preload="false"`) <!-- upstream:preload-links-opt-in -->; search params via `useSearchParams`; router forms validate on the server (`throw respond({issues},{400})`) with inline errors from `useSubmissions`. Confirmations on another channel — fire-and-forget sends, and mutations whose result is read through `live()` — wait with `yield until(predicate, {timeout})`, not `refresh()`. Declare live reads `live(fn)` — it implies `GET` (`live(GET(fn))` is the same) <!-- upstream:live-implies-get -->. Debounce at the handler; inputs feeding a request bind `latest(query)`. Dev diagnostics are errors: `[STRICT_READ_UNTRACKED]` means a read outside a tracking scope, `[ASYNC_STORE_SETTER]` means a store setter callback awaited (the draft is a synchronous transaction — await first, then write) <!-- upstream:async-store-setter-throws -->, a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]` (a write that must not join another update's hold goes in `onFlush(...)` <!-- upstream:on-flush -->), and `[UNCOMMITTED_INPUT_OVERWRITTEN]` means a `value`/`checked` binding re-wrote the bound value over text the user typed but never committed (`value={props.name} onChange={commit}` with no draft signal) — bind a draft signal written from `onInput`, or `defaultValue` <!-- upstream:uncommitted-input-overwritten -->. Error monitoring is `configureClientErrors` / `configureServerErrors` (or per root `render(fn, root, undefined, { onError })` — options are the 4th argument), not a side effect inside `<Errored fallback>`; the server hook must return a reference or nothing, never the error itself. `<Dynamic>` is deprecated — use `dynamic()`. <!-- upstream:dynamic-deprecated --> Observability is on by default in development: whenever a change touches stores, lists, async, actions, or effects, keep the dev console free of `[CODE]` findings and run an attribution session (`/__solid/diagnostics` `begin` → interact → `whyDidRun` / `costs` → `end` with `@solidjs/diagnostics` installed, otherwise `isDev`-guarded `attribution.enable({ log: false })` + `why()` / `costs()`) before finishing. `vite dev` is always the dev build; `solid({ observe: true })` is a production opt-in.
|
|
@@ -209,7 +209,8 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
209
209
|
the callback. The `onSettled` callback is a restricted scope: `onCleanup`
|
|
210
210
|
(`[CLEANUP_IN_FORBIDDEN_SCOPE]`), creating a memo or effect
|
|
211
211
|
(`[PRIMITIVE_IN_FORBIDDEN_SCOPE]`), and `flush()` all throw there in dev and halt
|
|
212
|
-
the reactive system
|
|
212
|
+
the reactive system; a pending async read there warns `[PENDING_ASYNC_FORBIDDEN_SCOPE]`
|
|
213
|
+
<!-- upstream:pending-async-forbidden-scope --> (nothing re-runs the callback when the answer lands). A 1.x `onMount(() => { …; onCleanup(…) })` becomes
|
|
213
214
|
`onSettled(() => { const id = setInterval(tick, 1000); return () => clearInterval(id); })`.
|
|
214
215
|
13. **Writes are staged** and commit on the next microtask. Event handlers need nothing
|
|
215
216
|
special; tests and imperative integration code must call `flush()` before observing
|
|
@@ -550,7 +551,7 @@ directory (default `src/`).
|
|
|
550
551
|
| `import ... from "solid-js/store"` or `"solid-js/web"` | stores/`merge`/`omit` from `"solid-js"`; `render`/`hydrate`/`Portal`/`Dynamic` from `"@solidjs/web"` |
|
|
551
552
|
| `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 <!-- upstream:jsx-namespace-absent --> |
|
|
552
553
|
| `createResource` | async `createMemo` + `<Loading>`/`<Errored>`; `refresh()`, `latest()`, `isPending()` |
|
|
553
|
-
| `createEffect(fn)` (one arg), `on(...)`, `createTrackedEffect` (deprecated in 2.0) <!-- upstream:tracked-effect-deprecated --> | `createEffect(compute, apply)`; deps belong in `compute`; one-time DOM work is `onSettled` |
|
|
554
|
+
| `createEffect(fn)` (one arg — dev throws `[MISSING_EFFECT_FN]` <!-- upstream:missing-effect-fn -->), `on(...)`, `createTrackedEffect` (deprecated in 2.0) <!-- upstream:tracked-effect-deprecated --> | `createEffect(compute, apply)`; deps belong in `compute`; one-time DOM work is `onSettled` |
|
|
554
555
|
| `onMount` | `onSettled(() => { ...; return cleanup })` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the callback |
|
|
555
556
|
| `batch(...)` | delete it — writes auto-batch; `flush()` only to observe synchronously |
|
|
556
557
|
| `<Suspense>`, `<ErrorBoundary>`, `<SuspenseList>` | `<Loading>`, `<Errored>` (fallback gets an error *accessor*), `<Reveal>` |
|
|
@@ -564,7 +565,7 @@ directory (default `src/`).
|
|
|
564
565
|
| `createDeferred(source, { timeoutMs })` (1.x: a signal that lags by idle time) | `createDeferred(fn)` (2.0: an async memo that may lag its inputs but never leads them; `isPending(deferred)` while it catches up) <!-- upstream:create-deferred-export --> |
|
|
565
566
|
| `startTransition`, `useTransition` | automatic held updates + `isPending` |
|
|
566
567
|
| `onError` / `catchError` | `<Errored>` or effect bundle `{ effect, error }` |
|
|
567
|
-
| `from(...)` / `observable(...)` (the solid-js helpers) | inbound:
|
|
568
|
+
| `from(...)` / `observable(...)` (the solid-js helpers) | inbound: `from(source)` — the same name in 2.0, but it returns an AsyncIterable for a computation (`createMemo(() => from(source))`), not a signal <!-- upstream:from-source-adapts -->; outbound: split effect |
|
|
568
569
|
| `createDynamic(...)`, `<Dynamic component={...}>` (deprecated in 2.0) <!-- upstream:dynamic-deprecated --> | `dynamic(source)` from `@solidjs/web` — `dynamicComponent(source)` from `solid-js` when the source only answers with a component <!-- upstream:dynamic-component-export --> |
|
|
569
570
|
| `renderToStringAsync(...)` | `await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`) |
|
|
570
571
|
| `clearDelegatedEvents` | delete — delegated listeners are scoped to each render root |
|
|
@@ -582,6 +583,11 @@ When unsure about any API, verify against the official Solid 2.0 docs — fetcha
|
|
|
582
583
|
listed in `references/official-docs.md` next to the `solid-2` skill. Do not guess from
|
|
583
584
|
Solid 1.x or React memory.
|
|
584
585
|
|
|
586
|
+
`createOwner` is not public API either: since 2.0.0-rc.21 it is typed only through
|
|
587
|
+
`solid-js/internal` (a runtime export kept for the renderers) <!-- upstream:create-owner-internal --> — host a
|
|
588
|
+
scope with `createRoot()`, pass its `dispose` around as a plain function, and re-enter
|
|
589
|
+
with `runWithOwner(owner, …)`.
|
|
590
|
+
|
|
585
591
|
`createRenderEffect` exists in Solid 2 but is not a default. `storePath`
|
|
586
592
|
(the 1.x path-setter migration helper) is no longer exported from `solid-js` as of
|
|
587
593
|
2.0.0-rc.9 — convert path setters to draft setters instead of importing it from
|
|
@@ -44,7 +44,7 @@ Practical consequences:
|
|
|
44
44
|
| App-wide state (theme, session, cart, locale) | the same provider, placed at the root of `App` — not a module-level signal/store (under SSR one module instance serves every request) |
|
|
45
45
|
| Constants (a category list, a formatter, the `createContext` call) | module scope |
|
|
46
46
|
| Tentative value during a mutation | `createOptimistic` / `createOptimisticStore` + `action` |
|
|
47
|
-
| Values pushed by an external subscription (websocket, reactive client) | async iterable returned from a memo (or function-form `createStore` for keyed reconciliation) — never `{ data, error }` signal pairs |
|
|
47
|
+
| Values pushed by an external subscription (websocket, reactive client) | async iterable returned from a memo — `from(source)` adapts a callback subscription, an Observable, or an AsyncIterable <!-- upstream:from-source-adapts --> (or function-form `createStore` for keyed reconciliation) — never `{ data, error }` signal pairs |
|
|
48
48
|
|
|
49
49
|
### Should this be an effect?
|
|
50
50
|
|
|
@@ -307,7 +307,10 @@ returns the value it set. Dev throws `effect callback returned an invalid cleanu
|
|
|
307
307
|
production throws a `TypeError` on the next run, and both halt the whole
|
|
308
308
|
reactive system (`[REACTIVITY_HALTED]`). TypeScript reports it as TS2345; a JS project
|
|
309
309
|
gets no warning. The cleanup of an effect is the `apply` return value — `onCleanup`
|
|
310
|
-
inside `apply` has no owner and never runs (`[NO_OWNER_CLEANUP]`).
|
|
310
|
+
inside `apply` has no owner and never runs (`[NO_OWNER_CLEANUP]`). The two-phase
|
|
311
|
+
signature itself is enforced: `createEffect(compute)` with no apply function throws
|
|
312
|
+
`[MISSING_EFFECT_FN]` in dev <!-- upstream:missing-effect-fn --> (prod dies on the same call with an uncoded
|
|
313
|
+
`TypeError`).
|
|
311
314
|
|
|
312
315
|
**Effects never compare.** `apply` runs on every re-run of `compute`, whether or not the
|
|
313
316
|
computed value changed; there is no `equals` on an effect. When equal results should skip
|
|
@@ -330,6 +333,13 @@ intercepted with the bundle form `createEffect(compute, { effect, error })`. Ski
|
|
|
330
333
|
initial run with `{ defer: true }` — the `on(deps, fn, { defer: true })` replacement;
|
|
331
334
|
the effect first runs on the next change.
|
|
332
335
|
|
|
336
|
+
When the side effect should fire **once per arming** rather than on every change —
|
|
337
|
+
"run this the first time the value crosses the threshold" — use `createReaction(fn)`
|
|
338
|
+
from `solid-js`: it returns a tracker; `fn` runs once after a source read inside a
|
|
339
|
+
`track(() => …)` call changes, then stays silent until the tracker is called again
|
|
340
|
+
(re-arm from inside `fn` to keep listening). Nothing runs at creation, and changes
|
|
341
|
+
made while unarmed are ignored. <!-- upstream:create-reaction -->
|
|
342
|
+
|
|
333
343
|
A two-phase effect whose apply only calls a local signal setter is "state + effect" in
|
|
334
344
|
disguise — formally legal Solid 2, still rule 4 (`solid2-kit check` flags it). The reset
|
|
335
345
|
form of a writable derivation replaces it, and moves the source read to the read site
|
|
@@ -452,7 +462,9 @@ return <StoryDetail story={story} />;
|
|
|
452
462
|
`affects(item, "quantity")`) or `isPending(() => todos.length)` dims only what
|
|
453
463
|
that read depends on. A hold with no feedback at all is `[SILENT_HOLD]` under
|
|
454
464
|
attribution — always pair a held write with one of these questions, an
|
|
455
|
-
optimistic value, or `affects()`.
|
|
465
|
+
optimistic value, or `affects()`. `affects` keys address store leaves only:
|
|
466
|
+
`affects(accessor, "quantity")` throws `[INVALID_AFFECTS_TARGET]` in dev
|
|
467
|
+
<!-- upstream:invalid-affects-target --> — the keyed form needs a store (or store row); a bare accessor takes no key.
|
|
456
468
|
- Default navigation **holds**. `selectedId()` and the old story stay put until
|
|
457
469
|
the new answer lands, so the highlight never points at the wrong content.
|
|
458
470
|
`latest(selectedId)` is the opt-in when the design wants the highlight to move
|
|
@@ -460,14 +472,27 @@ return <StoryDetail story={story} />;
|
|
|
460
472
|
`transition-delay` so fast swaps do not flash. Inputs that feed a request follow
|
|
461
473
|
the same rule: bind `value={latest(query)}` so a controlled search box reflects
|
|
462
474
|
keystrokes while its write is held (an uncontrolled input needs nothing).
|
|
475
|
+
When one write in a handler must *not* join the hold — closing a menu before
|
|
476
|
+
`props.onSelect` starts a navigation — defer just that write with
|
|
477
|
+
`onFlush(() => setOpen(false))` (since 2.0.0-rc.19): it runs once right after the
|
|
478
|
+
pending flush as its own update, instead of flushing early
|
|
479
|
+
<!-- upstream:on-flush -->. It belongs in event handlers and effect callbacks; in a
|
|
480
|
+
component body or computation dev warns `ON_FLUSH_IN_OWNED_SCOPE`.
|
|
463
481
|
- Read every reactive input **before the first `await`** in an async computation. A read
|
|
464
482
|
after `await` does not subscribe (dev reports `[UNTRACKED_READ_AFTER_AWAIT]` on V8 —
|
|
465
483
|
Chromium, Node, Deno, Bun); production can sit pending with no retry.
|
|
484
|
+
- An async memo returns **data**, never rendered output. On the server, a memo (or async
|
|
485
|
+
iterable source) landing on JSX, a component result, or a `<Loading>`/`<Errored>`
|
|
486
|
+
boundary cannot serialize for hydration: since 2.0.0-rc.19 the memo rejects with
|
|
487
|
+
`ASYNC_MEMO_ELEMENT` and the nearest `<Errored>` contains it like any render error
|
|
488
|
+
<!-- upstream:async-memo-element -->. Render the JSX from the data the memo returns.
|
|
466
489
|
- `<Errored>` function fallbacks receive an error **accessor** and a `reset` callback:
|
|
467
490
|
`fallback={(error, reset) => ...}`. The boundary **heals** when the source succeeds
|
|
468
491
|
again (refresh, live reconnect, input change) — it is graph status, not a terminal
|
|
469
492
|
React ErrorBoundary. `reset` retries the collected *data sources*, not a UI remount.
|
|
470
|
-
- Refetch: `refresh(results)`.
|
|
493
|
+
- Refetch: `refresh(results)`. `refresh` takes a source accessor or a refreshable
|
|
494
|
+
store only — anything else (a plain function included) throws `[INVALID_REFRESH_TARGET]`
|
|
495
|
+
in dev <!-- upstream:invalid-refresh-target -->, and prod silently accepts it. In-flight indicator: `isPending(results)` (or
|
|
471
496
|
`isPending(() => results())`). Freshest in-flight value for a preview:
|
|
472
497
|
`latest(results)`. `latest` is not a null-safe probe: before the source's first value
|
|
473
498
|
it throws `NotReadyError` in every scope (since 2.0.0-rc.9 also in event handlers and
|
|
@@ -540,7 +565,7 @@ functions (middleware, request setup) reported via
|
|
|
540
565
|
`renderToString` / `renderToStream`'s first pass is reported `render` / `failed`
|
|
541
566
|
before rethrow. <!-- upstream:report-request-failure --> A monitoring SDK's `init()` that
|
|
542
567
|
registers this hook must load before the server graph — put it in the plugin's
|
|
543
|
-
`start: {
|
|
568
|
+
`start: { serverSetup: "./src/instrument.ts" }`, not at the top of an entry (see
|
|
544
569
|
[Production observability](#production-observability-the-observe-build)).
|
|
545
570
|
|
|
546
571
|
`render` / `hydrate` accept a per-root `onError` that wins over the ambient hook —
|
|
@@ -558,7 +583,15 @@ errors matter (the rc.8 one-argument form keeps working). <!-- upstream:render-o
|
|
|
558
583
|
Push-based sources integrate by returning an **async iterable** from a computation — reads
|
|
559
584
|
then suspend to `<Loading>` until the first value, later pushes update the settled value,
|
|
560
585
|
and thrown errors reach `<Errored>`. Integration layers should return a plain accessor, not
|
|
561
|
-
`{ data, error }` signals
|
|
586
|
+
`{ data, error }` signals.
|
|
587
|
+
|
|
588
|
+
For a callback subscription, an Observable, or an existing AsyncIterable, the adapter is
|
|
589
|
+
built in: `from(source)` returns the async iterable a computation consumes —
|
|
590
|
+
`createMemo(() => from(source))` <!-- upstream:from-source-adapts -->. Read the inputs that
|
|
591
|
+
choose the source before calling it, so a change re-runs the memo, closes the old source,
|
|
592
|
+
and opens the new one <!-- upstream:from-source-adapts -->. The hand-rolled form below remains for what `from` does not fit —
|
|
593
|
+
an event stream where every message matters (keep a queue instead of conflating to the
|
|
594
|
+
latest), or a subscribe signature that is not `(next, error, complete) => unsubscribe`:
|
|
562
595
|
|
|
563
596
|
```tsx
|
|
564
597
|
import { createMemo, onCleanup } from 'solid-js';
|
|
@@ -736,7 +769,8 @@ list). An in-flight action does not freeze unrelated writes. Collecting failed
|
|
|
736
769
|
actions to replay is a design choice, not something the runtime requires.
|
|
737
770
|
|
|
738
771
|
Ordinary signal/store writes inside an action are held until it settles. Never call
|
|
739
|
-
`flush()` inside an action. Invoke actions from handlers, not from component/computation bodies
|
|
772
|
+
`flush()` inside an action. Invoke actions from handlers, not from component/computation bodies (dev warns
|
|
773
|
+
`[ACTION_CALLED_IN_OWNED_SCOPE]` <!-- upstream:action-in-owned-scope -->).
|
|
740
774
|
Do not set a `submitted` signal and watch it from an effect — that work belongs in the
|
|
741
775
|
handler or the `action`.
|
|
742
776
|
|
|
@@ -748,7 +782,9 @@ leaves an empty list once the action settles, with no warning. <!-- upstream:opt
|
|
|
748
782
|
from the function form — `createOptimisticStore(() => api.list(), [])` refreshed
|
|
749
783
|
after the `yield`, or `createOptimisticStore(() => base.todos, [])` over a separate
|
|
750
784
|
`createStore` that the action writes the confirmed value into. Keep the value form for
|
|
751
|
-
in-flight flags.
|
|
785
|
+
in-flight flags. A write against a function-form source whose first answer has not
|
|
786
|
+
landed yet is not guessed at all — dev warns `[OPTIMISTIC_WRITE_BEFORE_LOAD]`
|
|
787
|
+
<!-- upstream:optimistic-write-before-load -->; let the first answer land before writing.
|
|
752
788
|
|
|
753
789
|
`createOptimistic` / `createOptimisticStore` are for a tentative value during an
|
|
754
790
|
active mutation, not a local editing session (use a writable derivation or a plain
|
|
@@ -1073,7 +1109,11 @@ do not `return () => cleanup`. Put owned setup in a **directive factory** (like
|
|
|
1073
1109
|
`onCleanup` throws there (`[CLEANUP_IN_FORBIDDEN_SCOPE]`), creating a memo or effect
|
|
1074
1110
|
throws (`[PRIMITIVE_IN_FORBIDDEN_SCOPE]`), and so does `flush()` — each halts the
|
|
1075
1111
|
reactive system in dev. Return the cleanup; create effects and memos in the component
|
|
1076
|
-
body, next to the `onSettled` call, not inside it.
|
|
1112
|
+
body, next to the `onSettled` call, not inside it. A pending async read inside the
|
|
1113
|
+
callback warns `[PENDING_ASYNC_FORBIDDEN_SCOPE]` <!-- upstream:pending-async-forbidden-scope --> — the callback is not a
|
|
1114
|
+
computation, so nothing re-runs it when the answer lands. Register `onSettled` in the
|
|
1115
|
+
body, not out-of-band after an `await`: a cleanup returned from an unowned registration
|
|
1116
|
+
warns `[SETTLED_CLEANUP_UNOWNED]` <!-- upstream:settled-cleanup-unowned --> and never runs.
|
|
1077
1117
|
|
|
1078
1118
|
```tsx
|
|
1079
1119
|
// WRONG — the mechanical 1.x port of onMount + onCleanup
|
|
@@ -1098,6 +1138,12 @@ component body or the compute phase, or attach them to an owner captured with
|
|
|
1098
1138
|
`getOwner()` in the body via `runWithOwner(owner, …)`.
|
|
1099
1139
|
<!-- upstream:primitive-in-effect-callback -->
|
|
1100
1140
|
|
|
1141
|
+
`createOwner` is not public API: since rc.21 it is typed only through
|
|
1142
|
+
`solid-js/internal`, as a runtime export kept for the renderers
|
|
1143
|
+
<!-- upstream:create-owner-internal -->. Host a scope with `createRoot()` and pass its `dispose` argument
|
|
1144
|
+
around as a plain function (a detached `owner.dispose` is a method and throws unbound
|
|
1145
|
+
<!-- upstream:ownerless-and-disposed -->); re-enter the scope with `runWithOwner(owner, …)`.
|
|
1146
|
+
|
|
1101
1147
|
### Per-value SSR policy: `ssrSource`
|
|
1102
1148
|
|
|
1103
1149
|
`clientOnly` splits a *component*; `ssrSource` keeps one *value* off the server,
|
|
@@ -1227,13 +1273,23 @@ Always-on dev checks include misplaced reads
|
|
|
1227
1273
|
for a first read made after an `await` in an async compute — V8 engines only), misplaced writes
|
|
1228
1274
|
(`[REACTIVE_WRITE_IN_OWNED_SCOPE]` — since rc.9 also for store setters called in a
|
|
1229
1275
|
component or root body <!-- upstream:store-write-owned-scope -->, `[ASYNC_STORE_SETTER]` for a store setter callback that returns
|
|
1230
|
-
a promise <!-- upstream:async-store-setter-throws -->, `[FLUSH_IN_ACTION]`,
|
|
1276
|
+
a promise <!-- upstream:async-store-setter-throws -->, `[FLUSH_IN_ACTION]`,
|
|
1277
|
+
`[ACTION_CALLED_IN_OWNED_SCOPE]` for an `action()` invoked from a component body or
|
|
1278
|
+
computation <!-- upstream:action-in-owned-scope -->, `[SERVER_WRITE]`,
|
|
1231
1279
|
and since rc.17 `[STORE_WRITE_OUTSIDE_SETTER]` for a write to the store proxy itself
|
|
1232
1280
|
<!-- upstream:store-write-outside-setter -->), misplaced bindings
|
|
1233
1281
|
(`[UNCOMMITTED_INPUT_OVERWRITTEN]` — a `value` / `checked` binding re-applied its
|
|
1234
1282
|
committed value over uncommitted user input <!-- upstream:uncommitted-input-overwritten -->),
|
|
1235
|
-
`[ASYNC_OUTSIDE_LOADING_BOUNDARY]` when a tracked read has no boundary above it,
|
|
1236
|
-
`[LOADING_ON_OUTSIDE_HOLD]` when a `<Loading on>` boundary's data is also read outside it
|
|
1283
|
+
`[ASYNC_OUTSIDE_LOADING_BOUNDARY]` when a tracked read has no boundary above it,
|
|
1284
|
+
`[LOADING_ON_OUTSIDE_HOLD]` when a `<Loading on>` boundary's data is also read outside it,
|
|
1285
|
+
`[LOADING_ON_ACCESSOR]` when a `<Loading on>` receives a function instead of the value
|
|
1286
|
+
(the boundary never calls it, so it never re-arms; since rc.20) <!-- upstream:loading-on-accessor -->, and
|
|
1287
|
+
since rc.21 `[CAUGHT_REACTIVE_READ]` when a computation catches a reactive read's throw
|
|
1288
|
+
itself and completes with a value — the catch masks the load or hides the error from the
|
|
1289
|
+
boundaries <!-- upstream:caught-reactive-read -->. Ownerless creation warns rather than throws: `[NO_OWNER_EFFECT]`
|
|
1290
|
+
for an effect created outside any root or component, `[NO_OWNER_BOUNDARY]` for a
|
|
1291
|
+
boundary invoked outside a reactive context, and `[RUN_WITH_DISPOSED_OWNER]` for
|
|
1292
|
+
`runWithOwner` on a disposed owner <!-- upstream:ownerless-and-disposed --> — none of them is ever disposed.
|
|
1237
1293
|
Each entry carries a stable `[CODE]`, what the runtime observed, an owner chain
|
|
1238
1294
|
(`in <App> › <Cart> › <LineItem>`), and — since rc.16, under `vite dev` — an
|
|
1239
1295
|
`at src/Cart.tsx:12:5` line naming where the scope was created (the compiler's
|
|
@@ -1325,7 +1381,8 @@ first), `[ABANDONED_FLIGHTS]` (a source re-asked on every keystroke, answers thr
|
|
|
1325
1381
|
— debounce at the handler), `[STACKED_HOLDS]` (repeated clicks piling into one hold),
|
|
1326
1382
|
`[FALLBACK_FLASH]` (a fallback shown under 150ms), `[WASTED_RECOMPUTE]` (a scope whose
|
|
1327
1383
|
runs mostly compared equal — narrow its reads upstream), `[GRAPH_GROWTH]` (the graph grows
|
|
1328
|
-
on every visit to a route — an undisposed root or subscription),
|
|
1384
|
+
on every visit to a route — an undisposed root or subscription), `[HOT_SCOPE_FANOUT]`
|
|
1385
|
+
(many scopes hot from one root cause), and `[OPTIMISTIC_REVERTED]`
|
|
1329
1386
|
(info: a guess the screen showed was replaced). `costs()` and `feedback()` (named imports)
|
|
1330
1387
|
rank the session worst-first; `attribution.history("hold")` / `("waterfall")` /
|
|
1331
1388
|
`("navigation")` / `("interaction")` hold the fact tables behind the verdicts (each hold
|
|
@@ -1546,10 +1603,15 @@ production too (observe build; a no-op on the prod build).
|
|
|
1546
1603
|
- **Instrumentation order.** A monitoring SDK's `init()` (and `configureServerErrors`)
|
|
1547
1604
|
must run before the modules it patches load. `import "./instrument"` at the top of an
|
|
1548
1605
|
entry does not achieve that in ESM (static imports are hoisted in dependency order);
|
|
1549
|
-
the plugin's `start: {
|
|
1550
|
-
module before anything else in the server graph —
|
|
1551
|
-
`
|
|
1552
|
-
|
|
1606
|
+
the plugin's `start: { serverSetup: "./src/instrument.ts" }` awaits that server-only
|
|
1607
|
+
module before anything else in the server graph — the `node` entry too:
|
|
1608
|
+
`dist/server/node.js` awaits `./server.js` (which awaits the
|
|
1609
|
+
setup module) and then loads `node-runtime.js`, so deploy both files. The option was
|
|
1610
|
+
renamed from `start.instrument` between vite-plugin next.52 and next.57 — the plugin
|
|
1611
|
+
throws on the old key <!-- upstream:start-server-setup -->. The sibling seam
|
|
1612
|
+
`start: { serverRenderSetup: "./src/render-setup.ts" }` runs per request immediately
|
|
1613
|
+
before SSR — `(event, App) => Component | void` — for integrations that must prepare
|
|
1614
|
+
a per-request app instance before the render. Failures the
|
|
1553
1615
|
handler contains itself reach the hook as `{ kind: "request", handling: "failed" }`.
|
|
1554
1616
|
The client-side counterpart is `start: { clientSetup: "./src/client-setup.ts" }`: a
|
|
1555
1617
|
browser module run (and awaited, top-level `await` allowed) before `hydrate` / `render`,
|
|
@@ -1681,7 +1743,10 @@ plain `<a href={Router.paths.about}>` (or `useNavigate`); there is no `<A>` /
|
|
|
1681
1743
|
`<Navigate>` / `<HashRouter>` / `<FileRoutes />`. Do not assign
|
|
1682
1744
|
`window.location.href` or call `history.pushState` for in-app navigation.
|
|
1683
1745
|
Session location is `useLocation` / `useParams`,
|
|
1684
|
-
not `Router.paths`.
|
|
1746
|
+
not `Router.paths`. `useMatch(() => "/docs/:page")` tests a pattern against the
|
|
1747
|
+
current pathname without a route existing for it — the memo answers the match (params
|
|
1748
|
+
typed from the pattern) or nothing; that is the breadcrumb / section-highlight read
|
|
1749
|
+
<!-- upstream:router-use-match -->.
|
|
1685
1750
|
|
|
1686
1751
|
**Route `preload` starts reads; the page reads them in a memo.** `preload` runs when
|
|
1687
1752
|
the route matches (and ahead of a click under a `preloadLinks` strategy <!-- upstream:preload-links-opt-in -->), so it should kick off the query and return
|
|
@@ -2033,9 +2098,13 @@ every POST call as one). `live(GET(fn))` is the same declaration spelled out —
|
|
|
2033
2098
|
component rendered into a document that reads an *undeclared* async iterable still
|
|
2034
2099
|
producing after 5s is `[SSR_UNDECLARED_LIVE_SOURCE]` in dev — declare it `live()` (the
|
|
2035
2100
|
document takes the first value and closes it; the client connects after hydration) or
|
|
2036
|
-
bound the source. `broadcast(fn)`
|
|
2101
|
+
bound the source. `broadcast(fn)` is `live()` for one answer shared across
|
|
2037
2102
|
every viewer — the server runs the generator once per argument set instead of once per
|
|
2038
|
-
connection.
|
|
2103
|
+
connection. In a router app, `liveQuery(fn, "key")` from `@solidjs/router` declares
|
|
2104
|
+
the `live()` server function itself and keeps one connection per key, shared by every
|
|
2105
|
+
consumer — when several components need the same stream, declare it once with
|
|
2106
|
+
`liveQuery` instead of hoisting a hand-rolled `live()` declaration and sharing code
|
|
2107
|
+
<!-- upstream:router-live-query -->.
|
|
2039
2108
|
|
|
2040
2109
|
```ts
|
|
2041
2110
|
// src/data/auction.ts
|
|
@@ -2171,7 +2240,7 @@ how to reuse. Prefer the form on the right.
|
|
|
2171
2240
|
| `createEffect(() => user, (u) => { log(u.name); })` | `createEffect(() => user.name, (name) => { log(name); })` |
|
|
2172
2241
|
| `createEffect(() => title(), (t) => (document.title = t))` / `(v) => setX(v)` | `(t) => { document.title = t; }` — apply returns a cleanup or nothing; any other return value halts reactivity. An apply that only calls a local setter is rule 4 even with braces (`solid2-kit check` flags it): use a (writable) derivation |
|
|
2173
2242
|
| `<Loading fallback={<PageSkeleton />}>{/* header + data */}</Loading>` | wrap only the data slot; chrome stays outside |
|
|
2174
|
-
| `<Loading on={id} fallback={...}>` (the accessor), or `` on={`${a()}/${b()}`} `` to key several inputs | `on={id()}` / `on={[a(), b()]}` — `on` is a tracked dependency list, not a compared key: a bare accessor is never read (`solid2-kit check` flags it), and an array is the several-inputs form <!-- upstream:loading-on-dependency-list --> |
|
|
2243
|
+
| `<Loading on={id} fallback={...}>` (the accessor), or `` on={`${a()}/${b()}`} `` to key several inputs | `on={id()}` / `on={[a(), b()]}` — `on` is a tracked dependency list, not a compared key: a bare accessor is never read (`solid2-kit check` flags it; dev warns `[LOADING_ON_ACCESSOR]` since rc.20 <!-- upstream:loading-on-accessor -->), and an array is the several-inputs form <!-- upstream:loading-on-dependency-list --> |
|
|
2175
2244
|
| `setCount(count() + 1)` when writes can batch | `setCount((c) => c + 1)` |
|
|
2176
2245
|
| `setHandler(fn)` to store a function | `setHandler(() => fn)` (otherwise `fn` is an updater) |
|
|
2177
2246
|
| `{user() ? <P user={user()!} /> : <SignIn />}` | `<Show when={user()}>{(u) => <P user={u()} />}</Show>` |
|
|
@@ -2279,7 +2348,7 @@ how to reuse. Prefer the form on the right.
|
|
|
2279
2348
|
| `attribution.enable()` unguarded in app code, or `enable()` with the default `log: true` left on | `if (isDev) attribution.enable({ log: false })` — on the observe build an unguarded `enable()` records in production; prefer the `/__solid/diagnostics` session (`begin` enables, `end` disables) so app code carries nothing |
|
|
2280
2349
|
| `solid({ observe: true })` added "to see diagnostics in dev" | `vite dev` is always the dev build; `observe: true` is the production-observability opt-in (observe condition + `sourceNames` for `vite build`/preview <!-- upstream:source-names-option -->) |
|
|
2281
2350
|
| `configureServerErrors({ onError: (e) => { report(e); return e; } })` | return nothing or a reference (`new Error(\`ref ${id}\`)`) — the return value replaces the sanitized wire value; the error itself leaks message/stack/secrets |
|
|
2282
|
-
| `import "./instrument"` at the top of the server entry for a monitoring SDK | `start: {
|
|
2351
|
+
| `import "./instrument"` at the top of the server entry for a monitoring SDK | `start: { serverSetup: "./src/instrument.ts" }` — ESM hoists static imports in dependency order, so the entry's own deps load first (the `node` entry wraps it too: deploy `node.js` with `server.js`; the pre-rename spelling `start.instrument` throws in the plugin). Browser-side init before `hydrate` / `render` is `start: { clientSetup: "./src/client-setup.ts" }` |
|
|
2283
2352
|
| Express/middleware injecting `traceparent` / trace headers into responses | the runtime carries a sampled or provided trace on `Server-Timing` + `<meta>`; forward downstream with `getTraceContext()?.entries`; SDKs use `OBSERVE?.server.trace.provide` |
|
|
2284
2353
|
| `OBSERVE.records.subscribe("call", (e) => setStats(...))` | listeners run synchronously inside the runtime and must not write signals; collect into a plain buffer and drain from outside |
|
|
2285
2354
|
| `import { storePath } from "solid-js"` | gone from `solid-js` (rc.9); write draft setters `setState((d) => { d.user.name = "Grace"; })` <!-- upstream:store-path-removed --> |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid2-agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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",
|