solid2-agent-kit 0.18.0 → 0.19.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
@@ -11,12 +11,12 @@
11
11
  // @solidjs/vite-plugin's `next` is behind `latest`), so a bare install can
12
12
  // resolve a release older than the baseline.
13
13
  export const BASELINE = {
14
- 'solid-js': { line: 2, baseline: '2.0.0-rc.9' },
15
- '@solidjs/web': { line: 2, baseline: '2.0.0-rc.9' },
16
- '@solidjs/signals': { line: 2, baseline: '2.0.0-rc.9' },
17
- '@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.9' },
18
- '@solidjs/router': { line: 2, baseline: '2.0.0-next.27' },
19
- '@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.44' },
14
+ 'solid-js': { line: 2, baseline: '2.0.0-rc.13' },
15
+ '@solidjs/web': { line: 2, baseline: '2.0.0-rc.13' },
16
+ '@solidjs/signals': { line: 2, baseline: '2.0.0-rc.13' },
17
+ '@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.13' },
18
+ '@solidjs/router': { line: 2, baseline: '2.0.0-next.34' },
19
+ '@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.47' },
20
20
  '@solidjs/testing-library': { line: 1, baseline: '1.0.0-beta.3' },
21
21
  };
22
22
 
@@ -480,6 +480,29 @@ function reactKeyPropFindings(content) {
480
480
  // reads it from package.json / node_modules.
481
481
  const TANSTACK_ROUTER_MODULE = /^@tanstack\/(?:solid-router|router-core)$/;
482
482
 
483
+ // `<Loading on={id}>` hands the boundary an accessor: `on` is a tracked
484
+ // expression whose value is irrelevant, so a bare accessor is never read and
485
+ // the boundary never re-arms. `on={id()}`, `on={props.id}` and
486
+ // `on={[a(), b()]}` do not match. The opening tag is scanned to its own `>`
487
+ // (braces balanced) so a `fallback={<p>…</p>}` before `on` does not end it.
488
+ function loadingOnAccessorFindings(content) {
489
+ const matches = [];
490
+ for (const tag of content.matchAll(/<Loading\b/g)) {
491
+ let index = tag.index + tag[0].length;
492
+ let depth = 0;
493
+ while (index < content.length) {
494
+ const ch = content[index];
495
+ if (ch === '{') depth += 1;
496
+ else if (ch === '}') depth -= 1;
497
+ else if (ch === '>' && depth === 0) break;
498
+ index += 1;
499
+ }
500
+ const bare = content.slice(tag.index, index).match(/\son=\{\s*[A-Za-z_$][\w$]*\s*\}/);
501
+ if (bare) matches.push({ index: tag.index + bare.index });
502
+ }
503
+ return matches;
504
+ }
505
+
483
506
  // Local names bound by `import [Default,] { a, b as c } from "<module>"`.
484
507
  function importedNames(content, moduleName) {
485
508
  const names = new Set();
@@ -749,6 +772,13 @@ const CHECKS = [
749
772
  message:
750
773
  'Pass the accessor to isPending/latest, not the call: isPending(user), not isPending(user()). Calling it first evaluates the read before the helper runs.',
751
774
  },
775
+ {
776
+ // `<Loading on={id}>` — the accessor is never read, so `on` never re-arms.
777
+ id: 'loading-on-accessor',
778
+ find: loadingOnAccessorFindings,
779
+ message:
780
+ '`on` is a tracked dependency list, not a key: a bare accessor is never read, so the boundary never re-arms. Read it — on={id()} — or list several inputs: on={[a(), b()]}.',
781
+ },
752
782
  {
753
783
  id: 'dynamic-jsx',
754
784
  pattern: /<Dynamic[\s/>]/g,
@@ -1263,7 +1293,7 @@ const SOLID2_LINE_PACKAGES = [
1263
1293
  // Packages doctor checks the version of: the Solid 1.x lines above, then any
1264
1294
  // release older than the kit's baseline (bin/baseline.mjs) — a bare
1265
1295
  // `npm i @solidjs/web` installs `latest`, 2.0.0-rc.0, which the current
1266
- // @solidjs/vite-plugin's `@solidjs/web ^2.0.0-rc.9` peer rejects.
1296
+ // @solidjs/vite-plugin's `@solidjs/web ^2.0.0-rc.13` peer rejects.
1267
1297
  const VERSION_CHECKED = [...new Set([...SOLID2_LINE_PACKAGES.map((p) => p.name), ...Object.keys(BASELINE)])];
1268
1298
 
1269
1299
  // The lower bound of a single-comparator range ("^2.0.0-rc.0", "~2.0.0-rc.8",
@@ -14,7 +14,8 @@ Steps:
14
14
  4. Review every changed `.tsx`/`.jsx`/`.ts` file against the checklist, prioritizing what
15
15
  regexes cannot see:
16
16
  - reactive reads at component-body top level, or extracted into plain `const`s;
17
- - async computations reading reactive inputs **after** the first `await`;
17
+ - async computations reading reactive inputs **after** the first `await` (dev reports
18
+ `[UNTRACKED_READ_AFTER_AWAIT]` on V8);
18
19
  - effects that copy state instead of deriving it, or reading stores in the apply phase;
19
20
  - `<For>` over server/refetched rows without a stable-id key function;
20
21
  - core `action` bodies that are not generators (plain `async` functions) or that write
@@ -23,7 +24,8 @@ Steps:
23
24
  - `<Loading>`/`<Errored>` wrapping page chrome instead of the data slot, or
24
25
  `const u = user()` extracted then passed (a real parent-side read). Passing
25
26
  `user={user()}` is the colorless form — do not "fix" it into accessors or
26
- `Promise<User>` props;
27
+ `Promise<User>` props; `<Loading on={id}>` handed the accessor instead of a read
28
+ (`on={id()}`), or the boundary's data also read outside it (`[LOADING_ON_OUTSIDE_HOLD]`);
27
29
  - `latest(selectedId)` as the default highlight (hold is the default);
28
30
  `isPending` treated as a global spinner instead of a per-expression question;
29
31
  - treating `<Errored>` as a terminal ErrorBoundary, or expecting it to catch
@@ -74,7 +76,8 @@ Steps:
74
76
  thrown failures, or router forms without server-side validation (`throw respond(...)`)
75
77
  and `useSubmissions` inline errors;
76
78
  - browser-only *values* handled with a `clientOnly` component split or an `isServer`
77
- branch instead of `ssrSource: "client"` / `"hybrid"` on the memo/signal/derived store;
79
+ branch instead of `ssrSource: "client"` on the memo/signal/derived store (`"hybrid"`
80
+ only continues an async-iterable source after hydration <!-- upstream:ssr-source-hybrid-stream -->);
78
81
  - held writes with no feedback (`isPending` / `latest` / optimistic value / `affects()`)
79
82
  — the `[SILENT_HOLD]` shape — and dev-console diagnostics (`[STRICT_READ_UNTRACKED]`,
80
83
  attribution-only store/list/effect costs) left unaddressed;
@@ -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 `action`/`query` from `@solidjs/router` are POST forms and a read cache. 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. 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 silently ignored, 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` on the memo/signal/derived store (`client`/`hybrid`), not a `clientOnly` component split. Router link state is automatic (`aria-current`/`data-active`/`data-pending` + CSS; `useLinkState`/`useIsRouting` in JSX); 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(GET(fn))` (`live()` outermost). 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 -->, and a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]`. 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.
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 `action`/`query` from `@solidjs/router` are POST forms and a read cache. 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. 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 silently ignored, 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 automatic (`aria-current`/`data-active`/`data-pending` + CSS; `useLinkState`/`useIsRouting` in JSX); 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(GET(fn))` (`live()` outermost). 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 -->, and a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]`. 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.
@@ -125,14 +125,18 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
125
125
  must `refresh()` that source — type that prop `SourceAccessor<User>` (`Accessor<User>`
126
126
  is TS2345 at `refresh`) — `isPending(() => props.user)` works on the
127
127
  value. Read every reactive input **before the first
128
- `await`** — post-`await` reads do not subscribe, and in production the computation can
128
+ `await`** — post-`await` reads do not subscribe (dev reports `[UNTRACKED_READ_AFTER_AWAIT]`
129
+ on V8 engines), and in production the computation can
129
130
  sit pending with no retry. No `useEffect` + `setState` fetching, no `createResource`.
130
131
  `<Loading>` wraps the data slot, not page chrome. After first paint it keeps content
131
- during refetch (`isPending` for the indicator). Use `on={id()}` (the *value*, not the
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
132
+ during refetch (`isPending` for the indicator). Add `on={id()}` (a tracked *read*, not
133
+ the accessor `on={id}`) only when that change of subject should show the fallback again.
134
+ `on` is a dependency list, not a compared key: its value is irrelevant, a change to
135
+ anything it reads re-arms the boundary (several inputs: `on={[a(), b()]}`), and the
136
+ fallback lands with the frame of the change that caused it. <!-- upstream:loading-on-dependency-list -->
137
+ It can never show while the same data is also read outside the boundary (dev reports
138
+ `[LOADING_ON_OUTSIDE_HOLD]`) — move that read under the boundary so one hold owns the
139
+ data; that is how one slow panel stops holding the page. Do not start `fetch` (or any request) at component-body top
136
140
  level — that runs once at mount and is not a reactive source. Do not `try/catch` `NotReadyError` around a read, and do not
137
141
  use `loadingValue` / `seedLoadingValue` as the default first-flight UI — those skip
138
142
  `<Loading>`. `{latest(() => x())}` is a preview, not the visible answer, and not a
@@ -323,9 +327,10 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
323
327
  render. `NoHydration` / `Hydration` split hydration *ownership*; they do not choose
324
328
  visible content — `clientOnly` is for components that must never run on the server.
325
329
  A single browser-only *value* takes `ssrSource` on the memo/signal/derived store
326
- (`"client"` skips the server value and computes after hydration, e.g. `localStorage`;
327
- `"hybrid"` re-runs after adopting the serialized value, e.g. window size mixed with
328
- server data) — do not split a component via `clientOnly` for one value. An
330
+ (`"client"` skips the server value and computes after hydration, e.g. `localStorage`,
331
+ or server data mixed with the window size; `"hybrid"` adopts the serialized value and
332
+ then continues an async-iterable source on the client — for a sync or promise compute
333
+ it is identical to `"server"`, no re-run <!-- upstream:ssr-source-hybrid-stream -->) — do not split a component via `clientOnly` for one value. An
329
334
  `onSettled` fill-in remains a valid alternative.
330
335
  Pass a **function** to `render` / `hydrate` / `renderToString` / `renderToStream`
331
336
  (`render(() => <App />, root)`), never `render(<App />, root)`. `hydrate` when the
@@ -348,7 +353,8 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
348
353
  link previews) takes `{ deferStream: true }` on its memo; keep the boundary. `pipe` / `pipeTo` / `readable` each consume
349
354
  a stream render — use exactly one. `createRoot` is for tests, libraries, and
350
355
  non-render entry points; inside a component let `render` / the component owner
351
- own the scope. Async reads inside `<Portal>` start on the client — hoist the
356
+ own the scope (a root created under an owner is disposed with it — detach on purpose
357
+ with `runWithOwner(null, () => createRoot(...))`). Async reads inside `<Portal>` start on the client — hoist the
352
358
  read above the portal. Navigation-shaped updates (a setter, then async computeds holding previous values)
353
359
  do not need core `action` — reach for it only when writes happen *after* async work.
354
360
  Invoke actions from handlers, not from memos or effects.
@@ -437,9 +443,11 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
437
443
  experimental `serverFunctions.components` flag. Server-function arguments are JSON:
438
444
  send `Date` / `Map` / `Set` as ISO strings / arrays (they throw otherwise; one `File` /
439
445
  `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`) <!-- upstream:rich-args-vite-build https://github.com/solidjs/solid/issues/3627 -->. Declare live reads as `live(GET(fn))` —
442
- `live()` outermost. A plain `async function*` server function is an event stream on
446
+ `@solidjs/web/server-functions/rich-args` (at `src/App.tsx` module scope) lifts that
447
+ (its import builds cleanly since 2.0.0-rc.10 — a `resolve.dedupe: ["@solidjs/web"]`
448
+ workaround left from rc.9 is no longer needed) <!-- upstream:rich-args-vite-build https://github.com/solidjs/solid/issues/3627 -->. Declare live reads as `live(GET(fn))` —
449
+ `live()` outermost; an undeclared async iterable still producing while a document
450
+ renders is `[SSR_UNDECLARED_LIVE_SOURCE]` in dev. A plain `async function*` server function is an event stream on
443
451
  one connection (no reconnect when it drops); `live()` is one value that changes over
444
452
  time and reconnects. `live()` connection state is
445
453
  `source.onstatus` (`"connected"` / `"reconnecting"` / `"closed"`), never a
@@ -74,7 +74,7 @@ Most React `useEffect` code should NOT become `createEffect`:
74
74
  | Async value used several layers down | Create the memo high; pass `value={memo()}` through intermediates (they do not wait); put `<Loading>` around the leaf read |
75
75
  | Nested child with its own fetch | Leave it nested — it runs in parallel. Sequential only when the second call needs the first response (`fetchAuthor(story().authorId)`) |
76
76
  | Browser-only widget (charts, maps, `window`) | `clientOnly(() => import("./Chart"))` from `@solidjs/web` (`{ lazy: true }` defers the import until first render; `{ export: "Chart" }` for a named export). Placeholder: `<Chart fallback={<Skeleton />} />` — SSR renders the fallback; a `<Loading>` around it leaves the server HTML empty |
77
- | Browser-only *value* (`localStorage`, viewport size) | `createMemo(..., { ssrSource: "client" })` — per-value policy at the data source, no component split (`"hybrid"` when server data mixes with client signals) |
77
+ | Browser-only *value* (`localStorage`, viewport size) | `createMemo(..., { ssrSource: "client" })` — per-value policy at the data source, no component split (`"hybrid"` only to keep consuming an async-iterable source after hydration <!-- upstream:ssr-source-hybrid-stream -->) |
78
78
  | Server vs browser code path | `isServer` / `isDev` from `@solidjs/web` (build-time constants), not `typeof window` — never to choose markup (`{isServer ? <A /> : <B />}` is a hydration mismatch; use `clientOnly` / `ssrSource`) |
79
79
  | SSR-stable `id` / `for` / `aria-*` pairing | `createUniqueId()` |
80
80
 
@@ -256,7 +256,11 @@ const Result = dynamic(() => (detailed() ? Detailed : Compact));
256
256
  ```
257
257
 
258
258
  `dynamic()` returns a **stable** component whose source can be a component, an
259
- intrinsic tag name, a promise, or empty. Prefer it over swapping a component
259
+ intrinsic tag name, a promise, or empty. An async source (a promise, or a `live()`
260
+ server-component iterable) may resolve to a server component or a tag name; a promise
261
+ of a *client* component function is a dev error on the server
262
+ (`[DYNAMIC_ASYNC_COMPONENT]`) — resolve the async upstream, or `lazy()` for code.
263
+ Prefer it over swapping a component
260
264
  variable in JSX (`const View = tab() ? A : B` freezes the choice at setup).
261
265
  `<Dynamic component={...}>` is the same primitive with a worse shape (the tag travels
262
266
  in the props bag, so every instance merges and omits it and rebuilds a factory) and
@@ -399,8 +403,12 @@ return <StoryDetail story={story} />;
399
403
  Lifting the fetch does **not** mean lifting the boundary — that is consume glued
400
404
  back onto block. After first paint the
401
405
  boundary keeps settled content during refetch; `isPending(results)` (pass the accessor,
402
- not `isPending(results())`) is the indicator. Use `on={query()}` (the **value**, not the
403
- accessor `on={query}`) only when that identity should put the fallback back on screen.
406
+ not `isPending(results())`) is the indicator. Add `on={query()}` (a tracked **read** —
407
+ the accessor `on={query}` reads nothing) only when that change of subject should put the
408
+ fallback back on screen. `on` is a dependency list, not a compared key: its value is
409
+ irrelevant, several inputs go in as `on={[query(), page()]}`, and a change to anything it
410
+ reads re-arms the boundary. <!-- upstream:loading-on-dependency-list --> `[LOADING_ON_OUTSIDE_HOLD]` in dev means the same data is
411
+ also read outside the boundary, so the fallback can never show — move that read under it.
404
412
  Do not use `isPending` as the first-load spinner.
405
413
  - **Nesting is not a waterfall.** `Comments` above fetches in parallel with `story`
406
414
  because it reads `props.storyId` (already known), not `props.story`. Passing
@@ -426,7 +434,8 @@ return <StoryDetail story={story} />;
426
434
  the same rule: bind `value={latest(query)}` so a controlled search box reflects
427
435
  keystrokes while its write is held (an uncontrolled input needs nothing).
428
436
  - Read every reactive input **before the first `await`** in an async computation. A read
429
- after `await` does not subscribe; production can sit pending with no retry.
437
+ after `await` does not subscribe (dev reports `[UNTRACKED_READ_AFTER_AWAIT]` on V8 —
438
+ Chromium, Node, Deno, Bun); production can sit pending with no retry.
430
439
  - `<Errored>` function fallbacks receive an error **accessor** and a `reset` callback:
431
440
  `fallback={(error, reset) => ...}`. The boundary **heals** when the source succeeds
432
441
  again (refresh, live reconnect, input change) — it is graph status, not a terminal
@@ -988,8 +997,8 @@ function DraftPreview(props: { documentId: string }) {
988
997
  | Policy | Meaning | Use when |
989
998
  |---|---|---|
990
999
  | `"server"` (default) | Client adopts the serialized server value; compute does not re-run | Compute is deterministic from server-available inputs |
991
- | `"hybrid"` | Client adopts the serialized value, then re-runs the compute to take over | Server data mixes with client signals (window size, user locale) |
992
- | `"client"` | Server value skipped; compute runs after hydration as if first-mounted | Serialization is meaningless (`localStorage`, viewport) |
1000
+ | `"hybrid"` | Client adopts the serialized value, then — for an async-iterable source — continues the stream on the client; for a sync or promise compute it is identical to `"server"` (no re-run) <!-- upstream:ssr-source-hybrid-stream --> | A stream (`live()`, a subscription) the client should keep consuming after hydration |
1001
+ | `"client"` | Server value skipped; compute runs after hydration as if first-mounted | Serialization is meaningless (`localStorage`, viewport), or server data mixes with a browser-only input |
993
1002
 
994
1003
  With `"client"`, the bare form suspends on the server and the nearest `<Loading>`
995
1004
  fallback renders into the HTML; a declared first value (`loadingValue` on
@@ -1068,19 +1077,24 @@ see [Production observability](#production-observability-the-observe-build) —
1068
1077
  dev setting. Diagnostics come in two layers: **findings** (facts about a render, present
1069
1078
  in observe and dev) and **checks** (dev-only guidance printed by the console reporter).
1070
1079
  Always-on dev checks include misplaced reads
1071
- (`[STRICT_READ_UNTRACKED]`, `[PENDING_ASYNC_UNTRACKED_READ]`), misplaced writes
1080
+ (`[STRICT_READ_UNTRACKED]`, `[PENDING_ASYNC_UNTRACKED_READ]`, `[UNTRACKED_READ_AFTER_AWAIT]`
1081
+ for a first read made after an `await` in an async compute — V8 engines only), misplaced writes
1072
1082
  (`[REACTIVE_WRITE_IN_OWNED_SCOPE]` — since rc.9 also for store setters called in a
1073
1083
  component or root body <!-- upstream:store-write-owned-scope -->, `[ASYNC_STORE_SETTER]` for a store setter callback that returns
1074
1084
  a promise <!-- upstream:async-store-setter-throws -->, `[FLUSH_IN_ACTION]`, `[SERVER_WRITE]`), and
1075
- `[ASYNC_OUTSIDE_LOADING_BOUNDARY]` when a tracked read has no boundary above it.
1085
+ `[ASYNC_OUTSIDE_LOADING_BOUNDARY]` when a tracked read has no boundary above it, and
1086
+ `[LOADING_ON_OUTSIDE_HOLD]` when a `<Loading on>` boundary's data is also read outside it.
1076
1087
  Each entry carries a stable `[CODE]`, what the runtime observed, and an owner chain
1077
1088
  (`in <App> › <Cart> › <LineItem>`); never silence a code before understanding it.
1078
1089
  Since rc.9 the server render reports on the same channel with the same owner chains:
1079
1090
  `[SSR_RENDER_ERROR_CONTAINED]` (a boundary routed a render error; `ownerPath` is where
1080
- it was thrown, `boundaryPath` where it was met) <!-- upstream:ssr-render-error-contained -->, `[SSR_ERROR_SANITIZED]` (the original
1081
- behind a production-sanitized error), `[SSR_CLIENT_CONTENT_MASKED]` (client-only content
1082
- that surfaced only after a real server wait — the wait was wasted), server-side
1083
- `[ASYNC_WATERFALL]`, and `[REVEAL_IN_RENDER_TO_STRING]`.
1091
+ it was thrown, `boundaryPath` where it was met) <!-- upstream:ssr-render-error-contained -->, `[SERVER_ERROR_SANITIZED]` (the original
1092
+ behind a production-sanitized error, from a server function or an SSR render),
1093
+ `[SSR_CLIENT_CONTENT_MASKED]` (client-only content that surfaced only after a real
1094
+ server wait — the wait was wasted), `[SSR_BOUNDARY_WATERFALL]` (sequential waits inside
1095
+ one server `<Loading>` boundary), `[SSR_UNDECLARED_LIVE_SOURCE]` (an async iterable
1096
+ still pumping into a document render — declare it `live()`), and
1097
+ `[REVEAL_IN_RENDER_TO_STRING]`.
1084
1098
  For the repair behind any code, read the `reactivity-diagnostics` skill Solid ships
1085
1099
  in the repo (`node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md`) — every
1086
1100
  code maps to what the runtime observed and what to change.
@@ -1091,13 +1105,18 @@ every scope that re-ran, what changed to cause it, and how long it took. Since
1091
1105
  no longer exists <!-- upstream:dev-attribution-removed --> (it is a type error; `DEV` holds only devtools hooks, graph
1092
1106
  traversal, and console reporting). `OBSERVE` is the public observability surface on
1093
1107
  the observe and dev builds — `OBSERVE.records`, `OBSERVE.diagnostics`,
1094
- `OBSERVE.exclude`, `OBSERVE.server.trace` are for app and tool code; only the
1095
- `OBSERVE.attribution` hook slot (`install` / `withInteraction` / `withOrigin`) belongs
1096
- to engines, routers, and devtools. Since 2.0.0-rc.9 the folds, queries, and
1097
- formatters are **named exports** of that subpath, not methods of `attribution`
1098
- (`attribution.why` / `.costs` / `.feedback` / `.subscriptions` / `.format` are type
1099
- errors) <!-- upstream:attribution-named-exports -->; `attribution` keeps `enable`, `disable`, `subscribe`, `markFlight`, and the
1100
- record ring buffers (`history`, `holds`, `waterfalls`, `navigations`, `interactions`):
1108
+ `OBSERVE.exclude` / `OBSERVE.include`, `OBSERVE.ownerPath`, `OBSERVE.server.trace` are
1109
+ for app and tool code; only the `OBSERVE.attribution` slot (`withInteraction` /
1110
+ `withOrigin`) belongs to engines, routers, and devtools. Since 2.0.0-rc.9 the folds,
1111
+ queries, and formatters are **named exports** of that subpath, not methods of
1112
+ `attribution` (`attribution.why` / `.costs` / `.feedback` / `.subscriptions` / `.format`
1113
+ are type errors) <!-- upstream:attribution-named-exports -->; `attribution` keeps `enable` (a hold on the one shared engine — it
1114
+ returns the release), `disable` (full teardown), `markFlight`, and one ring-buffer reader,
1115
+ `attribution.history(type)` (`"rerun"` / `"hold"` / `"waterfall"` / `"navigation"` /
1116
+ `"interaction"`). Live records — the engine's included — arrive on
1117
+ `OBSERVE.records.subscribe(type, (event, live) => …)`; `attribution.subscribe` and the
1118
+ `holds()` / `waterfalls()` / `navigations()` / `interactions()` methods are gone since
1119
+ 2.0.0-rc.10. <!-- upstream:attribution-history-records -->
1101
1120
 
1102
1121
  ```ts
1103
1122
  import { attribution, formatRerun, why } from 'solid-js/attribution';
@@ -1116,13 +1135,15 @@ to query the tables yourself. Name the scopes you intend to interrogate
1116
1135
  (`createMemo(..., { name: "total" })`) — chains refer to nodes by name, and
1117
1136
  anonymous nodes print as `computed` / `effect` / `signal`; component labels
1118
1137
  (`<Checkout>`) are always present under `vite dev` and survive minification only with
1119
- the compiler's `componentNames` option (`solid({ observe: true })` turns it on). On the
1138
+ the compiler's `sourceNames` option (components, binding effects, primitives — the Vite
1139
+ plugin turns it on under `vite dev` and `observe: true`; `componentNames` was its rc.9 name
1140
+ and is rejected now) <!-- upstream:source-names-option -->. On the
1120
1141
  **prod build** the import resolves to an inert same-surface twin whose `enable()` is a
1121
1142
  no-op, so the import can stay in the code. On the **observe build** it is the real
1122
1143
  engine and an unguarded `enable()` records in production — keep the `isDev` guard
1123
1144
  unless recording in production is the intent. The split is pay-for-use: importing
1124
1145
  `costs` or `feedback` is what turns their tables on, so a records-only consumer
1125
- (`attribution.subscribe(...)`) ships neither.
1146
+ (`OBSERVE.records.subscribe("rerun", …)`) ships neither.
1126
1147
 
1127
1148
  While attribution is enabled, Solid also warns on its own about over-subscription,
1128
1149
  chained async waterfalls (`[ASYNC_WATERFALL]`), memos whose fresh-but-equal output
@@ -1136,10 +1157,18 @@ ran while held; info from 100ms, warning from 200ms, tunable via
1136
1157
  `enable({ holds: { infoMs, warnMs } })`). A hold that *was* acknowledged but still ran
1137
1158
  long is `[LONG_HOLD]` (info from 500ms, warning from 1000ms;
1138
1159
  `enable({ longHolds: { infoMs, warnMs } })`) — the suggestion is a `<Loading on={...}>`
1139
- boundary, never removing the hold. `costs()`, `feedback()` (named imports), and
1140
- `attribution.holds()` rank the session worst-first; `attribution.waterfalls()`,
1141
- `attribution.navigations()`, and `attribution.interactions()` hold the fact tables
1142
- behind the verdicts.
1160
+ boundary, never removing the hold. Beside them: `[UNTRACKED_ASYNC_HANDLER]` (an `async`
1161
+ handler that awaits with no write before the `await` and no `action` — no hold exists, so
1162
+ the click looks dead; make it an `action`, or write a `createOptimistic(false)` flag
1163
+ first), `[ABANDONED_FLIGHTS]` (a source re-asked on every keystroke, answers thrown away
1164
+ — debounce at the handler), `[STACKED_HOLDS]` (repeated clicks piling into one hold),
1165
+ `[FALLBACK_FLASH]` (a fallback shown under 150ms), `[WASTED_RECOMPUTE]` (a scope whose
1166
+ runs mostly compared equal — narrow its reads upstream), `[GRAPH_GROWTH]` (the graph grows
1167
+ on every visit to a route — an undisposed root or subscription), and `[OPTIMISTIC_REVERTED]`
1168
+ (info: a guess the screen showed was replaced). `costs()` and `feedback()` (named imports)
1169
+ rank the session worst-first; `attribution.history("hold")` / `("waterfall")` /
1170
+ `("navigation")` / `("interaction")` hold the fact tables behind the verdicts (each hold
1171
+ carries its `silent` / `long` verdict). <!-- upstream:attribution-history-records -->
1143
1172
  `subscriptions(scope)` (named import) lists a scope's current dependencies to compare
1144
1173
  against what it uses.
1145
1174
 
@@ -1202,11 +1231,14 @@ effects** — a pure markup or styling change does not need it.
1202
1231
  `attribution.enable({ log: false })` in the client entry, then `why(scope)` /
1203
1232
  `costs()` / `feedback()` from the console or a scratch effect — never `log: true`
1204
1233
  left on (one collapsed `[why-run]` group per re-run) and never unguarded (on the
1205
- observe build it records in production).
1234
+ observe build it records in production). Under `vite dev` the plugin also paints the
1235
+ same records as tracks in the Chrome Performance panel (`performanceTracks`, on by
1236
+ default; `false` opts out) — record there when a timeline reads better than tables.
1206
1237
  4. **Read the verdicts, then the tables.** Fix every coded warning the session raised
1207
1238
  (`[SILENT_HOLD]`, `[IMMUTABLE_UPDATE_IN_STORE]`, `[UNSTABLE_LIST_IDENTITY]`,
1208
1239
  `[EFFECT_RELAY_TEAR]`, `[EFFECT_WRITES_OWN_SOURCE]`, `[ASYNC_WATERFALL]`,
1209
- `[HOT_SCOPE_RERUNS]`). Then `costs().scopes` (self time, `wastedMs` for runs whose
1240
+ `[HOT_SCOPE_RERUNS]`, `[WASTED_RECOMPUTE]`, `[ABANDONED_FLIGHTS]`,
1241
+ `[UNTRACKED_ASYNC_HANDLER]`). Then `costs().scopes` (self time, `wastedMs` for runs whose
1210
1242
  result did not change), `costs().writes` (root writes by downstream work),
1211
1243
  `feedback().sources` / `.interactions` / `.flights` / `.fallbacks` (silent holds,
1212
1244
  held time per interaction, abandoned requests, fallback flashes). A scope that
@@ -1233,14 +1265,16 @@ export default { plugins: [solid({ ssr: true, observe: true })] };
1233
1265
  ```
1234
1266
 
1235
1267
  `observe: true` adds the `observe` condition to every environment (client and server,
1236
- `vite build` and preview) and turns on the compiler's `componentNames` option so
1237
- `ownerPath` / `boundaryPath` name components instead of `computed`. Without the plugin,
1238
- set `resolve.conditions` (or `node --conditions=observe` for an unbundled server) and
1239
- `componentNames` yourself. Cost in Solid's size suite: ~16.8 KB against ~15.3 KB brotli
1268
+ `vite build` and preview) and turns on the compiler's `sourceNames` option (components,
1269
+ binding effects, and primitives) so `ownerPath` / `boundaryPath` name components instead
1270
+ of `computed` <!-- upstream:source-names-option -->. Without the plugin, set `resolve.conditions` (or `node --conditions=observe`
1271
+ for an unbundled server) and `sourceNames` yourself. Cost in Solid's size suite: ~16.8 KB against ~15.3 KB brotli
1240
1272
  for a small client app; importing the attribution engine on top raises the cap to
1241
1273
  ~27 KB (a separate entry — an observe build that never imports it never ships it). The
1242
1274
  two error hooks are **not** part of this: they fire on every build, prod included, only
1243
- the paths are `undefined` there.
1275
+ the paths are `undefined` there. `enablePerformanceTracks()` from
1276
+ `@solidjs/web/performance-tracks` paints the records on the Chrome Performance panel in
1277
+ production too (observe build; a no-op on the prod build).
1244
1278
 
1245
1279
  - **Instrumentation order.** A monitoring SDK's `init()` (and `configureServerErrors`)
1246
1280
  must run before the modules it patches load. `import "./instrument"` at the top of an
@@ -1257,22 +1291,31 @@ the paths are `undefined` there.
1257
1291
  `OBSERVE?.server.trace.provide((request) => ({ traceId, spanId, entries }))`, once per
1258
1292
  request. Do not write middleware to inject trace headers.
1259
1293
  - **Records.** `OBSERVE?.records.subscribe(type, (event, live) => …)` delivers one plain
1260
- record per `"boundary"` (a `<Loading>` that waited on the server), `"invocation"`
1261
- (server-function execution), `"call"` (server-function call from the page — shares
1262
- `id` with its invocation), and `"frame"` (produced / applied). Records are
1294
+ record per `"render"` (a server render: `mode`, `shellMs`, `durationMs`, `boundaries`,
1295
+ `outcome`, the matched `route`), `"boundary"` (a `<Loading>` that waited on the server),
1296
+ `"invocation"` (server-function execution), `"request"` / `"call"` (a server-function
1297
+ call from the page, at send and at settle — `call` shares `id` with its invocation),
1298
+ `"frame"` (produced / applied), and `"recovery"` (a boundary the client re-rendered
1299
+ after a server failure); the attribution engine's own records (`"rerun"`, `"hold"`,
1300
+ `"interaction"`, `"navigation"`, …) ride the same channel while it is enabled. Records are
1263
1301
  serializable data; live handles (request, args, the error as thrown) ride in the
