solid2-agent-kit 0.12.0 → 0.14.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/solid2-kit.mjs +20 -0
- package/files/commands/solid2-review.md +11 -1
- package/files/shared/agents-section.md +1 -1
- package/files/shared/rules-body.md +23 -5
- package/files/skills/solid-2/SKILL.md +247 -33
- package/files/skills/solid-2/references/official-docs.md +12 -2
- package/package.json +1 -1
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 ------------------------------------------------------------------
|
|
@@ -34,6 +34,9 @@ Steps:
|
|
|
34
34
|
`[ASYNC_STORE_SETTER]`), store setters called at component-body top level, and
|
|
35
35
|
`latest()` used as a null-safe read of an unsettled source;
|
|
36
36
|
- nested fetches assumed to waterfall, or `<Loading>` lifted along with a lifted fetch;
|
|
37
|
+
- mutations on a value read through `live()` that settle on `yield save()` alone or
|
|
38
|
+
`refresh()` the live-derived store, instead of `yield until(predicate, { timeout })`
|
|
39
|
+
for the stream's echo (the old value flashes back when the overlay drops);
|
|
37
40
|
- rewriting App with loading/error branches, or snapshot/restore, when wrapping
|
|
38
41
|
a client store in server functions; disabling optimistic rows until ack;
|
|
39
42
|
- tRPC / type-gen around `"use server"`, a client `fetch` after a mutation,
|
|
@@ -57,7 +60,14 @@ Steps:
|
|
|
57
60
|
attribution-only store/list/effect costs) left unaddressed;
|
|
58
61
|
- context values passed as snapshots instead of accessors/setters/stores;
|
|
59
62
|
- components with conditional/early returns on reactive values.
|
|
60
|
-
5.
|
|
63
|
+
5. If the change set touches stores, lists, async computations, actions, or effects, run
|
|
64
|
+
the skill's development loop against the dev server: `/__solid/diagnostics` `begin` →
|
|
65
|
+
exercise the changed UI → `whyDidRun` for the changed scopes / `costs` → `end`
|
|
66
|
+
(`@solidjs/diagnostics` installed), or an `isDev`-guarded
|
|
67
|
+
`attribution.enable({ log: false })` + `why()` / `costs()` / `feedback()`. Report
|
|
68
|
+
every coded warning, the silent holds in `feedback().sources`, and the top
|
|
69
|
+
`costs().scopes` entries as findings.
|
|
70
|
+
6. Verify any API you are not certain about against the official docs mirror
|
|
61
71
|
(`https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt`); never trust Solid 1.x or
|
|
62
72
|
React memory.
|
|
63
73
|
|
|
@@ -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`.
|
|
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`. 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), 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.
|
|
@@ -129,7 +129,12 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
129
129
|
Granularity is the expression (`isPending(() => item.quantity)` with
|
|
130
130
|
`affects(item, "quantity")`). A hold with no feedback at all is `[SILENT_HOLD]`
|
|
131
131
|
under attribution — always pair a held write with one of these questions, an
|
|
132
|
-
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.
|
|
133
138
|
"Prop with local edits" = writable derivation:
|
|
134
139
|
`createSignal(() => props.value)` or `createStore(() => props.value, fallback)`.
|
|
135
140
|
`createOptimistic` is for an in-flight mutation, not a local editing session.
|
|
@@ -191,6 +196,9 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
191
196
|
failures no global handler sees reach `configureClientErrors({ onError })` from
|
|
192
197
|
`solid-js` (or `render(..., { onError })` per root) and `configureServerErrors({ onError })`
|
|
193
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).
|
|
194
202
|
Making a client store "real" is additive: same setters, wrap mutations in
|
|
195
203
|
`action`, swap `createOptimisticStore` and a file of server functions — do
|
|
196
204
|
not rewrite `App.tsx` with loading/error branches.
|
|
@@ -234,9 +242,16 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
234
242
|
`pending` on the record rather than a second copy of state. Do not snapshot
|
|
235
243
|
and restore, disable optimistic rows until ack, mutex rapid clicks, or freeze
|
|
236
244
|
unrelated writes because one action is in flight; failed-action replay is
|
|
237
|
-
optional.
|
|
238
|
-
{ timeout })` — the predicate reads authoritative state
|
|
239
|
-
|
|
245
|
+
optional. Confirmations that arrive on another channel wait with `yield until(predicate,
|
|
246
|
+
{ timeout })` — the predicate reads authoritative state, so the overlay cannot satisfy it.
|
|
247
|
+
That covers fire-and-forget sends **and** request/response mutations whose result is
|
|
248
|
+
read through a `live()` source: `live()` takes no part in revalidation or single-flight,
|
|
249
|
+
so after `yield save()` alone the overlay drops before the stream echoes the write and
|
|
250
|
+
the old value flashes back. Follow it with `yield until(...)`, not `refresh(liveStore)`
|
|
251
|
+
(that re-runs the derivation — the stream closes and the source reconnects — so each
|
|
252
|
+
mutation costs a reconnect, and the flash remains whenever the new connection's first
|
|
253
|
+
value predates the write). Skip `until` only for reactive clients (e.g. Convex)
|
|
254
|
+
whose subscriptions already carry the write when the mutation resolves. Compiler: `"jsxImportSource": "@solidjs/web"` (not `"solid-js"`);
|
|
240
255
|
Vite plugin is `@solidjs/vite-plugin` (not `vite-plugin-solid`) — run `solid2-kit doctor`
|
|
241
256
|
after touching `package.json` / tsconfig / root configs; it fails on React and Solid 1.x
|
|
242
257
|
wiring. Server vs browser:
|
|
@@ -338,7 +353,10 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
|
|
|
338
353
|
components from `"use server"` unless the project already enabled the
|
|
339
354
|
experimental `serverFunctions.components` flag. JSON-encodable server-function arguments
|
|
340
355
|
only, unless `enableRichArguments()` was called once in the client entry
|
|
341
|
-
(`Date` / `Map` / `Set` throw without it). `live()`
|
|
356
|
+
(`Date` / `Map` / `Set` throw without it). Declare live reads as `live(GET(fn))` —
|
|
357
|
+
`live()` outermost. A plain `async function*` server function is an event stream on
|
|
358
|
+
one connection (no reconnect when it drops); `live()` is one value that changes over
|
|
359
|
+
time and reconnects. `live()` connection state is
|
|
342
360
|
`source.onstatus` (`"connected"` / `"reconnecting"` / `"closed"`), never a
|
|
343
361
|
field in the yielded value. Sibling `<Loading>` reveal order is `<Reveal>`
|
|
344
362
|
(`collapsed` suppresses tail skeletons under sequential order).
|
|
@@ -429,11 +429,31 @@ import { configureServerErrors } from '@solidjs/web';
|
|
|
429
429
|
configureClientErrors({
|
|
430
430
|
onError: (error, { ownerPath, boundaryPath }) => report(error, { ownerPath, boundaryPath }),
|
|
431
431
|
});
|
|
432
|
-
// server side (SSR renders and server functions);
|
|
433
|
-
//
|
|
434
|
-
|
|
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
|
+
});
|
|
435
441
|
```
|
|
436
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
|
+
|
|
437
457
|
`render` / `hydrate` accept a per-root `onError` that wins over the ambient hook.
|
|
438
458
|
Uncaught errors (`REACTIVITY_HALTED`) go to the platform's `reportError`, which every
|
|
439
459
|
monitor already listens on — do not report them twice. `renderToStream` /
|
|
@@ -612,6 +632,38 @@ only the echo can. Timeout rejects with `TimeoutError` (strongly recommended on
|
|
|
612
632
|
droppable transports; abort via `signal`). Outside actions, `await until(() => user())`
|
|
613
633
|
waits for a value before continuing.
|
|
614
634
|
|
|
635
|
+
The same hold applies when the mutation *does* answer but the value is read through a
|
|
636
|
+
`live()` source. A live source updates only through its open stream — it takes no part
|
|
637
|
+
in revalidation or single-flight mutation data — so the mutation's response and the
|
|
638
|
+
stream's echo race. Settle on `yield saveBid(...)` alone and the overlay is discarded
|
|
639
|
+
first: the old bid flashes back until the stream catches up.
|
|
640
|
+
|
|
641
|
+
```tsx
|
|
642
|
+
import { action, createOptimisticStore, until } from 'solid-js';
|
|
643
|
+
import { liveAuction, saveBid } from '../data/auction';
|
|
644
|
+
|
|
645
|
+
function Auction(props: { id: string }) {
|
|
646
|
+
const [auction, setAuction] = createOptimisticStore(() => liveAuction(props.id), { highBid: 0 });
|
|
647
|
+
|
|
648
|
+
const bid = action(function* (amount: number) {
|
|
649
|
+
setAuction((a) => { a.highBid = amount; }); // overlay
|
|
650
|
+
yield saveBid(props.id, amount); // the write goes up
|
|
651
|
+
yield until(() => auction.highBid >= amount, { timeout: 10_000 }); // hold for the echo
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
return <button onClick={() => bid(auction.highBid + 1)}>Bid {auction.highBid + 1}</button>;
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Do not `refresh(auction)` in place of `until`: on a store derived from a live source it
|
|
659
|
+
re-runs the derivation — the current stream is closed and `liveAuction()` opens a new
|
|
660
|
+
connection. That costs a reconnect per mutation and hides the flash only when the new
|
|
661
|
+
connection's first value already includes the write; when the read side lags the write,
|
|
662
|
+
the old value still flashes back before the echo. Reactive clients whose
|
|
663
|
+
subscriptions already carry the write when the mutation resolves (e.g. Convex) need
|
|
664
|
+
neither. The server side of `liveAuction` / `saveBid` is in
|
|
665
|
+
[Server functions](#server-functions--use-server).
|
|
666
|
+
|
|
615
667
|
Navigation-shaped updates do **not** need core `action`. A plain setter is enough:
|
|
616
668
|
reads pull the async, and downstream async computeds hold previous values until the
|
|
617
669
|
new ones are ready (`isPending` / `latest`). Reach for `action` from `solid-js` only
|
|
@@ -850,8 +902,20 @@ an agent sneaks in fails the suite instead of scrolling by.
|
|
|
850
902
|
|
|
851
903
|
### Dev diagnostics and attribution
|
|
852
904
|
|
|
853
|
-
|
|
854
|
-
|
|
905
|
+
Solid ships **three builds** of every runtime package, selected by export condition:
|
|
906
|
+
|
|
907
|
+
| Build | Condition | Carries | `OBSERVE` | `DEV` |
|
|
908
|
+
|---|---|---|---|---|
|
|
909
|
+
| prod | default | the runtime + the two error hooks | `undefined` | `undefined` |
|
|
910
|
+
| observe | `observe` | prod + records channel, diagnostics channel, attribution slot, server trace slot; no console output, no checks | object | `undefined` |
|
|
911
|
+
| dev | `development` | observe + the development checks and the console reporter; unminified | object | object |
|
|
912
|
+
|
|
913
|
+
`vite dev` is **always the dev build** (the `development` condition wins), so everything
|
|
914
|
+
below works there with no configuration. The observe build is a *production* opt-in —
|
|
915
|
+
see [Production observability](#production-observability-the-observe-build) — not a
|
|
916
|
+
dev setting. Diagnostics come in two layers: **findings** (facts about a render, present
|
|
917
|
+
in observe and dev) and **checks** (dev-only guidance printed by the console reporter).
|
|
918
|
+
Always-on dev checks include misplaced reads
|
|
855
919
|
(`[STRICT_READ_UNTRACKED]`, `[PENDING_ASYNC_UNTRACKED_READ]`), misplaced writes
|
|
856
920
|
(`[REACTIVE_WRITE_IN_OWNED_SCOPE]` — since rc.9 also for store setters called in a
|
|
857
921
|
component or root body, `[ASYNC_STORE_SETTER]` for a store setter callback that returns
|
|
@@ -873,8 +937,11 @@ Cost and responsiveness findings need the opt-in attribution engine, which recor
|
|
|
873
937
|
every scope that re-ran, what changed to cause it, and how long it took. Since
|
|
874
938
|
`solid-js` 2.0.0-rc.8 the engine lives behind its own subpath — `DEV.attribution`
|
|
875
939
|
no longer exists (it is a type error; `DEV` holds only devtools hooks, graph
|
|
876
|
-
traversal, and console reporting)
|
|
877
|
-
|
|
940
|
+
traversal, and console reporting). `OBSERVE` is the public observability surface on
|
|
941
|
+
the observe and dev builds — `OBSERVE.records`, `OBSERVE.diagnostics`,
|
|
942
|
+
`OBSERVE.exclude`, `OBSERVE.server.trace` are for app and tool code; only the
|
|
943
|
+
`OBSERVE.attribution` hook slot (`install` / `withInteraction` / `withOrigin`) belongs
|
|
944
|
+
to engines, routers, and devtools. Since 2.0.0-rc.9 the folds, queries, and
|
|
878
945
|
formatters are **named exports** of that subpath, not methods of `attribution`
|
|
879
946
|
(`attribution.why` / `.costs` / `.feedback` / `.subscriptions` / `.format` are type
|
|
880
947
|
errors); `attribution` keeps `enable`, `disable`, `subscribe`, `markFlight`, and the
|
|
@@ -895,11 +962,15 @@ for (const event of why(total)) {
|
|
|
895
962
|
(`log: true`); pass `{ log: false }` when you only want the coded findings and plan
|
|
896
963
|
to query the tables yourself. Name the scopes you intend to interrogate
|
|
897
964
|
(`createMemo(..., { name: "total" })`) — chains refer to nodes by name, and
|
|
898
|
-
anonymous nodes print as `computed` / `effect` / `signal
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
so
|
|
965
|
+
anonymous nodes print as `computed` / `effect` / `signal`; component labels
|
|
966
|
+
(`<Checkout>`) are always present under `vite dev` and survive minification only with
|
|
967
|
+
the compiler's `componentNames` option (`solid({ observe: true })` turns it on). On the
|
|
968
|
+
**prod build** the import resolves to an inert same-surface twin whose `enable()` is a
|
|
969
|
+
no-op, so the import can stay in the code. On the **observe build** it is the real
|
|
970
|
+
engine and an unguarded `enable()` records in production — keep the `isDev` guard
|
|
971
|
+
unless recording in production is the intent. The split is pay-for-use: importing
|
|
972
|
+
`costs` or `feedback` is what turns their tables on, so a records-only consumer
|
|
973
|
+
(`attribution.subscribe(...)`) ships neither.
|
|
903
974
|
|
|
904
975
|
While attribution is enabled, Solid also warns on its own about over-subscription,
|
|
905
976
|
chained async waterfalls (`[ASYNC_WATERFALL]`), memos whose fresh-but-equal output
|
|
@@ -910,7 +981,10 @@ row-rebuilding lists (`[UNSTABLE_LIST_IDENTITY]` — key by id or reconcile into
|
|
|
910
981
|
store), and silent holds (`[SILENT_HOLD]` — a write held on async work with no
|
|
911
982
|
`isPending()` / `latest()` reader, optimistic value, `affects()` mark, or effect that
|
|
912
983
|
ran while held; info from 100ms, warning from 200ms, tunable via
|
|
913
|
-
`enable({ holds: { infoMs, warnMs } })`).
|
|
984
|
+
`enable({ holds: { infoMs, warnMs } })`). A hold that *was* acknowledged but still ran
|
|
985
|
+
long is `[LONG_HOLD]` (info from 500ms, warning from 1000ms;
|
|
986
|
+
`enable({ longHolds: { infoMs, warnMs } })`) — the suggestion is a `<Loading on={...}>`
|
|
987
|
+
boundary, never removing the hold. `costs()`, `feedback()` (named imports), and
|
|
914
988
|
`attribution.holds()` rank the session worst-first; `attribution.waterfalls()`,
|
|
915
989
|
`attribution.navigations()`, and `attribution.interactions()` hold the fact tables
|
|
916
990
|
behind the verdicts.
|
|
@@ -920,6 +994,7 @@ against what it uses.
|
|
|
920
994
|
For regression tests, `@solidjs/diagnostics` turns the same channels into assertions:
|
|
921
995
|
|
|
922
996
|
```ts
|
|
997
|
+
import '@solidjs/diagnostics/vitest'; // registers the matchers (or list it in vitest `setupFiles`)
|
|
923
998
|
import { captureArtifact } from '@solidjs/diagnostics';
|
|
924
999
|
|
|
925
1000
|
const { artifact } = await captureArtifact(() => {
|
|
@@ -939,22 +1014,113 @@ verification loops: capture until the channel is quiet, scenario budgets as the
|
|
|
939
1014
|
definition of done, and hold/latency gates. See the debugging-reactivity guide in
|
|
940
1015
|
[references/official-docs.md](references/official-docs.md).
|
|
941
1016
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1017
|
+
### Development loop: self-diagnose before finishing
|
|
1018
|
+
|
|
1019
|
+
Enable observability in development **by default** and lean on it. The dev build
|
|
1020
|
+
already prints every check; the attribution engine is the part you turn on. Run the
|
|
1021
|
+
loop whenever a change touches **stores, lists, async computations, actions, or
|
|
1022
|
+
effects** — a pure markup or styling change does not need it.
|
|
1023
|
+
|
|
1024
|
+
1. **Dev console is clean.** Reproduce the change in `vite dev`; zero `[CODE]` entries
|
|
1025
|
+
is the bar (`[STRICT_READ_UNTRACKED]`, `[REACTIVE_WRITE_IN_OWNED_SCOPE]`,
|
|
1026
|
+
`[ASYNC_STORE_SETTER]`, `[ASYNC_OUTSIDE_LOADING_BOUNDARY]`, …). Fix, do not silence.
|
|
1027
|
+
2. **Name the scopes you changed** (`createMemo(fn, { name: "total" })`,
|
|
1028
|
+
`createStore(v, { name: "cart" })`) so chains and tables refer to them by name.
|
|
1029
|
+
3. **Record the interaction and ask.** First choice — no app code — is the diagnostics
|
|
1030
|
+
endpoint: with `@solidjs/diagnostics` in `devDependencies`, the Vite plugin's
|
|
1031
|
+
`diagnostics` option (auto-on when the package is declared; dev server only, never
|
|
1032
|
+
under vitest, `vite build`, or preview) injects the capture bridge and serves
|
|
1033
|
+
`/__solid/diagnostics`. `begin` enables attribution with `log: false` for the
|
|
1034
|
+
session; `end` disables it and returns the artifact (`diagnostics`,
|
|
1035
|
+
`attribution.{reruns, costs, holds, feedback}`, `records`):
|
|
1036
|
+
|
|
1037
|
+
```sh
|
|
1038
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"begin"}'
|
|
1039
|
+
# ... perform the interaction in the browser ...
|
|
1040
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"whyDidRun","params":{"name":"total"}}'
|
|
1041
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"costs"}'
|
|
1042
|
+
curl -X POST localhost:5173/__solid/diagnostics -d '{"method":"end"}' # holds + feedback tables are in the result
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
`GET` the endpoint for status and the method list (`begin` / `end` / `active` /
|
|
1046
|
+
`whyDidRun` / `costs` — `feedback` and `holds` come back from `end`). With several
|
|
1047
|
+
open tabs the first responder wins, so keep one page under test. Without
|
|
1048
|
+
`@solidjs/diagnostics`, the in-code alternative is an `isDev`-guarded
|
|
1049
|
+
`attribution.enable({ log: false })` in the client entry, then `why(scope)` /
|
|
1050
|
+
`costs()` / `feedback()` from the console or a scratch effect — never `log: true`
|
|
1051
|
+
left on (one collapsed `[why-run]` group per re-run) and never unguarded (on the
|
|
1052
|
+
observe build it records in production).
|
|
1053
|
+
4. **Read the verdicts, then the tables.** Fix every coded warning the session raised
|
|
1054
|
+
(`[SILENT_HOLD]`, `[IMMUTABLE_UPDATE_IN_STORE]`, `[UNSTABLE_LIST_IDENTITY]`,
|
|
1055
|
+
`[EFFECT_RELAY_TEAR]`, `[EFFECT_WRITES_OWN_SOURCE]`, `[ASYNC_WATERFALL]`,
|
|
1056
|
+
`[HOT_SCOPE_RERUNS]`). Then `costs().scopes` (self time, `wastedMs` for runs whose
|
|
1057
|
+
result did not change), `costs().writes` (root writes by downstream work),
|
|
1058
|
+
`feedback().sources` / `.interactions` / `.flights` / `.fallbacks` (silent holds,
|
|
1059
|
+
held time per interaction, abandoned requests, fallback flashes). A scope that
|
|
1060
|
+
re-runs more than its inputs change is a `why(scope)` question — the chain names the
|
|
1061
|
+
write, the changed dependency, and the time.
|
|
1062
|
+
5. **Make it a test when it is worth keeping**: `captureArtifact` +
|
|
1063
|
+
`toHaveNoDiagnostics()` / `toHaveNoSilentHolds()` / `toStayWithinRerunBudget(n,
|
|
1064
|
+
{ scope })` is the definition of done for the scenario you just tuned.
|
|
1065
|
+
|
|
1066
|
+
The bundled `agent-loops` skill (`node_modules/@solidjs/diagnostics/skills/agent-loops/SKILL.md`)
|
|
1067
|
+
is the long form of this loop; `solid2-kit init` prints the devDependency note when it
|
|
1068
|
+
runs in a project.
|
|
1069
|
+
|
|
1070
|
+
### Production observability: the observe build
|
|
1071
|
+
|
|
1072
|
+
Everything above is the dev build. Records, traces, and attribution in **production**
|
|
1073
|
+
need the observe build, opted into per project — and it changes nothing under `vite dev`:
|
|
1074
|
+
|
|
1075
|
+
```ts
|
|
1076
|
+
// vite.config.ts
|
|
1077
|
+
import solid from '@solidjs/vite-plugin';
|
|
1078
|
+
|
|
1079
|
+
export default { plugins: [solid({ ssr: true, observe: true })] };
|
|
954
1080
|
```
|
|
955
1081
|
|
|
956
|
-
`
|
|
957
|
-
|
|
1082
|
+
`observe: true` adds the `observe` condition to every environment (client and server,
|
|
1083
|
+
`vite build` and preview) and turns on the compiler's `componentNames` option so
|
|
1084
|
+
`ownerPath` / `boundaryPath` name components instead of `computed`. Without the plugin,
|
|
1085
|
+
set `resolve.conditions` (or `node --conditions=observe` for an unbundled server) and
|
|
1086
|
+
`componentNames` yourself. Cost in Solid's size suite: ~16.8 KB against ~15.3 KB brotli
|
|
1087
|
+
for a small client app; importing the attribution engine on top raises the cap to
|
|
1088
|
+
~27 KB (a separate entry — an observe build that never imports it never ships it). The
|
|
1089
|
+
two error hooks are **not** part of this: they fire on every build, prod included, only
|
|
1090
|
+
the paths are `undefined` there.
|
|
1091
|
+
|
|
1092
|
+
- **Instrumentation order.** A monitoring SDK's `init()` (and `configureServerErrors`)
|
|
1093
|
+
must run before the modules it patches load. `import "./instrument"` at the top of an
|
|
1094
|
+
entry does not achieve that in ESM (static imports are hoisted in dependency order);
|
|
1095
|
+
the plugin's `start: { instrument: "./src/instrument.ts" }` awaits that server-only
|
|
1096
|
+
module before anything else in the server graph.
|
|
1097
|
+
- **Tracing.** The server continues an incoming W3C `traceparent` or originates one;
|
|
1098
|
+
`getTraceContext()` from `@solidjs/web` reads it during the request (in-process
|
|
1099
|
+
server-function calls included) — forward it downstream with
|
|
1100
|
+
`headers: { ...getTraceContext()?.entries }`. The browser is told which trace the page
|
|
1101
|
+
belongs to on `Server-Timing` and `<meta>` tags, but only when the incoming trace was
|
|
1102
|
+
sampled or a provider answered; a page with no tracing tool sees no wire change. On
|
|
1103
|
+
the observe build a tracing SDK supplies the trace with
|
|
1104
|
+
`OBSERVE?.server.trace.provide((request) => ({ traceId, spanId, entries }))`, once per
|
|
1105
|
+
request. Do not write middleware to inject trace headers.
|
|
1106
|
+
- **Records.** `OBSERVE?.records.subscribe(type, (event, live) => …)` delivers one plain
|
|
1107
|
+
record per `"boundary"` (a `<Loading>` that waited on the server), `"invocation"`
|
|
1108
|
+
(server-function execution), `"call"` (server-function call from the page — shares
|
|
1109
|
+
`id` with its invocation), and `"frame"` (produced / applied). Records are
|
|
1110
|
+
serializable data; live handles (request, args, the error as thrown) ride in the
|
|
1111
|
+
second argument. Listeners run synchronously inside the runtime, **must not write
|
|
1112
|
+
signals**, and should subscribe from a module that loads before the app.
|
|
1113
|
+
- **Interactions.** `attribution.enable()` then
|
|
1114
|
+
`attribution.subscribe("interaction", (event) => …)` says what each click waited on
|
|
1115
|
+
(`event.settledMs`, `event.holds[].blockers`, `event.holds[].holdMs`); a hold nothing acknowledged
|
|
1116
|
+
is the dead click. The fold tables stay separate imports.
|
|
1117
|
+
- **What leaves the process.** Beyond names, four record fields carry page data —
|
|
1118
|
+
`target` (element as `tag#id "text"`, 30 chars), `prev` / `value` previews (40 chars),
|
|
1119
|
+
navigation `to` / `from` / `params`, and `data.error` on server render findings — an
|
|
1120
|
+
exporter decides what to do with each. A tool that renders inside the app it watches
|
|
1121
|
+
(devtools panel) marks its root with `OBSERVE?.exclude(getOwner()!)` inside its
|
|
1122
|
+
`createRoot` so its own effects and stores never become findings; do not route its
|
|
1123
|
+
writes through `runWithOwner` (that is a write in an owned scope).
|
|
958
1124
|
|
|
959
1125
|
## Client mode: no server HTML, no hydration reflexes
|
|
960
1126
|
|
|
@@ -1233,8 +1399,42 @@ export const fetchStory = GET(async (id: number) => {
|
|
|
1233
1399
|
});
|
|
1234
1400
|
```
|
|
1235
1401
|
|
|
1402
|
+
A value that changes over time is `live(GET(fn))` — **`live()` outermost**: `GET()`
|
|
1403
|
+
chooses the read transport and `live()` wraps the call behavior around it. `live()`
|
|
1404
|
+
does not imply `GET()`; without it the source streams over POST, which is fine when a
|
|
1405
|
+
cacheable URL is of no use.
|
|
1406
|
+
|
|
1407
|
+
```ts
|
|
1408
|
+
// src/data/auction.ts
|
|
1409
|
+
import { GET, live } from '@solidjs/web/server-functions';
|
|
1410
|
+
|
|
1411
|
+
export const liveAuction = live(
|
|
1412
|
+
GET(async function* (id: string) {
|
|
1413
|
+
'use server';
|
|
1414
|
+
yield await db.auctions.find(id); // current state first, from storage
|
|
1415
|
+
for await (const _change of db.auctions.subscribe(id)) {
|
|
1416
|
+
yield await db.auctions.find(id);
|
|
1417
|
+
}
|
|
1418
|
+
}),
|
|
1419
|
+
);
|
|
1420
|
+
|
|
1421
|
+
export async function saveBid(id: string, amount: number) {
|
|
1422
|
+
'use server';
|
|
1423
|
+
await db.bids.place(id, amount);
|
|
1424
|
+
}
|
|
1425
|
+
```
|
|
1426
|
+
|
|
1427
|
+
A plain `async function*` server function (no `live()`) is a different tool: an
|
|
1428
|
+
**event sequence** on one connection, consumed with `for await` and accumulated. When
|
|
1429
|
+
that connection drops, the iteration ends with an error and nothing reopens it; ending
|
|
1430
|
+
the iteration in the browser aborts the request and fires the server's
|
|
1431
|
+
`request.signal`. Use it for an order's status history; use `live()` when each value
|
|
1432
|
+
replaces the previous one.
|
|
1433
|
+
|
|
1236
1434
|
`live()` yields **current state** (each yield replaces the last); do not treat
|
|
1237
|
-
it as an append-only event log.
|
|
1435
|
+
it as an append-only event log. A reconnect calls the function again and its first
|
|
1436
|
+
yield replaces the stale answer, so read that first value from the state mutations write
|
|
1437
|
+
to (database, shared server state) — not from generator locals or per-connection memory. Until the first yield it is unsettled
|
|
1238
1438
|
(`<Loading>`), same as a Promise; later yields are updates — the fallback does
|
|
1239
1439
|
not return.
|
|
1240
1440
|
Wire status is a side channel on the **iterable the `live()` call returns**,
|
|
@@ -1335,7 +1535,10 @@ how to reuse. Prefer the form on the right.
|
|
|
1335
1535
|
| `throw new Error("expired")` from a server function (prod) | `throw markSafeError(...)` or `throw respond(body, { status })` |
|
|
1336
1536
|
| `const r = await api.x(); if (!r.ok) ...` result-object checks at every read | throw unusable responses; contain each region with `<Errored>` |
|
|
1337
1537
|
| `GET(async (id) => { "use server"; await db.delete(id) })` | mutations stay on POST; `GET()` is for idempotent reads |
|
|
1338
|
-
| `live()` yields as an event log to append | each yield **replaces** the current answer; yield current state first |
|
|
1538
|
+
| `live()` yields as an event log to append | each yield **replaces** the current answer; yield current state first, read from the state mutations write to — a reconnect calls the function again, so generator locals or per-connection memory are not the source |
|
|
1539
|
+
| `GET(live(async function* () { ... }))` | `live(GET(async function* () { ... }))` — `live()` outermost; `live()` alone streams over POST |
|
|
1540
|
+
| a plain `async function*` `"use server"` stream for a value that must survive a dropped connection | `live()` — a plain stream is an event sequence on one connection (`for await`, accumulate); nothing reopens it when it drops |
|
|
1541
|
+
| `yield saveBid(...)` then `refresh(auction)` on a store derived from `live()` | `yield saveBid(...); yield until(() => auction.highBid >= amount, { timeout })` — `live()` is outside revalidation/single-flight; `refresh` re-runs the derivation (closes the stream, calls the source again): a reconnect per mutation, and the old value still flashes back whenever the new first value predates the write |
|
|
1339
1542
|
| `<article innerHTML={html()}>…children…</article>` | `innerHTML` **or** children, not both |
|
|
1340
1543
|
| `fallback={(error) => <p>{error.message}</p>}` | `error` is an accessor: `error().message` (or `String(error())`) |
|
|
1341
1544
|
| `import { action } from "solid-js"` on a `<form>` | `import { action } from "@solidjs/router"` + `<form action={save} method="post">` (core `action` is a generator transaction, not a form URL) |
|
|
@@ -1406,6 +1609,12 @@ how to reuse. Prefer the form on the right.
|
|
|
1406
1609
|
| `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` |
|
|
1407
1610
|
| `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())` |
|
|
1408
1611
|
| `attribution.why(total)` / `attribution.format(e)` / `attribution.costs()` | named imports from `solid-js/attribution`: `why(total)`, `formatRerun(e)`, `costs()`, `feedback()`, `subscriptions(scope)` (rc.9) |
|
|
1612
|
+
| `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 |
|
|
1613
|
+
| `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) |
|
|
1614
|
+
| `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 |
|
|
1615
|
+
| `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 |
|
|
1616
|
+
| 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` |
|
|
1617
|
+
| `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 |
|
|
1409
1618
|
| `import { storePath } from "solid-js"` | gone from `solid-js` (rc.9); write draft setters `setState((d) => { d.user.name = "Grace"; })` |
|
|
1410
1619
|
| `<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 |
|
|
1411
1620
|
| `useHead({ tag: "meta", ... })` for ordinary page tags | `<Title>` / `<Meta>` / `<Script>` (JSON-LD). `useHead` is the lower-level registry |
|
|
@@ -1586,7 +1795,9 @@ always-applied rules installed alongside this skill.
|
|
|
1586
1795
|
setters + `action` + server functions): do not rewrite App with
|
|
1587
1796
|
loading/error branches, disable optimistic rows until ack, or refetch in
|
|
1588
1797
|
`hydrate` / `onSettled`. HTTP does not enforce TypeScript — validate
|
|
1589
|
-
inside `"use server"`; do not invent tRPC. `live()` owns the client connection
|
|
1798
|
+
inside `"use server"`; do not invent tRPC. `live()` owns the client connection,
|
|
1799
|
+
is declared `live(GET(fn))`, and a mutation on a live-read value holds with
|
|
1800
|
+
`yield until(...)` for the echo instead of `refresh()`.
|
|
1590
1801
|
A server bundle uses `handleRequest` from `dist/server/server.js`; client-only start
|
|
1591
1802
|
is static `dist/client`. Do not delay `renderToStream` for visual order
|
|
1592
1803
|
(`<Reveal>` does).
|
|
@@ -1609,9 +1820,12 @@ always-applied rules installed alongside this skill.
|
|
|
1609
1820
|
writes paired with `isPending` / `latest` / optimistic values / `affects()`
|
|
1610
1821
|
(no `[SILENT_HOLD]`). Attribution-only costs
|
|
1611
1822
|
(`[IMMUTABLE_UPDATE_IN_STORE]`, `[UNSTABLE_LIST_IDENTITY]`, `[EFFECT_*]`)
|
|
1612
|
-
checked via
|
|
1613
|
-
|
|
1614
|
-
|
|
1823
|
+
checked via the [development loop](#development-loop-self-diagnose-before-finishing)
|
|
1824
|
+
(`/__solid/diagnostics` `begin` → interact → `whyDidRun` / `costs` → `end`, or an
|
|
1825
|
+
`isDev`-guarded `attribution.enable({ log: false })` + `why` / `costs` /
|
|
1826
|
+
`feedback`) whenever the change touched stores, lists, async, actions, or
|
|
1827
|
+
effects. Name interrogated scopes (`{ name: "total" }`). `observe: true` is a
|
|
1828
|
+
production opt-in, not a dev step.
|
|
1615
1829
|
- [ ] If the project has a router: `createRouter({ routes })`, not JSX `<Route>` / `<A>`.
|
|
1616
1830
|
One instance, no nested `<Router>`. Navigate with `useNavigate` / `<a href>`, not
|
|
1617
1831
|
`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.14.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",
|