solid2-agent-kit 0.12.0 → 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.
@@ -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 ------------------------------------------------------------------
@@ -57,7 +57,14 @@ Steps:
57
57
  attribution-only store/list/effect costs) left unaddressed;
58
58
  - context values passed as snapshots instead of accessors/setters/stores;
59
59
  - components with conditional/early returns on reactive values.
60
- 5. Verify any API you are not certain about against the official docs mirror
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
61
68
  (`https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt`); never trust Solid 1.x or
62
69
  React memory.
63
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, `[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>`. `<Dynamic>` is deprecated — use `dynamic()`.
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.
@@ -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.
@@ -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); its return value, if any, is the
433
- // sanitized value the client receives instead of the original error
434
- configureServerErrors({ onError: (error, { kind, handling }) => report(error, { kind, handling }) });
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` /
@@ -850,8 +870,20 @@ an agent sneaks in fails the suite instead of scrolling by.
850
870
 
851
871
  ### Dev diagnostics and attribution
852
872
 
853
- Diagnostics come in two layers, both dev-build only (production strips them and `DEV`
854
- is `undefined` there). Always-on findings include misplaced reads
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
855
887
  (`[STRICT_READ_UNTRACKED]`, `[PENDING_ASYNC_UNTRACKED_READ]`), misplaced writes
856
888
  (`[REACTIVE_WRITE_IN_OWNED_SCOPE]` — since rc.9 also for store setters called in a
857
889
  component or root body, `[ASYNC_STORE_SETTER]` for a store setter callback that returns
@@ -873,8 +905,11 @@ Cost and responsiveness findings need the opt-in attribution engine, which recor
873
905
  every scope that re-ran, what changed to cause it, and how long it took. Since
874
906
  `solid-js` 2.0.0-rc.8 the engine lives behind its own subpath — `DEV.attribution`
875
907
  no longer exists (it is a type error; `DEV` holds only devtools hooks, graph
876
- traversal, and console reporting), and the low-level hook slot on `OBSERVE` is for
877
- engines and devtools, not app code. Since 2.0.0-rc.9 the folds, queries, and
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
878
913
  formatters are **named exports** of that subpath, not methods of `attribution`
879
914
  (`attribution.why` / `.costs` / `.feedback` / `.subscriptions` / `.format` are type
880
915
  errors); `attribution` keeps `enable`, `disable`, `subscribe`, `markFlight`, and the
@@ -895,11 +930,15 @@ for (const event of why(total)) {
895
930
  (`log: true`); pass `{ log: false }` when you only want the coded findings and plan
896
931
  to query the tables yourself. Name the scopes you intend to interrogate
897
932
  (`createMemo(..., { name: "total" })`) — chains refer to nodes by name, and
898
- anonymous nodes print as `computed` / `effect` / `signal`. In production the import
899
- resolves to an inert same-surface twin whose `enable()` is a no-op, so
900
- with the `isDev` guard the cost is zero and the import can stay in the code. The
901
- split is pay-for-use: importing `costs` or `feedback` is what turns their tables on,
902
- so a records-only consumer (`attribution.subscribe(...)`) ships neither.
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.
903
942
 
904
943
  While attribution is enabled, Solid also warns on its own about over-subscription,
905
944
  chained async waterfalls (`[ASYNC_WATERFALL]`), memos whose fresh-but-equal output
@@ -910,7 +949,10 @@ row-rebuilding lists (`[UNSTABLE_LIST_IDENTITY]` — key by id or reconcile into
910
949
  store), and silent holds (`[SILENT_HOLD]` — a write held on async work with no
911
950
  `isPending()` / `latest()` reader, optimistic value, `affects()` mark, or effect that
912
951
  ran while held; info from 100ms, warning from 200ms, tunable via
913
- `enable({ holds: { infoMs, warnMs } })`). `costs()`, `feedback()` (named imports), and
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
914
956
  `attribution.holds()` rank the session worst-first; `attribution.waterfalls()`,
915
957
  `attribution.navigations()`, and `attribution.interactions()` hold the fact tables
916
958
  behind the verdicts.
@@ -920,6 +962,7 @@ against what it uses.
920
962
  For regression tests, `@solidjs/diagnostics` turns the same channels into assertions:
921
963
 
922
964
  ```ts
965
+ import '@solidjs/diagnostics/vitest'; // registers the matchers (or list it in vitest `setupFiles`)
923
966
  import { captureArtifact } from '@solidjs/diagnostics';
924
967
 
925
968
  const { artifact } = await captureArtifact(() => {
@@ -939,22 +982,113 @@ verification loops: capture until the channel is quiet, scenario budgets as the
939
982
  definition of done, and hold/latency gates. See the debugging-reactivity guide in
940
983
  [references/official-docs.md](references/official-docs.md).
941
984
 
942
- With `@solidjs/diagnostics` in `devDependencies`, the Vite plugin's `diagnostics`
943
- option (auto-on; dev server only) injects the capture bridge and serves
944
- `POST /__solid/diagnostics`, so a live page can be interrogated with curl alone —
945
- no test harness:
946
-
947
- ```sh
948
- curl -X POST localhost:3000/__solid/diagnostics -d '{"method":"begin"}'
949
- # ... interact with the app in the browser ...
950
- curl -X POST localhost:3000/__solid/diagnostics -d '{"method":"whyDidRun","params":{"name":"TodoRow"}}'
951
- curl -X POST localhost:3000/__solid/diagnostics -d '{"method":"costs"}'
952
- curl -X POST localhost:3000/__solid/diagnostics -d '{"method":"feedback"}'
953
- curl -X POST localhost:3000/__solid/diagnostics -d '{"method":"end"}'
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 })] };
954
1048
  ```
955
1049
 
956
- `GET` the endpoint for status. With several open tabs the first responder wins,
957
- so keep one page under test.
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).
958
1092
 
959
1093
  ## Client mode: no server HTML, no hydration reflexes
960
1094
 
@@ -1406,6 +1540,12 @@ how to reuse. Prefer the form on the right.
1406
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` |
1407
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())` |
1408
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 |
1409
1549
  | `import { storePath } from "solid-js"` | gone from `solid-js` (rc.9); write draft setters `setState((d) => { d.user.name = "Grace"; })` |
1410
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 |
1411
1551
  | `useHead({ tag: "meta", ... })` for ordinary page tags | `<Title>` / `<Meta>` / `<Script>` (JSON-LD). `useHead` is the lower-level registry |
@@ -1609,9 +1749,12 @@ always-applied rules installed alongside this skill.
1609
1749
  writes paired with `isPending` / `latest` / optimistic values / `affects()`
1610
1750
  (no `[SILENT_HOLD]`). Attribution-only costs
1611
1751
  (`[IMMUTABLE_UPDATE_IN_STORE]`, `[UNSTABLE_LIST_IDENTITY]`, `[EFFECT_*]`)
1612
- checked via `attribution` + the named folds (`why`, `costs`, `feedback`) from
1613
- `solid-js/attribution` when touching stores, lists, or effects. Name
1614
- interrogated scopes (`{ name: "total" }`).
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.
1615
1758
  - [ ] If the project has a router: `createRouter({ routes })`, not JSX `<Route>` / `<A>`.
1616
1759
  One instance, no nested `<Router>`. Navigate with `useNavigate` / `<a href>`, not
1617
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 (dev builds only)
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.12.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",