1264
1302
  second argument. Listeners run synchronously inside the runtime, **must not write
1265
1303
  signals**, and should subscribe from a module that loads before the app.
1266
- - **Interactions.** `attribution.enable()` then
1267
- `attribution.subscribe("interaction", (event) => …)` says what each click waited on
1304
+ - **Interactions.** `attribution.enable()` (keep the release it returns) then
1305
+ `OBSERVE?.records.subscribe("interaction", (event) => …)` <!-- upstream:attribution-history-records --> says what each click waited on
1268
1306
  (`event.settledMs`, `event.holds[].blockers`, `event.holds[].holdMs`); a hold nothing acknowledged
1269
1307
  is the dead click. The fold tables stay separate imports.
1270
1308
  - **What leaves the process.** Beyond names, four record fields carry page data —
1271
1309
  `target` (element as `tag#id "text"`, 30 chars), `prev` / `value` previews (40 chars),
1272
- navigation `to` / `from` / `params`, and `data.error` on server render findings — an
1273
- exporter decides what to do with each. A tool that renders inside the app it watches
1310
+ navigation `to` / `from` / `params`, and `data.error` on server render findings. The
1311
+ engine's `values` option governs the first three at the source — `"full"` (the dev
1312
+ default), `"labels"` (no previews; element text only on buttons and links), `"none"`
1313
+ (the observe-build default) — so an exporter on the observe build passes its level
1314
+ explicitly (`attribution.enable({ values: "labels" })`) and decides what to do with the
1315
+ rest. A tool that renders inside the app it watches
1274
1316
  (devtools panel) marks its root with `OBSERVE?.exclude(getOwner()!)` inside its
