solid2-agent-kit 0.11.1 → 0.13.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/README.md +1 -1
- package/bin/solid2-kit.mjs +48 -1
- package/files/commands/solid2-review.md +13 -1
- package/files/shared/agents-section.md +1 -1
- package/files/shared/rules-body.md +43 -11
- package/files/skills/solid-2/SKILL.md +280 -43
- package/files/skills/solid-2/references/official-docs.md +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Supported agents: **Cursor** and **Claude Code**.
|
|
|
16
16
|
| Always-applied hard rules | `.cursor/rules/solid-2.mdc` (glob-attached to `*.tsx`/`*.jsx`) | managed block in `CLAUDE.md` | 23 hard rules + banned Solid 1.x API table + eslint-plugin-solid lint-heritage table. Injected without the agent having to decide to read anything |
|
|
17
17
|
| Agent skill | `.cursor/skills/solid-2/` | `.claude/skills/solid-2/` | Execution-model primer, decision tables, canonical patterns, a “still runs / write the other form” table, review checklist, official-doc URL index |
|
|
18
18
|
| Shared agent context | managed block in `AGENTS.md` | same (Claude Code users can reference it from `CLAUDE.md`) | Core principles and pointers, always in context |
|
|
19
|
-
| Mechanical gate | `solid2-kit check` | same | Comment-stripped regex guard over `src/**/*.{ts,tsx,jsx}` (or explicit `[files...]`): fails on props destructuring (including multi-line signatures), `{ ...props }` rest copies, `{list().map(...)}` in JSX, React imports/hooks/JSX props/`React.lazy`/`useOptimistic`/`onDoubleClick`, Solid 1.x imports/APIs/components/JSX namespaces, `vite-plugin-solid`, old-router `<Route>`/`<A>`/`<HashRouter>`/`<FileRoutes>`, `MetaProvider`, `Context.Provider`, camelCase style keys, `key` props, `value()!` hand-narrowing, hand-rolled loading signals, `=== undefined` readiness branches, SolidStart leftovers (`@solidjs/start`, `h3`, `createAsync`, `useSubmission`, `"use client"`), Next.js imports/`useRouter`/`typeof window`, `history.pushState`, `render(<App />)` (pass a function), `isPending(user())` / `latest(user())` (pass the accessor), `<Dynamic>` (use `dynamic()`), `import type { JSX } from "solid-js"` (no `JSX` namespace there — `Element` from `solid-js`, DOM `JSX` types from `@solidjs/web`), two-phase effects whose apply only writes a local signal (use a writable derivation),
|
|
19
|
+
| Mechanical gate | `solid2-kit check` | same | Comment-stripped regex guard over `src/**/*.{ts,tsx,jsx}` (or explicit `[files...]`): fails on props destructuring (including multi-line signatures), `{ ...props }` rest copies, `{list().map(...)}` in JSX, React imports/hooks/JSX props/`React.lazy`/`useOptimistic`/`onDoubleClick`, Solid 1.x imports/APIs/components/JSX namespaces, `vite-plugin-solid`, old-router `<Route>`/`<A>`/`<HashRouter>`/`<FileRoutes>`, `MetaProvider`, `Context.Provider`, camelCase style keys, `key` props, `value()!` hand-narrowing, hand-rolled loading signals, `=== undefined` readiness branches, SolidStart leftovers (`@solidjs/start`, `h3`, `createAsync`, `useSubmission`, `"use client"`), Next.js imports/`useRouter`/`typeof window`, `history.pushState`, `render(<App />)` (pass a function), `isPending(user())` / `latest(user())` (pass the accessor), `<Dynamic>` (use `dynamic()`), `import type { JSX } from "solid-js"` (no `JSX` namespace there — `Element` from `solid-js`, DOM `JSX` types from `@solidjs/web`), two-phase effects whose apply only writes a local signal (use a writable derivation), core `action(async ...)` with a plain async function instead of a generator (only `yield` re-enters the transaction), and `setStore(async ...)` on a `createStore` / `createOptimisticStore` setter (the draft is a synchronous transaction; dev throws `[ASYNC_STORE_SETTER]`) |
|
|
20
20
|
| Edit-time hook | `postToolUse` entry in `.cursor/hooks.json` | `PostToolUse` entry in `.claude/settings.json` (matcher includes `Bash`) | Runs the mechanical gate automatically on **every agent file edit — including edits made through shell commands** (sed, heredocs, codemods; source paths mentioned in the command are checked after it ran) — and feeds findings straight back into the conversation (Cursor: `additional_context`; Claude Code: stderr + exit 2), so enforcement does not depend on the agent remembering to run `check`. Wired only when the kit is a local devDependency; opt out with `--no-hooks` |
|
|
21
21
|
| Turn-end gate | — | `Stop` entry in `.claude/settings.json` | Whole-project `check` + `doctor` when the agent tries to end its turn: with findings in place the stop is blocked (exit 2) and the findings are fed back, so a turn cannot finish with React/Solid 1.x patterns left behind. Loop-safe via `stop_hook_active` (one forced continuation, never an infinite loop) |
|
|
22
22
|
| Pre-execution guard | `preToolUse` + `beforeShellExecution` entries in `.cursor/hooks.json` | `PreToolUse` entry in `.claude/settings.json` | Denies actions **before they run**: edits/deletes of kit-owned guardrail files (an agent blocked by a gate will try to remove the gate), edits that would strip the kit's hook entries or managed blocks, uninspectable shell rewrites of those files, and `npm/pnpm/yarn/bun install` of banned dependencies (`react`, `vite-plugin-solid`, `eslint-plugin-solid`, SolidStart, ...) — caught proactively instead of post-hoc by `doctor` |
|
package/bin/solid2-kit.mjs
CHANGED
|
@@ -270,6 +270,26 @@ function init() {
|
|
|
270
270
|
console.log(`solid2-agent-kit v${VERSION} — installed for ${[wantCursor && 'Cursor', wantClaude && 'Claude Code'].filter(Boolean).join(' + ')}:`);
|
|
271
271
|
for (const path of written.filter(Boolean)) console.log(` ${relative(target, path) || '.'}`);
|
|
272
272
|
if (hooksNote) console.log(` note: ${hooksNote}`);
|
|
273
|
+
if (!hasDiagnosticsDependency(target)) {
|
|
274
|
+
console.log(
|
|
275
|
+
' note: the solid-2 skill\'s development loop drives the dev server\'s /__solid/diagnostics endpoint; add "@solidjs/diagnostics" to devDependencies so @solidjs/vite-plugin serves it (auto-on when declared). Without it the fallback is an isDev-guarded attribution.enable({ log: false }).',
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Whether the consuming project declares @solidjs/diagnostics — the signal
|
|
281
|
+
// @solidjs/vite-plugin uses to auto-enable the diagnostics endpoint. An
|
|
282
|
+
// informational note only: `doctor` does not fail without it.
|
|
283
|
+
function hasDiagnosticsDependency(target) {
|
|
284
|
+
const filePath = join(target, 'package.json');
|
|
285
|
+
if (!existsSync(filePath)) return true;
|
|
286
|
+
let pkg;
|
|
287
|
+
try {
|
|
288
|
+
pkg = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
289
|
+
} catch {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return '@solidjs/diagnostics' in { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.optionalDependencies };
|
|
273
293
|
}
|
|
274
294
|
|
|
275
295
|
// --- check ------------------------------------------------------------------
|
|
@@ -338,6 +358,24 @@ function soleLocalSetterApply(apply, localSetters) {
|
|
|
338
358
|
|
|
339
359
|
const LOCAL_SETTER_DECL = /\[\s*[\w$]+\s*,\s*(set[A-Z][\w$]*)\s*\]\s*=\s*createSignal\b/g;
|
|
340
360
|
|
|
361
|
+
// A store setter callback is a synchronous transaction — the draft closes when
|
|
362
|
+
// the callback returns, so `setStore(async (draft) => …)` loses every write
|
|
363
|
+
// after its first await (Solid 2.0.0-rc.9 throws ASYNC_STORE_SETTER in dev).
|
|
364
|
+
// Signals may hold promises, so only setters declared via createStore /
|
|
365
|
+
// createOptimisticStore destructuring in the same file are checked.
|
|
366
|
+
const LOCAL_STORE_SETTER_DECL =
|
|
367
|
+
/\[\s*[\w$]+\s*,\s*(set[A-Z][\w$]*)\s*\]\s*=\s*create(?:Optimistic)?Store\b/g;
|
|
368
|
+
|
|
369
|
+
function storeSetterAsyncFindings(content) {
|
|
370
|
+
const setters = new Set([...content.matchAll(LOCAL_STORE_SETTER_DECL)].map((m) => m[1]));
|
|
371
|
+
if (setters.size === 0) return [];
|
|
372
|
+
const findings = [];
|
|
373
|
+
for (const call of content.matchAll(/(?<![.\w$])(set[A-Z][\w$]*)\s*\(\s*async\b/g)) {
|
|
374
|
+
if (setters.has(call[1])) findings.push({ index: call.index });
|
|
375
|
+
}
|
|
376
|
+
return findings;
|
|
377
|
+
}
|
|
378
|
+
|
|
341
379
|
function effectSyncFindings(content) {
|
|
342
380
|
const localSetters = new Set([...content.matchAll(LOCAL_SETTER_DECL)].map((m) => m[1]));
|
|
343
381
|
if (localSetters.size === 0) return [];
|
|
@@ -603,7 +641,16 @@ const CHECKS = [
|
|
|
603
641
|
id: 'dynamic-jsx',
|
|
604
642
|
pattern: /<Dynamic[\s/>]/g,
|
|
605
643
|
message:
|
|
606
|
-
'`<Dynamic>` is
|
|
644
|
+
'`<Dynamic>` is deprecated (Solid 2.0.0-rc.9). Hoist a component with dynamic(() => ...) from "@solidjs/web" so the factory is built once and the component identity stays stable.',
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
// `setStore(async (draft) => …)` on a setter declared via createStore /
|
|
648
|
+
// createOptimisticStore in this file — the draft closes before the await
|
|
649
|
+
// resumes. `setSignal(async …)` is not flagged (a signal may hold a promise).
|
|
650
|
+
id: 'store-setter-async',
|
|
651
|
+
find: storeSetterAsyncFindings,
|
|
652
|
+
message:
|
|
653
|
+
'Store setter callbacks are synchronous transactions — writes after the first `await` are lost (dev throws [ASYNC_STORE_SETTER]). Await outside, then call the setter with a synchronous draft mutation (inside an action, `yield` first).',
|
|
607
654
|
},
|
|
608
655
|
{
|
|
609
656
|
// `action(async (item) => ...)` with core `action` never re-enters the
|
|
@@ -28,6 +28,11 @@ Steps:
|
|
|
28
28
|
`isPending` treated as a global spinner instead of a per-expression question;
|
|
29
29
|
- treating `<Errored>` as a terminal ErrorBoundary, or routing per-row mutation
|
|
30
30
|
failures through it (those belong in the action / a projection-folded map);
|
|
31
|
+
error reporting done as a side effect inside `fallback` instead of
|
|
32
|
+
`configureClientErrors` / `configureServerErrors` / `render(..., { onError })`;
|
|
33
|
+
- store setter callbacks that `await` (the draft closes when the callback returns —
|
|
34
|
+
`[ASYNC_STORE_SETTER]`), store setters called at component-body top level, and
|
|
35
|
+
`latest()` used as a null-safe read of an unsettled source;
|
|
31
36
|
- nested fetches assumed to waterfall, or `<Loading>` lifted along with a lifted fetch;
|
|
32
37
|
- rewriting App with loading/error branches, or snapshot/restore, when wrapping
|
|
33
38
|
a client store in server functions; disabling optimistic rows until ack;
|
|
@@ -52,7 +57,14 @@ Steps:
|
|
|
52
57
|
attribution-only store/list/effect costs) left unaddressed;
|
|
53
58
|
- context values passed as snapshots instead of accessors/setters/stores;
|
|
54
59
|
- components with conditional/early returns on reactive values.
|
|
55
|
-
5.
|
|
60
|
+
5. If the change set touches stores, lists, async computations, actions, or effects, run
|
|
61
|
+
the skill's development loop against the dev server: `/__solid/diagnostics` `begin` →
|
|
62
|
+
exercise the changed UI → `whyDidRun` for the changed scopes / `costs` → `end`
|
|
63
|
+
(`@solidjs/diagnostics` installed), or an `isDev`-guarded
|
|
64
|
+
`attribution.enable({ log: false })` + `why()` / `costs()` / `feedback()`. Report
|
|
65
|
+
every coded warning, the silent holds in `feedback().sources`, and the top
|
|
66
|
+
`costs().scopes` entries as findings.
|
|
67
|
+
6. Verify any API you are not certain about against the official docs mirror
|
|
56
68
|
(`https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt`); never trust Solid 1.x or
|
|
57
69
|
React memory.
|
|
58
70
|
|
|
@@ -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`. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — children/return types are `Element` from `solid-js`; DOM `JSX` types come from `@solidjs/web`. 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`. Fire-and-forget confirmations wait with `yield until(predicate, {timeout})`. 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, and 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 `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`. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — children/return types are `Element` from `solid-js`; DOM `JSX` types come from `@solidjs/web`. 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`. Fire-and-forget confirmations wait with `yield until(predicate, {timeout})`. 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), and a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]`. Error monitoring is `configureClientErrors` / `configureServerErrors` (or `render(..., { onError })`), 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()`. 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.
|
|
@@ -15,7 +15,10 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
15
15
|
1. **Never destructure props.** `function C({ name })` and `const { name } = props` read the
|
|
16
16
|
prop getter once at setup and kill reactivity. Keep `props.x` and read it inside JSX, a
|
|
17
17
|
memo, or an effect compute function. Defaults go at the read site: `props.x ?? fallback`.
|
|
18
|
-
Rest props: `omit(props, "label", "value")
|
|
18
|
+
Rest props: `omit(props, "label", "value")` (or a predicate: `omit(props, (k) => k.startsWith("data-"))`),
|
|
19
|
+
never `const rest = { ...props }` (a snapshot). `merge` / `omit` return read-only live
|
|
20
|
+
views — assigning onto them is a silent no-op; `{ ...merged }` gives an own object that
|
|
21
|
+
never updates again.
|
|
19
22
|
JSX `{...rest}` is fine once `rest` is a reactive proxy. Rule of thumb: the string `props.`
|
|
20
23
|
must never appear at component-body top level *as a read* (`const x = props.x`); nested
|
|
21
24
|
functions (`() => props.x`, `omit(props, ...)`, `children(() => props.children)`) are fine.
|
|
@@ -83,6 +86,12 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
83
86
|
`reconcile` or the derived form (dev + attribution flags the store-level waste as
|
|
84
87
|
`[IMMUTABLE_UPDATE_IN_STORE]`). Stores are
|
|
85
88
|
**property reads** (`todos.length`, `todo.title`), not accessors — never `todos()`.
|
|
89
|
+
The setter callback is a **synchronous** transaction: `setStore(async (draft) => …)`
|
|
90
|
+
throws `[ASYNC_STORE_SETTER]` in dev (writes after the first `await` were silently
|
|
91
|
+
lost before). `await` first, then call the setter (inside an `action`, `yield` first).
|
|
92
|
+
Mechanically enforced by `solid2-kit check`. A store setter called at
|
|
93
|
+
component-body top level is a write in an owned scope (`[REACTIVE_WRITE_IN_OWNED_SCOPE]`,
|
|
94
|
+
same as a signal setter) — seed the initial shape through the `createStore` argument.
|
|
86
95
|
10. **Async data is an async computation**: `createMemo(async () => ...)` (or
|
|
87
96
|
`createMemo(() => fetchUser(id()))`) read under `<Loading>` / `<Errored>`.
|
|
88
97
|
**Fetch high, block low** — creating/reading are like sync; consuming (await)
|
|
@@ -103,7 +112,9 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
103
112
|
accessor) only when that identity change should show the fallback again. Do not start `fetch` (or any request) at component-body top
|
|
104
113
|
level — that runs once at mount and is not a reactive source. Do not `try/catch` `NotReadyError` around a read, and do not
|
|
105
114
|
use `loadingValue` / `seedLoadingValue` as the default first-flight UI — those skip
|
|
106
|
-
`<Loading>`. `{latest(() => x())}` is a preview, not the visible answer
|
|
115
|
+
`<Loading>`. `{latest(() => x())}` is a preview, not the visible answer, and not a
|
|
116
|
+
null-safe probe: `latest(user)` before the first value throws `NotReadyError` in every
|
|
117
|
+
scope (event handlers included) — it never returns `undefined` for an unsettled source.
|
|
107
118
|
Default navigation holds: keep `selectedId()` so highlight and content stay
|
|
108
119
|
consistent; `latest(selectedId)` only when the highlight should move first.
|
|
109
120
|
Pair `class={{ pending: isPending(selectedId) }}` with a short CSS
|
|
@@ -118,7 +129,12 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
118
129
|
Granularity is the expression (`isPending(() => item.quantity)` with
|
|
119
130
|
`affects(item, "quantity")`). A hold with no feedback at all is `[SILENT_HOLD]`
|
|
120
131
|
under attribution — always pair a held write with one of these questions, an
|
|
121
|
-
optimistic value, or `affects()`.
|
|
132
|
+
optimistic value, or `affects()`. When a change touches stores, lists, async,
|
|
133
|
+
actions, or effects, run the skill's development loop before finishing: dev console
|
|
134
|
+
free of `[CODE]` findings, then an attribution session (`/__solid/diagnostics`
|
|
135
|
+
`begin` → interact → `whyDidRun` / `costs` → `end`, or `isDev`-guarded
|
|
136
|
+
`attribution.enable({ log: false })`). `vite dev` is always the dev build;
|
|
137
|
+
`solid({ observe: true })` is the production-observability opt-in, not a dev step.
|
|
122
138
|
"Prop with local edits" = writable derivation:
|
|
123
139
|
`createSignal(() => props.value)` or `createStore(() => props.value, fallback)`.
|
|
124
140
|
`createOptimistic` is for an in-flight mutation, not a local editing session.
|
|
@@ -176,6 +192,13 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
176
192
|
live reconnect, input change). `reset` retries the collected *sources*, not a UI
|
|
177
193
|
remount. Per-row mutation failures belong in an errors map the projection folds
|
|
178
194
|
in (survives the optimistic overlay), not in `<Errored>` around the whole list.
|
|
195
|
+
Error monitoring is a hook, not a side effect in the fallback: the boundary-caught
|
|
196
|
+
failures no global handler sees reach `configureClientErrors({ onError })` from
|
|
197
|
+
`solid-js` (or `render(..., { onError })` per root) and `configureServerErrors({ onError })`
|
|
198
|
+
from `@solidjs/web` — do not call `captureException` inside `<Errored fallback>`.
|
|
199
|
+
The server hook's return value replaces the sanitized error on the wire: return
|
|
200
|
+
nothing or a reference id, never the error itself (message/stack/secrets would
|
|
201
|
+
reach the browser).
|
|
179
202
|
Making a client store "real" is additive: same setters, wrap mutations in
|
|
180
203
|
`action`, swap `createOptimisticStore` and a file of server functions — do
|
|
181
204
|
not rewrite `App.tsx` with loading/error branches.
|
|
@@ -212,7 +235,10 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
212
235
|
cross an async gap default to `action` + `createOptimistic`/`createOptimisticStore` —
|
|
213
236
|
the sync mutation *is* the prediction (an overlay, discarded on settle); write the
|
|
214
237
|
body as a generator (`function*` / `async function*`) suspending on `yield`, never a
|
|
215
|
-
plain `async` function (`solid2-kit check` flags that)
|
|
238
|
+
plain `async` function (`solid2-kit check` flags that). After a bare `await`, put a
|
|
239
|
+
bare `yield` before the next write **and** before anything that creates a reader
|
|
240
|
+
(`yield until(...)`, `latest()`, a memo/effect) — `until(...)`'s argument is evaluated
|
|
241
|
+
in the post-`await` continuation, outside the transaction; put
|
|
216
242
|
`pending` on the record rather than a second copy of state. Do not snapshot
|
|
217
243
|
and restore, disable optimistic rows until ack, mutex rapid clicks, or freeze
|
|
218
244
|
unrelated writes because one action is in flight; failed-action replay is
|
|
@@ -263,7 +289,8 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
263
289
|
— `lazy(() => import("./pages").then((m) => ({ default: m.About })))` hydrates wrong
|
|
264
290
|
because the named export is not a call-site literal. Select a component from reactive
|
|
265
291
|
state with `dynamic(() => ...)` from `@solidjs/web`
|
|
266
|
-
(stable identity)
|
|
292
|
+
(stable identity); `<Dynamic component={...}>` is deprecated (`@deprecated` in the
|
|
293
|
+
types since 2.0.0-rc.9, still shipped in 2.0). If the project has `@solidjs/router`, routes are `createRouter({ routes })`
|
|
267
294
|
at module scope — not JSX `<Route>` / `<A>` / `<HashRouter>`. That package's `action` /
|
|
268
295
|
`query` are URL-addressable POST forms and a read cache — not core `action` /
|
|
269
296
|
`refresh` from `solid-js`. Router mutations: `<form action={save} method="post">`
|
|
@@ -291,7 +318,11 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
291
318
|
(`<meta name="description">`) that Solid Meta should manage.
|
|
292
319
|
Return `respond(value, { status, headers })` from a `"use server"` function, not
|
|
293
320
|
`Response.json` — a raw `Response` is HTTP-handler control flow; scripted
|
|
294
|
-
callers should receive the value.
|
|
321
|
+
callers should receive the value. Production sanitizes every thrown value that
|
|
322
|
+
crosses the wire — server-function throws **and** SSR render errors (an `<Errored>`
|
|
323
|
+
fallback printing `err().message` shows `"Internal Server Error"` in a production
|
|
324
|
+
server render) — so intentional client-facing failures are `markSafeError(...)` or
|
|
325
|
+
`throw respond(body, { status })`. Reads are function calls too (`await getUser(id)`
|
|
295
326
|
on a `GET()`-declared read; the method mirrors the operation). Return or throw a
|
|
296
327
|
redirect (`if (!session?.userId) throw redirect("/sign-in")`); `redirect()` /
|
|
297
328
|
`respond()` take `revalidate` like `reload()`. Cache GET reads with
|
|
@@ -367,7 +398,7 @@ directory (default `src/`).
|
|
|
367
398
|
| `startTransition`, `useTransition` | automatic held updates + `isPending` |
|
|
368
399
|
| `onError` / `catchError` | `<Errored>` or effect bundle `{ effect, error }` |
|
|
369
400
|
| `from(...)` / `observable(...)` (the solid-js helpers) | inbound: async iterable from a memo; outbound: split effect |
|
|
370
|
-
| `createDynamic(...)
|
|
401
|
+
| `createDynamic(...)`, `<Dynamic component={...}>` (deprecated in 2.0) | `dynamic(source)` from `@solidjs/web` |
|
|
371
402
|
| `renderToStringAsync(...)` | `await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`) |
|
|
372
403
|
| `clearDelegatedEvents` | delete — delegated listeners are scoped to each render root |
|
|
373
404
|
| `vite-plugin-solid` / `jsxImportSource: "solid-js"` | `@solidjs/vite-plugin` / `"jsxImportSource": "@solidjs/web"` |
|
|
@@ -383,7 +414,8 @@ When unsure about any API, verify against the official Solid 2.0 docs — fetcha
|
|
|
383
414
|
listed in `references/official-docs.md` next to the `solid-2` skill. Do not guess from
|
|
384
415
|
Solid 1.x or React memory.
|
|
385
416
|
|
|
386
|
-
`createTrackedEffect
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
path setters
|
|
417
|
+
`createTrackedEffect` and `createRenderEffect` exist in Solid 2 but are not defaults:
|
|
418
|
+
use two-phase `createEffect` unless a single tracked callback is required. `storePath`
|
|
419
|
+
(the 1.x path-setter migration helper) is no longer exported from `solid-js` as of
|
|
420
|
+
2.0.0-rc.9 — convert path setters to draft setters instead of importing it from
|
|
421
|
+
`@solidjs/signals`.
|
|
@@ -68,7 +68,7 @@ Most React `useEffect` code should NOT become `createEffect`:
|
|
|
68
68
|
| Inspect, count, or iterate children | `children(() => props.children)` then `.toArray()` |
|
|
69
69
|
| Code-split a component | `lazy(() => import("./X"))` read under `<Loading>` |
|
|
70
70
|
| Named export from a lazy module | `lazy(() => import("./pages"), { export: "About" })` |
|
|
71
|
-
| Pick a component/tag from reactive state | `dynamic(() => ...)` from `@solidjs/web` (stable identity;
|
|
71
|
+
| Pick a component/tag from reactive state | `dynamic(() => ...)` from `@solidjs/web` (stable identity; `<Dynamic>` is deprecated) |
|
|
72
72
|
| Overlay / modal | `<Portal>` — hoist async reads *above* the portal (reads inside start on the client) |
|
|
73
73
|
| 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 |
|
|
74
74
|
| 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)`) |
|
|
@@ -120,7 +120,11 @@ To merge defaults or split props reactively use `merge` / `omit` from `solid-js`
|
|
|
120
120
|
Omitted keys still fall through, so `merge({ type: "button" }, props)` is the
|
|
121
121
|
usual defaults pattern. That is not Solid 1 `mergeProps`, which ignored
|
|
122
122
|
`undefined`. Forwarding leftover props: `const rest = omit(props, "label")` then
|
|
123
|
-
`<input {...rest}
|
|
123
|
+
`<input {...rest} />`; a predicate hides keys by rule without enumerating first
|
|
124
|
+
(`omit(props, (key) => key.startsWith("data-"))`). `const rest = { ...props }` compiles
|
|
125
|
+
and then never updates. Both helpers return **read-only live views** (since
|
|
126
|
+
2.0.0-rc.9 for every input, plain objects included): assigning onto the result is a
|
|
127
|
+
silent no-op, and `{ ...merged }` is an own object that carries no sources.
|
|
124
128
|
|
|
125
129
|
### Store updates (draft mutation)
|
|
126
130
|
|
|
@@ -134,6 +138,26 @@ setProfile((draft) => {
|
|
|
134
138
|
});
|
|
135
139
|
```
|
|
136
140
|
|
|
141
|
+
The setter callback is a **synchronous transaction** — the draft closes when it
|
|
142
|
+
returns. `setProfile(async (draft) => { draft.name = await load(); })` used to commit
|
|
143
|
+
only the writes before the first `await` and silently drop the rest; since
|
|
144
|
+
2.0.0-rc.9 it throws `[ASYNC_STORE_SETTER]` in dev (`solid2-kit check` flags it too).
|
|
145
|
+
Await outside, then write synchronously (inside an `action`, `yield` first):
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
// WRONG — the draft is already closed when the await resumes
|
|
149
|
+
setProfile(async (draft) => { draft.name = await fetchName(); });
|
|
150
|
+
|
|
151
|
+
// CORRECT
|
|
152
|
+
const name = await fetchName();
|
|
153
|
+
setProfile((draft) => { draft.name = name; });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
A signal may hold a promise, so `setSignal` has no such rule. A store setter called
|
|
157
|
+
at component-body top level is a write in an owned scope — dev throws
|
|
158
|
+
`[REACTIVE_WRITE_IN_OWNED_SCOPE]` (rc.9 closed the exemption store writes had) — so
|
|
159
|
+
seed the shape through the `createStore` argument or a derivation.
|
|
160
|
+
|
|
137
161
|
**External collections enter stores through reconciliation, never wholesale assignment.**
|
|
138
162
|
`draft.todos = serverTodos` renders correctly but replaces every object identity — all
|
|
139
163
|
subscribers under the path re-run and row identity is lost. In order of preference:
|
|
@@ -204,8 +228,11 @@ const Result = dynamic(() => (detailed() ? Detailed : Compact));
|
|
|
204
228
|
`dynamic()` returns a **stable** component whose source can be a component, an
|
|
205
229
|
intrinsic tag name, a promise, or empty. Prefer it over swapping a component
|
|
206
230
|
variable in JSX (`const View = tab() ? A : B` freezes the choice at setup).
|
|
207
|
-
`<Dynamic>` is the
|
|
208
|
-
|
|
231
|
+
`<Dynamic component={...}>` is the same primitive with a worse shape (the tag travels
|
|
232
|
+
in the props bag, so every instance merges and omits it and rebuilds a factory) and
|
|
233
|
+
is **deprecated** since 2.0.0-rc.9 — `@deprecated` in the types, no runtime warning,
|
|
234
|
+
still shipped in 2.0. Hoist a `dynamic()` per component instance (or per module for a
|
|
235
|
+
constant tag) instead.
|
|
209
236
|
|
|
210
237
|
### Two-phase effect (imperative boundary only)
|
|
211
238
|
|
|
@@ -357,7 +384,11 @@ return <StoryDetail story={story} />;
|
|
|
357
384
|
React ErrorBoundary. `reset` retries the collected *data sources*, not a UI remount.
|
|
358
385
|
- Refetch: `refresh(results)`. In-flight indicator: `isPending(results)` (or
|
|
359
386
|
`isPending(() => results())`). Freshest in-flight value for a preview:
|
|
360
|
-
`latest(results)`.
|
|
387
|
+
`latest(results)`. `latest` is not a null-safe probe: before the source's first value
|
|
388
|
+
it throws `NotReadyError` in every scope (since 2.0.0-rc.9 also in event handlers and
|
|
389
|
+
imperative code, which used to receive `undefined`). A write is visible to `latest()` /
|
|
390
|
+
`isPending()` only from the flush that carries it — `flush()` first when a test reads
|
|
391
|
+
its own write through them. Do not start `fetch` at component-body top level.
|
|
361
392
|
- Coordinate sibling `<Loading>` reveal with `<Reveal>` (`order="sequential"` default,
|
|
362
393
|
or `"together"` / `"natural"`). `collapsed` (sequential only) suppresses tail
|
|
363
394
|
skeletons past the frontier so the page does not stack fallbacks as popcorn.
|
|
@@ -384,6 +415,52 @@ repeating `String(error())` at each boundary:
|
|
|
384
415
|
<Errored fallback={(error, reset) => <ErrorFallback error={error} reset={reset} />}>
|
|
385
416
|
```
|
|
386
417
|
|
|
418
|
+
**Report boundary-caught errors through the error hooks, not from the fallback.** An
|
|
419
|
+
error an `<Errored>` collected never reaches `window.onerror`; a `captureException`
|
|
420
|
+
call inside `fallback` is a side effect in render that fires on every re-render of the
|
|
421
|
+
fallback. Since 2.0.0-rc.9 the runtime has a seam for exactly this failure, called
|
|
422
|
+
once per error object with where it was thrown (`ownerPath`) and where it was met
|
|
423
|
+
(`boundaryPath`):
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import { configureClientErrors } from 'solid-js';
|
|
427
|
+
import { configureServerErrors } from '@solidjs/web';
|
|
428
|
+
|
|
429
|
+
configureClientErrors({
|
|
430
|
+
onError: (error, { ownerPath, boundaryPath }) => report(error, { ownerPath, boundaryPath }),
|
|
431
|
+
});
|
|
432
|
+
// server side (SSR renders and server functions); runs inside the request scope, so
|
|
433
|
+
// getRequestEvent() works here. Return nothing, or a reference the user can quote —
|
|
434
|
+
// NEVER the error itself: the return value replaces the sanitized value on the wire
|
|
435
|
+
configureServerErrors({
|
|
436
|
+
onError(error, { kind, handling, ownerPath, boundaryPath, functionId, direct }) {
|
|
437
|
+
const ref = report(error, { site: `${kind}/${handling}`, ownerPath, boundaryPath, functionId, direct });
|
|
438
|
+
return new Error(`Something went wrong (ref ${ref})`);
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**The server hook's return value goes on the wire.** What the client normally receives
|
|
444
|
+
is the runtime's sanitized value (a generic `Error` outside the dev build). Returning
|
|
445
|
+
the error object hands its `message`, stack, and whatever secret they carry to the
|
|
446
|
+
browser. `kind`/`handling` name the road: `render` / `fallback` (an `<Errored>`
|
|
447
|
+
rendered), `render` / `client` (a `<Loading>` fragment rejected, client re-renders),
|
|
448
|
+
`render` / `failed` (request fails; return ignored), `render` / `serialize` (a
|
|
449
|
+
hydration value would not serialize), `server-function` / `thrown` (`direct: true` for
|
|
450
|
+
an in-process call during SSR), `server-function` / `channel` (a rejection or throw
|
|
451
|
+
escaping through the result graph — a promise, an iterable, or a stream — after the
|
|
452
|
+
head committed). A monitoring SDK's `init()` that
|
|
453
|
+
registers this hook must load before the server graph — put it in the plugin's
|
|
454
|
+
`start: { instrument: "./src/instrument.ts" }`, not at the top of an entry (see
|
|
455
|
+
[Production observability](#production-observability-the-observe-build)).
|
|
456
|
+
|
|
457
|
+
`render` / `hydrate` accept a per-root `onError` that wins over the ambient hook.
|
|
458
|
+
Uncaught errors (`REACTIVITY_HALTED`) go to the platform's `reportError`, which every
|
|
459
|
+
monitor already listens on — do not report them twice. `renderToStream` /
|
|
460
|
+
`renderToString`'s `onError` option *is* the per-render server hook: it now hears every
|
|
461
|
+
handled failure, so filter on `context.handling === "failed"` when only request-failing
|
|
462
|
+
errors matter (the rc.8 one-argument form keeps working).
|
|
463
|
+
|
|
387
464
|
### Streams and subscriptions (websockets, reactive clients)
|
|
388
465
|
|
|
389
466
|
Push-based sources integrate by returning an **async iterable** from a computation — reads
|
|
@@ -532,7 +609,11 @@ Write the body as a generator — `function*` or `async function*`, never a plai
|
|
|
532
609
|
`async (...) =>` — because only `yield` re-enters the transaction: the runtime has no
|
|
533
610
|
hook into a plain `await` continuation, so even inside `async function*` an `await`
|
|
534
611
|
leaves the transaction until the next `yield` (`solid2-kit check` flags the
|
|
535
|
-
non-generator form).
|
|
612
|
+
non-generator form). The bare `yield` must also come before anything that **creates a
|
|
613
|
+
reader** — `until()`, `latest()`, a memo or effect, a mount — not only before writes:
|
|
614
|
+
the expression of the next `yield` is evaluated in the post-`await` continuation, so
|
|
615
|
+
`await save(); yield until(...)` builds the predicate's reader outside the transaction.
|
|
616
|
+
Write `await save(); yield; yield until(...)`.
|
|
536
617
|
|
|
537
618
|
When confirmation arrives outside the response — a fire-and-forget transport echoed
|
|
538
619
|
back on a live source — hold the action open with `yield until(predicate, { timeout })`:
|
|
@@ -789,13 +870,33 @@ an agent sneaks in fails the suite instead of scrolling by.
|
|
|
789
870
|
|
|
790
871
|
### Dev diagnostics and attribution
|
|
791
872
|
|
|
792
|
-
|
|
793
|
-
|
|
873
|
+
Solid ships **three builds** of every runtime package, selected by export condition:
|
|
874
|
+
|
|
875
|
+
| Build | Condition | Carries | `OBSERVE` | `DEV` |
|
|
876
|
+
|---|---|---|---|---|
|
|
877
|
+
| prod | default | the runtime + the two error hooks | `undefined` | `undefined` |
|
|
878
|
+
| observe | `observe` | prod + records channel, diagnostics channel, attribution slot, server trace slot; no console output, no checks | object | `undefined` |
|
|
879
|
+
| dev | `development` | observe + the development checks and the console reporter; unminified | object | object |
|
|
880
|
+
|
|
881
|
+
`vite dev` is **always the dev build** (the `development` condition wins), so everything
|
|
882
|
+
below works there with no configuration. The observe build is a *production* opt-in —
|
|
883
|
+
see [Production observability](#production-observability-the-observe-build) — not a
|
|
884
|
+
dev setting. Diagnostics come in two layers: **findings** (facts about a render, present
|
|
885
|
+
in observe and dev) and **checks** (dev-only guidance printed by the console reporter).
|
|
886
|
+
Always-on dev checks include misplaced reads
|
|
794
887
|
(`[STRICT_READ_UNTRACKED]`, `[PENDING_ASYNC_UNTRACKED_READ]`), misplaced writes
|
|
795
|
-
(`[REACTIVE_WRITE_IN_OWNED_SCOPE]
|
|
888
|
+
(`[REACTIVE_WRITE_IN_OWNED_SCOPE]` — since rc.9 also for store setters called in a
|
|
889
|
+
component or root body, `[ASYNC_STORE_SETTER]` for a store setter callback that returns
|
|
890
|
+
a promise, `[FLUSH_IN_ACTION]`, `[SERVER_WRITE]`), and
|
|
796
891
|
`[ASYNC_OUTSIDE_LOADING_BOUNDARY]` when a tracked read has no boundary above it.
|
|
797
892
|
Each entry carries a stable `[CODE]`, what the runtime observed, and an owner chain
|
|
798
893
|
(`in <App> › <Cart> › <LineItem>`); never silence a code before understanding it.
|
|
894
|
+
Since rc.9 the server render reports on the same channel with the same owner chains:
|
|
895
|
+
`[SSR_RENDER_ERROR_CONTAINED]` (a boundary routed a render error; `ownerPath` is where
|
|
896
|
+
it was thrown, `boundaryPath` where it was met), `[SSR_ERROR_SANITIZED]` (the original
|
|
897
|
+
behind a production-sanitized error), `[SSR_CLIENT_CONTENT_MASKED]` (client-only content
|
|
898
|
+
that surfaced only after a real server wait — the wait was wasted), server-side
|
|
899
|
+
`[ASYNC_WATERFALL]`, and `[REVEAL_IN_RENDER_TO_STRING]`.
|
|
799
900
|
For the repair behind any code, read the `reactivity-diagnostics` skill Solid ships
|
|
800
901
|
in the repo (`node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md`) — every
|
|
801
902
|
code maps to what the runtime observed and what to change.
|
|
@@ -804,17 +905,24 @@ Cost and responsiveness findings need the opt-in attribution engine, which recor
|
|
|
804
905
|
every scope that re-ran, what changed to cause it, and how long it took. Since
|
|
805
906
|
`solid-js` 2.0.0-rc.8 the engine lives behind its own subpath — `DEV.attribution`
|
|
806
907
|
no longer exists (it is a type error; `DEV` holds only devtools hooks, graph
|
|
807
|
-
traversal, and console reporting)
|
|
808
|
-
|
|
908
|
+
traversal, and console reporting). `OBSERVE` is the public observability surface on
|
|
909
|
+
the observe and dev builds — `OBSERVE.records`, `OBSERVE.diagnostics`,
|
|
910
|
+
`OBSERVE.exclude`, `OBSERVE.server.trace` are for app and tool code; only the
|
|
911
|
+
`OBSERVE.attribution` hook slot (`install` / `withInteraction` / `withOrigin`) belongs
|
|
912
|
+
to engines, routers, and devtools. Since 2.0.0-rc.9 the folds, queries, and
|
|
913
|
+
formatters are **named exports** of that subpath, not methods of `attribution`
|
|
914
|
+
(`attribution.why` / `.costs` / `.feedback` / `.subscriptions` / `.format` are type
|
|
915
|
+
errors); `attribution` keeps `enable`, `disable`, `subscribe`, `markFlight`, and the
|
|
916
|
+
record ring buffers (`history`, `holds`, `waterfalls`, `navigations`, `interactions`):
|
|
809
917
|
|
|
810
918
|
```ts
|
|
811
|
-
import { attribution } from 'solid-js/attribution';
|
|
919
|
+
import { attribution, formatRerun, why } from 'solid-js/attribution';
|
|
812
920
|
import { isDev } from '@solidjs/web';
|
|
813
921
|
|
|
814
922
|
if (isDev) attribution.enable({ log: false });
|
|
815
923
|
// reproduce the interaction, then ask why a scope ran:
|
|
816
|
-
for (const event of
|
|
817
|
-
console.log(
|
|
924
|
+
for (const event of why(total)) {
|
|
925
|
+
console.log(formatRerun(event));
|
|
818
926
|
}
|
|
819
927
|
```
|
|
820
928
|
|
|
@@ -822,9 +930,15 @@ for (const event of attribution.why(total)) {
|
|
|
822
930
|
(`log: true`); pass `{ log: false }` when you only want the coded findings and plan
|
|
823
931
|
to query the tables yourself. Name the scopes you intend to interrogate
|
|
824
932
|
(`createMemo(..., { name: "total" })`) — chains refer to nodes by name, and
|
|
825
|
-
anonymous nodes print as `computed` / `effect` / `signal
|
|
826
|
-
|
|
827
|
-
|
|
933
|
+
anonymous nodes print as `computed` / `effect` / `signal`; component labels
|
|
934
|
+
(`<Checkout>`) are always present under `vite dev` and survive minification only with
|
|
935
|
+
the compiler's `componentNames` option (`solid({ observe: true })` turns it on). On the
|
|
936
|
+
**prod build** the import resolves to an inert same-surface twin whose `enable()` is a
|
|
937
|
+
no-op, so the import can stay in the code. On the **observe build** it is the real
|
|
938
|
+
engine and an unguarded `enable()` records in production — keep the `isDev` guard
|
|
939
|
+
unless recording in production is the intent. The split is pay-for-use: importing
|
|
940
|
+
`costs` or `feedback` is what turns their tables on, so a records-only consumer
|
|
941
|
+
(`attribution.subscribe(...)`) ships neither.
|
|
828
942
|
|
|
829
943
|
While attribution is enabled, Solid also warns on its own about over-subscription,
|
|
830
944
|
chained async waterfalls (`[ASYNC_WATERFALL]`), memos whose fresh-but-equal output
|
|
@@ -835,15 +949,20 @@ row-rebuilding lists (`[UNSTABLE_LIST_IDENTITY]` — key by id or reconcile into
|
|
|
835
949
|
store), and silent holds (`[SILENT_HOLD]` — a write held on async work with no
|
|
836
950
|
`isPending()` / `latest()` reader, optimistic value, `affects()` mark, or effect that
|
|
837
951
|
ran while held; info from 100ms, warning from 200ms, tunable via
|
|
838
|
-
`enable({ holds: { infoMs, warnMs } })`).
|
|
839
|
-
`
|
|
840
|
-
`
|
|
841
|
-
`
|
|
952
|
+
`enable({ holds: { infoMs, warnMs } })`). A hold that *was* acknowledged but still ran
|
|
953
|
+
long is `[LONG_HOLD]` (info from 500ms, warning from 1000ms;
|
|
954
|
+
`enable({ longHolds: { infoMs, warnMs } })`) — the suggestion is a `<Loading on={...}>`
|
|
955
|
+
boundary, never removing the hold. `costs()`, `feedback()` (named imports), and
|
|
956
|
+
`attribution.holds()` rank the session worst-first; `attribution.waterfalls()`,
|
|
957
|
+
`attribution.navigations()`, and `attribution.interactions()` hold the fact tables
|
|
958
|
+
behind the verdicts.
|
|
959
|
+
`subscriptions(scope)` (named import) lists a scope's current dependencies to compare
|
|
842
960
|
against what it uses.
|
|
843
961
|
|
|
844
962
|
For regression tests, `@solidjs/diagnostics` turns the same channels into assertions:
|
|
845
963
|
|
|
846
964
|
```ts
|
|
965
|
+
import '@solidjs/diagnostics/vitest'; // registers the matchers (or list it in vitest `setupFiles`)
|
|
847
966
|
import { captureArtifact } from '@solidjs/diagnostics';
|
|
848
967
|
|
|
849
968
|
const { artifact } = await captureArtifact(() => {
|
|
@@ -863,22 +982,113 @@ verification loops: capture until the channel is quiet, scenario budgets as the
|
|
|
863
982
|
definition of done, and hold/latency gates. See the debugging-reactivity guide in
|
|
864
983
|
[references/official-docs.md](references/official-docs.md).
|
|
865
984
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
985
|
+
### Development loop: self-diagnose before finishing
|
|
986
|
+
|
|
987
|
+
Enable observability in development **by default** and lean on it. The dev build
|
|
988
|
+
already prints every check; the attribution engine is the part you turn on. Run the
|
|
989
|
+
loop whenever a change touches **stores, lists, async computations, actions, or
|
|
990
|
+
effects** — a pure markup or styling change does not need it.
|
|
991
|
+
|
|
992
|
+
1. **Dev console is clean.** Reproduce the change in `vite dev`; zero `[CODE]` entries
|
|
993
|
+
is the bar (`[STRICT_READ_UNTRACKED]`, `[REACTIVE_WRITE_IN_OWNED_SCOPE]`,
|
|
994
|
+
`[ASYNC_STORE_SETTER]`, `[ASYNC_OUTSIDE_LOADING_BOUNDARY]`, …). Fix, do not silence.
|
|
995
|
+
2. **Name the scopes you changed** (`createMemo(fn, { name: "total" })`,
|
|
996
|
+
`createStore(v, { name: "cart" })`) so chains and tables refer to them by name.
|
|
997
|
+
3. **Record the interaction and ask.** First choice — no app code — is the diagnostics
|
|
998
|
+
endpoint: with `@solidjs/diagnostics` in `devDependencies`, the Vite plugin's
|
|
999
|
+
`diagnostics` option (auto-on when the package is declared; dev server only, never
|
|
1000
|
+
under vitest, `vite build`, or preview) injects the capture bridge and serves
|
|
1001
|
+
`/__solid/diagnostics`. `begin` enables attribution with `log: false` for the
|
|
1002
|
+
session; `end` disables it and returns the artifact (`diagnostics`,
|
|
1003
|
+
`attribution.{reruns, costs, holds, feedback}`, `records`):
|
|
1004
|
+
|
|
1005
|
+
```sh
|
|
1006
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"begin"}'
|
|
1007
|
+
# ... perform the interaction in the browser ...
|
|
1008
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"whyDidRun","params":{"name":"total"}}'
|
|
1009
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"costs"}'
|
|
1010
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"end"}' # holds + feedback tables are in the result
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
`GET` the endpoint for status and the method list (`begin` / `end` / `active` /
|
|
1014
|
+
`whyDidRun` / `costs` — `feedback` and `holds` come back from `end`). With several
|
|
1015
|
+
open tabs the first responder wins, so keep one page under test. Without
|
|
1016
|
+
`@solidjs/diagnostics`, the in-code alternative is an `isDev`-guarded
|
|
1017
|
+
`attribution.enable({ log: false })` in the client entry, then `why(scope)` /
|
|
1018
|
+
`costs()` / `feedback()` from the console or a scratch effect — never `log: true`
|
|
1019
|
+
left on (one collapsed `[why-run]` group per re-run) and never unguarded (on the
|
|
1020
|
+
observe build it records in production).
|
|
1021
|
+
4. **Read the verdicts, then the tables.** Fix every coded warning the session raised
|
|
1022
|
+
(`[SILENT_HOLD]`, `[IMMUTABLE_UPDATE_IN_STORE]`, `[UNSTABLE_LIST_IDENTITY]`,
|
|
1023
|
+
`[EFFECT_RELAY_TEAR]`, `[EFFECT_WRITES_OWN_SOURCE]`, `[ASYNC_WATERFALL]`,
|
|
1024
|
+
`[HOT_SCOPE_RERUNS]`). Then `costs().scopes` (self time, `wastedMs` for runs whose
|
|
1025
|
+
result did not change), `costs().writes` (root writes by downstream work),
|
|
1026
|
+
`feedback().sources` / `.interactions` / `.flights` / `.fallbacks` (silent holds,
|
|
1027
|
+
held time per interaction, abandoned requests, fallback flashes). A scope that
|
|
1028
|
+
re-runs more than its inputs change is a `why(scope)` question — the chain names the
|
|
1029
|
+
write, the changed dependency, and the time.
|
|
1030
|
+
5. **Make it a test when it is worth keeping**: `captureArtifact` +
|
|
1031
|
+
`toHaveNoDiagnostics()` / `toHaveNoSilentHolds()` / `toStayWithinRerunBudget(n,
|
|
1032
|
+
{ scope })` is the definition of done for the scenario you just tuned.
|
|
1033
|
+
|
|
1034
|
+
The bundled `agent-loops` skill (`node_modules/@solidjs/diagnostics/skills/agent-loops/SKILL.md`)
|
|
1035
|
+
is the long form of this loop; `solid2-kit init` prints the devDependency note when it
|
|
1036
|
+
runs in a project.
|
|
1037
|
+
|
|
1038
|
+
### Production observability: the observe build
|
|
1039
|
+
|
|
1040
|
+
Everything above is the dev build. Records, traces, and attribution in **production**
|
|
1041
|
+
need the observe build, opted into per project — and it changes nothing under `vite dev`:
|
|
1042
|
+
|
|
1043
|
+
```ts
|
|
1044
|
+
// vite.config.ts
|
|
1045
|
+
import solid from '@solidjs/vite-plugin';
|
|
1046
|
+
|
|
1047
|
+
export default { plugins: [solid({ ssr: true, observe: true })] };
|
|
878
1048
|
```
|
|
879
1049
|
|
|
880
|
-
`
|
|
881
|
-
|
|
1050
|
+
`observe: true` adds the `observe` condition to every environment (client and server,
|
|
1051
|
+
`vite build` and preview) and turns on the compiler's `componentNames` option so
|
|
1052
|
+
`ownerPath` / `boundaryPath` name components instead of `computed`. Without the plugin,
|
|
1053
|
+
set `resolve.conditions` (or `node --conditions=observe` for an unbundled server) and
|
|
1054
|
+
`componentNames` yourself. Cost in Solid's size suite: ~16.8 KB against ~15.3 KB brotli
|
|
1055
|
+
for a small client app; importing the attribution engine on top raises the cap to
|
|
1056
|
+
~27 KB (a separate entry — an observe build that never imports it never ships it). The
|
|
1057
|
+
two error hooks are **not** part of this: they fire on every build, prod included, only
|
|
1058
|
+
the paths are `undefined` there.
|
|
1059
|
+
|
|
1060
|
+
- **Instrumentation order.** A monitoring SDK's `init()` (and `configureServerErrors`)
|
|
1061
|
+
must run before the modules it patches load. `import "./instrument"` at the top of an
|
|
1062
|
+
entry does not achieve that in ESM (static imports are hoisted in dependency order);
|
|
1063
|
+
the plugin's `start: { instrument: "./src/instrument.ts" }` awaits that server-only
|
|
1064
|
+
module before anything else in the server graph.
|
|
1065
|
+
- **Tracing.** The server continues an incoming W3C `traceparent` or originates one;
|
|
1066
|
+
`getTraceContext()` from `@solidjs/web` reads it during the request (in-process
|
|
1067
|
+
server-function calls included) — forward it downstream with
|
|
1068
|
+
`headers: { ...getTraceContext()?.entries }`. The browser is told which trace the page
|
|
1069
|
+
belongs to on `Server-Timing` and `<meta>` tags, but only when the incoming trace was
|
|
1070
|
+
sampled or a provider answered; a page with no tracing tool sees no wire change. On
|
|
1071
|
+
the observe build a tracing SDK supplies the trace with
|
|
1072
|
+
`OBSERVE?.server.trace.provide((request) => ({ traceId, spanId, entries }))`, once per
|
|
1073
|
+
request. Do not write middleware to inject trace headers.
|
|
1074
|
+
- **Records.** `OBSERVE?.records.subscribe(type, (event, live) => …)` delivers one plain
|
|
1075
|
+
record per `"boundary"` (a `<Loading>` that waited on the server), `"invocation"`
|
|
1076
|
+
(server-function execution), `"call"` (server-function call from the page — shares
|
|
1077
|
+
`id` with its invocation), and `"frame"` (produced / applied). Records are
|
|
1078
|
+
serializable data; live handles (request, args, the error as thrown) ride in the
|
|
1079
|
+
second argument. Listeners run synchronously inside the runtime, **must not write
|
|
1080
|
+
signals**, and should subscribe from a module that loads before the app.
|
|
1081
|
+
- **Interactions.** `attribution.enable()` then
|
|
1082
|
+
`attribution.subscribe("interaction", (event) => …)` says what each click waited on
|
|
1083
|
+
(`event.settledMs`, `event.holds[].blockers`, `event.holds[].holdMs`); a hold nothing acknowledged
|
|
1084
|
+
is the dead click. The fold tables stay separate imports.
|
|
1085
|
+
- **What leaves the process.** Beyond names, four record fields carry page data —
|
|
1086
|
+
`target` (element as `tag#id "text"`, 30 chars), `prev` / `value` previews (40 chars),
|
|
1087
|
+
navigation `to` / `from` / `params`, and `data.error` on server render findings — an
|
|
1088
|
+
exporter decides what to do with each. A tool that renders inside the app it watches
|
|
1089
|
+
(devtools panel) marks its root with `OBSERVE?.exclude(getOwner()!)` inside its
|
|
1090
|
+
`createRoot` so its own effects and stores never become findings; do not route its
|
|
1091
|
+
writes through `runWithOwner` (that is a write in an owned scope).
|
|
882
1092
|
|
|
883
1093
|
## Client mode: no server HTML, no hydration reflexes
|
|
884
1094
|
|
|
@@ -1118,11 +1328,17 @@ reaches the client — the directive is the privacy boundary. Mutations that sho
|
|
|
1118
1328
|
revalidate: `redirect` / `reload` from `@solidjs/web` (see the server-function
|
|
1119
1329
|
guides). Return or throw a redirect — guards read `if (!session?.userId) throw
|
|
1120
1330
|
redirect("/sign-in")`. `redirect()` and `respond()` accept the same `revalidate`
|
|
1121
|
-
option as `reload()` when the mutation also changes location or returns a value
|
|
1331
|
+
option as `reload()` when the mutation also changes location or returns a value;
|
|
1332
|
+
`revalidate: REVALIDATE_ALL` (`"*"`, from `@solidjs/web`) declares every cache entry
|
|
1333
|
+
stale, distinct from omitting it (the host's default) and from `[]` (nothing). After a router/server mutation do **not** follow with a client `fetch`
|
|
1122
1334
|
to "refresh the page" — `reload` / `revalidate` (and the router's single-flight
|
|
1123
1335
|
response) close that loop. For 404 during SSR: `httpStatus(404)` from `@solidjs/web`.
|
|
1124
1336
|
`throw new Error("…")` is stripped to `"Internal Server Error"` in production —
|
|
1125
|
-
|
|
1337
|
+
on the server-function wire **and**, since 2.0.0-rc.9, on every SSR road a failure
|
|
1338
|
+
takes (an `<Errored>` fallback rendered on the server, a rejected async source
|
|
1339
|
+
serialized into the stream, a `<Loading>` fragment's rejection), so `err().message`
|
|
1340
|
+
in a fallback reads the generic message in a production server render. Use
|
|
1341
|
+
`markSafeError` or `respond(..., { status })` for intentional client-facing
|
|
1126
1342
|
failures. Do not `return Response.json(...)` from `"use server"` — that is
|
|
1127
1343
|
HTTP-handler control flow; `respond(value, { status })` is what a scripted
|
|
1128
1344
|
caller unwraps. JSON-encodable arguments only unless `enableRichArguments()`
|
|
@@ -1317,7 +1533,20 @@ how to reuse. Prefer the form on the right.
|
|
|
1317
1533
|
| `submission.clear()` from an effect | on dismiss/resubmit only — from an effect, errors flash and vanish |
|
|
1318
1534
|
| `revalidate(liveSource)` | `live()` updates through the open stream; do not revalidate it |
|
|
1319
1535
|
| `refresh(user)` without `affects` when the reload should look pending | pair `affects(user)` with `refresh(user)` — a bare `refresh` re-asks quietly |
|
|
1320
|
-
| `<Dynamic component={tab() ? A : B} />` | `const View = dynamic(() => (tab() ? A : B))` — `<Dynamic>` is
|
|
1536
|
+
| `<Dynamic component={tab() ? A : B} />` | `const View = dynamic(() => (tab() ? A : B))` — `<Dynamic>` is deprecated (rc.9); `dynamic()` hoists the factory and keeps identity stable |
|
|
1537
|
+
| `setStore(async (draft) => { draft.x = await load(); })` | `const x = await load(); setStore((draft) => { draft.x = x; })` — the setter is a synchronous transaction; dev throws `[ASYNC_STORE_SETTER]` |
|
|
1538
|
+
| `setStore((d) => { d.items = seed; })` in a component body | seed through `createStore(seed)` / `createStore(() => props.items, fallback)` — a body-level store write is `[REACTIVE_WRITE_IN_OWNED_SCOPE]` |
|
|
1539
|
+
| `const saved = await api.save(); yield until(() => ...)` | `await api.save(); yield; yield until(() => ...)` — a bare `yield` re-enters the transaction before any reader is created, not only before writes |
|
|
1540
|
+
| `fallback={(err) => { captureException(err()); return <Fallback />; }}` | `configureClientErrors({ onError })` from `solid-js` (or `render(..., { onError })`), `configureServerErrors({ onError })` from `@solidjs/web` — once per error, with `ownerPath` / `boundaryPath` |
|
|
1541
|
+
| `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())` |
|
|
1542
|
+
| `attribution.why(total)` / `attribution.format(e)` / `attribution.costs()` | named imports from `solid-js/attribution`: `why(total)`, `formatRerun(e)`, `costs()`, `feedback()`, `subscriptions(scope)` (rc.9) |
|
|
1543
|
+
| `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 |
|
|
1544
|
+
| `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) |
|
|
1545
|
+
| `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 |
|
|
1546
|
+
| `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 |
|
|
1547
|
+
| 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` |
|
|
1548
|
+
| `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 |
|
|
1549
|
+
| `import { storePath } from "solid-js"` | gone from `solid-js` (rc.9); write draft setters `setState((d) => { d.user.name = "Grace"; })` |
|
|
1321
1550
|
| `<meta name="description" content="...">` hardcoded in the document shell | `<Meta>` from `@solidjs/meta`. A static `<title>` is the fallback; other hardcoded tags coexist with the registry |
|
|
1322
1551
|
| `useHead({ tag: "meta", ... })` for ordinary page tags | `<Title>` / `<Meta>` / `<Script>` (JSON-LD). `useHead` is the lower-level registry |
|
|
1323
1552
|
| two `<Meta property="og:image">` meant as one replacement set | wrap them in `<Head>` — a later group replaces the earlier set as one unit |
|
|
@@ -1490,7 +1719,9 @@ always-applied rules installed alongside this skill.
|
|
|
1490
1719
|
or `isPending(selectedId)` when the write is the question. `<Errored>` heals
|
|
1491
1720
|
when the source succeeds; `reset` retries sources, not a UI remount. Per-row
|
|
1492
1721
|
mutation failures stay in the action/projection, not in `<Errored>` around
|
|
1493
|
-
the list.
|
|
1722
|
+
the list. Error monitoring goes through `configureClientErrors` /
|
|
1723
|
+
`configureServerErrors` (or `render(..., { onError })`), never a side effect in
|
|
1724
|
+
the fallback. Core `refresh(source)`, router `revalidate(key)`, and `return reload(...)` are
|
|
1494
1725
|
different APIs — do not mix them. Client → server is additive (same
|
|
1495
1726
|
setters + `action` + server functions): do not rewrite App with
|
|
1496
1727
|
loading/error branches, disable optimistic rows until ack, or refetch in
|
|
@@ -1513,11 +1744,17 @@ always-applied rules installed alongside this skill.
|
|
|
1513
1744
|
(values), not `typeof window`.
|
|
1514
1745
|
Component teardown is `onSettled` (return cleanup), not `onCleanup`.
|
|
1515
1746
|
- [ ] Dev console shows no diagnostics: reads in tracking scopes (no
|
|
1516
|
-
`[STRICT_READ_UNTRACKED]`),
|
|
1517
|
-
|
|
1747
|
+
`[STRICT_READ_UNTRACKED]`), store setters synchronous and never called at
|
|
1748
|
+
body level (no `[ASYNC_STORE_SETTER]` / `[REACTIVE_WRITE_IN_OWNED_SCOPE]`), held
|
|
1749
|
+
writes paired with `isPending` / `latest` / optimistic values / `affects()`
|
|
1750
|
+
(no `[SILENT_HOLD]`). Attribution-only costs
|
|
1518
1751
|
(`[IMMUTABLE_UPDATE_IN_STORE]`, `[UNSTABLE_LIST_IDENTITY]`, `[EFFECT_*]`)
|
|
1519
|
-
checked via
|
|
1520
|
-
|
|
1752
|
+
checked via the [development loop](#development-loop-self-diagnose-before-finishing)
|
|
1753
|
+
(`/__solid/diagnostics` `begin` → interact → `whyDidRun` / `costs` → `end`, or an
|
|
1754
|
+
`isDev`-guarded `attribution.enable({ log: false })` + `why` / `costs` /
|
|
1755
|
+
`feedback`) whenever the change touched stores, lists, async, actions, or
|
|
1756
|
+
effects. Name interrogated scopes (`{ name: "total" }`). `observe: true` is a
|
|
1757
|
+
production opt-in, not a dev step.
|
|
1521
1758
|
- [ ] If the project has a router: `createRouter({ routes })`, not JSX `<Route>` / `<A>`.
|
|
1522
1759
|
One instance, no nested `<Router>`. Navigate with `useNavigate` / `<a href>`, not
|
|
1523
1760
|
`window.location`. Router `action`/`query` come from `@solidjs/router` (POST forms + cache), not
|
|
@@ -67,6 +67,9 @@ read these before writing route JSX.
|
|
|
67
67
|
|
|
68
68
|
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/avoid-unnecessary-effects.md
|
|
69
69
|
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/debugging-reactivity.md
|
|
70
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/performance.md
|
|
71
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/observability.md
|
|
72
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/observability-adapters.md
|
|
70
73
|
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/lists.md
|
|
71
74
|
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/data-fetching-patterns.md
|
|
72
75
|
- https://v2-rebuild--solid-docs-v2.netlify.app/guides/forms.md
|
|
@@ -117,9 +120,17 @@ verify API names against the reference pages above.
|
|
|
117
120
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/specialized-reactivity/on-cleanup.md
|
|
118
121
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/store-advanced/snapshot.md
|
|
119
122
|
|
|
120
|
-
## Reference: diagnostics
|
|
123
|
+
## Reference: diagnostics, attribution, and observability
|
|
124
|
+
|
|
125
|
+
`DEV` is dev builds only; `OBSERVE` and attribution exist on observe and dev builds;
|
|
126
|
+
the error hooks fire on every build.
|
|
121
127
|
|
|
122
128
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/diagnostics-dev-hooks/dev.md
|
|
129
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/diagnostics-dev-hooks/observe.md
|
|
130
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/diagnostics-dev-hooks/attribution.md
|
|
131
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-js/advanced/diagnostics-dev-hooks/configure-client-errors.md
|
|
132
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response/configure-server-errors.md
|
|
133
|
+
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/request-response/get-trace-context.md
|
|
123
134
|
|
|
124
135
|
## Reference: components and JSX (solid-js)
|
|
125
136
|
|
|
@@ -151,7 +162,6 @@ verify API names against the reference pages above.
|
|
|
151
162
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/head/use-head.md
|
|
152
163
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/components/portal.md
|
|
153
164
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/components/dynamic.md
|
|
154
|
-
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/components/dynamic-component.md
|
|
155
165
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/jsx-properties/ref.md
|
|
156
166
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/jsx-properties/class.md
|
|
157
167
|
- https://v2-rebuild--solid-docs-v2.netlify.app/reference/solid-web/jsx-properties/style.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid2-agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|