1275
- `createRoot` so its own effects and stores never become findings; do not route its
1317
+ `createRoot` so its own effects and stores never become findings (`OBSERVE?.include(owner)`
1318
+ re-admits the app's root under an excluded shell); do not route its
1276
1319
  writes through `runWithOwner` (that is a write in an owned scope).
1277
1320
 
1278
1321
  ## Client mode: no server HTML, no hydration reflexes
@@ -1642,10 +1685,10 @@ status, so `return respond({ error }, { status: 400 })` reads as success to the
1642
1685
  is success metadata a scripted caller unwraps. Arguments are JSON: send `Date` / `Map` /
1643
1686
  `Set` as ISO strings / arrays (they throw otherwise; one `File` / `Blob` / `FormData`
1644
1687
  argument travels natively). `enableRichArguments()` from
1645
- `@solidjs/web/server-functions/rich-args` (at `src/App.tsx` module scope) lifts that, but
1646
- in rc.9 importing it fails `vite build` (`"./client" is not exported` — the dev server
1647
- works, so the break shows only at build); `resolve: { dedupe: ["@solidjs/web"] }` in the
1648
- Vite config works around it. <!-- upstream:rich-args-vite-build https://github.com/solidjs/solid/issues/3627 -->
1688
+ `@solidjs/web/server-functions/rich-args` (at `src/App.tsx` module scope) lifts that. Its
1689
+ import failed `vite build` in rc.9 (`"./client" is not exported`); that is fixed since
1690
+ 2.0.0-rc.10, so a `resolve: { dedupe: ["@solidjs/web"] }` workaround left in a Vite config
1691
+ is no longer needed (the plugin dedupes the runtime packages itself under `vite dev`). <!-- upstream:rich-args-vite-build https://github.com/solidjs/solid/issues/3627 -->
1649
1692
  `GET()` is only for idempotent reads (URLs leak into logs/history);
1650
1693
  import it from `@solidjs/web/server-functions`, never `@solidjs/start`.
1651
1694
  Reads are function calls too: `GET(async (id) => { "use server"; ... })` is invoked
@@ -1673,7 +1716,10 @@ export const fetchStory = GET(async (id: number) => {
1673
1716
  A value that changes over time is `live(GET(fn))` — **`live()` outermost**: `GET()`
1674
1717
  chooses the read transport and `live()` wraps the call behavior around it. `live()`
1675
1718
  does not imply `GET()`; without it the source streams over POST, which is fine when a
1676
- cacheable URL is of no use.
1719
+ cacheable URL is of no use. A server component rendered into a document that reads an
1720
+ *undeclared* async iterable still producing after 5s is `[SSR_UNDECLARED_LIVE_SOURCE]` in
1721
+ dev — declare it `live()` (the document takes the first value and closes it; the client
1722
+ connects after hydration) or bound the source.
1677
1723
 
1678
1724
  ```ts
1679
1725
  // src/data/auction.ts
@@ -1806,7 +1852,7 @@ how to reuse. Prefer the form on the right.
1806
1852
  | `createEffect(() => user, (u) => { log(u.name); })` | `createEffect(() => user.name, (name) => { log(name); })` |
1807
1853
  | `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 |
1808
1854
  | `<Loading fallback={<PageSkeleton />}>{/* header + data */}</Loading>` | wrap only the data slot; chrome stays outside |
1809
- | `<Loading on={id} fallback={...}>` (the accessor) | `on={id()}` — a value, so identity changes can show fallback |
1855
+ | `<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 --> |
1810
1856
  | `setCount(count() + 1)` when writes can batch | `setCount((c) => c + 1)` |
1811
1857
  | `setHandler(fn)` to store a function | `setHandler(() => fn)` (otherwise `fn` is an updater) |
1812
1858
  | `{user() ? <P user={user()!} /> : <SignIn />}` | `<Show when={user()}>{(u) => <P user={u()} />}</Show>` |
@@ -1838,7 +1884,7 @@ how to reuse. Prefer the form on the right.
1838
1884
  | `renderToString(() => <App />)` for an async or lazy-route tree | `await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`). String render emits `<Loading>` fallbacks |
1839
1885
  | `clientOnly(() => import("./Map"))` for a rarely shown widget | same + `{ lazy: true }` so the import waits until first render (default starts at declaration) |
1840
1886
  | `<NoHydration><Chart /></NoHydration>` to skip SSR of a widget | `clientOnly(() => import("./Chart"))` — `NoHydration`/`Hydration` split ownership, they do not choose visible content |
1841
- | `clientOnly` split (or `isServer` branch) for one browser-only *value* | `createMemo(() => localStorage.getItem(k), { ssrSource: "client" })` — `"hybrid"` when server data mixes with client signals |
1887
+ | `clientOnly` split (or `isServer` branch) for one browser-only *value* | `createMemo(() => localStorage.getItem(k), { ssrSource: "client" })` — `"hybrid"` only to keep consuming an async-iterable source after hydration <!-- upstream:ssr-source-hybrid-stream --> |
1842
1888
  | `await flush()` / `flush()` to wait on a pending memo | `await resolve(() => value())` (or Testing Library async queries); `flush()` only drains staged *sync* writes |
1843
1889
  | `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 |
1844
1890
  | `export const [cart, setCart] = createStore(...)` for app-wide state | `createCart()` called by a `CartProvider` at the root of `App`, read with `useCart()` over `createContext<T>()` — module state has no owner and, under SSR, one instance serves every request (`[SERVER_WRITE]`). Module scope is for constants and `createContext` |
@@ -1901,7 +1947,7 @@ how to reuse. Prefer the form on the right.
1901
1947
  | `latest(user) ?? placeholder` in a handler as a null-safe read | `latest()` throws `NotReadyError` before the first value in every scope; read settled values under `<Loading>`, or `await until(() => user())` <!-- upstream:latest-throws-unsettled --> |
1902
1948
  | `attribution.why(total)` / `attribution.format(e)` / `attribution.costs()` | named imports from `solid-js/attribution`: `why(total)`, `formatRerun(e)`, `costs()`, `feedback()`, `subscriptions(scope)` (rc.9) <!-- upstream:attribution-named-exports --> |
1903
1949
  | `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 |
1904
- | `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 + `componentNames` for `vite build`/preview) |
1950
+ | `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 -->) |
1905
1951
  | `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 |
1906
1952
  | `import "./instrument"` at the top of the server entry for a monitoring SDK | `start: { instrument: "./src/instrument.ts" }` — ESM hoists static imports in dependency order, so the entry's own deps load first |
1907
1953
  | 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` |
@@ -2112,7 +2158,7 @@ always-applied rules installed alongside this skill.
2112
2158
  memo/effect. Effect `apply` functions have block bodies and return only a
2113
2159
  cleanup or nothing.
2114
2160
  - [ ] Dev console shows no diagnostics: reads in tracking scopes (no
2115
- `[STRICT_READ_UNTRACKED]`), store setters synchronous and never called at
2161
+ `[STRICT_READ_UNTRACKED]`, no `[UNTRACKED_READ_AFTER_AWAIT]`), store setters synchronous and never called at
2116
2162
  body level (no `[ASYNC_STORE_SETTER]` <!-- upstream:async-store-setter-throws --> /
2117
2163
  `[REACTIVE_WRITE_IN_OWNED_SCOPE]` <!-- upstream:store-write-owned-scope -->), held
2118
2164
  writes paired with `isPending` / `latest` / optimistic values / `affects()`
@@ -26,6 +26,7 @@ The skill does not cover these; fetch the pages when the task needs them.
26
26
  - Row selection without `createSelector` (id-keyed projection): https://v2-rebuild--solid-docs-v2.netlify.app/guides/lists.md, https://v2-rebuild--solid-docs-v2.netlify.app/guides/performance.md
27
27
  - Infinite scroll, polling, sharing one request: https://v2-rebuild--solid-docs-v2.netlify.app/guides/data-fetching-patterns.md
28
28
  - Writing a custom primitive (called under an owner, `MaybeAccessor`, `getOwner` / `runWithOwner` / `isDisposed` after async): https://v2-rebuild--solid-docs-v2.netlify.app/guides/custom-primitives.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/owner-introspection/get-owner.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/owner-introspection/run-with-owner.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/owner-introspection/is-disposed.md
29
+ - Library code that must know whether it is hydrating, or write a keyed server-to-client value (`isHydrating`, `isHydratable`, `getHydrationWriter` / `takeHydrationValue`): https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/manual-hydration/is-hydrating.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/manual-hydration/is-hydratable.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/rendering-ssr/get-hydration-writer.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/rendering-ssr/take-hydration-value.md
29
30
  - Wrapping a non-Solid library, push feeds with a reactive argument, web components (`prop:`, event names): https://v2-rebuild--solid-docs-v2.netlify.app/guides/integrate-non-solid-code.md
30
31
  - Typed env (`env.ts` schema, `VITE_` client keys), `import "server-only"`, what compiles `"use server"`: https://v2-rebuild--solid-docs-v2.netlify.app/building-apps/environment.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/vite-plugin-solid/modules.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/vite-plugin-solid/server-functions.md
31
32
  - Sessions and cookies (`parseCookieHeader` / `serializeCookie`, `RequestEventLocals` typing, when a cookie write commits): https://v2-rebuild--solid-docs-v2.netlify.app/building-apps/sessions-and-auth.md, https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response/cookies.md
@@ -158,6 +159,7 @@ the error hooks fire on every build.
158
159
  - https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/diagnostics-dev-hooks/configure-client-errors.md
159
160
  - https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response/configure-server-errors.md
160
161
  - https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response/get-trace-context.md
162
+ - https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/performance-tracks/enable-performance-tracks.md
161
163
 
162
164
  ## Reference: components and JSX (solid-js)
163
165
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid2-agent-kit",
3
- "version": "0.18.0",
3
+ "version": "0.19.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",