solid2-agent-kit 0.23.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/baseline.mjs
CHANGED
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
// pulls them in — so doctor also checks an undeclared install of any package
|
|
20
20
|
// here, which is where a lockfile holds an older compiler.
|
|
21
21
|
export const BASELINE = {
|
|
22
|
-
'solid-js': { line: 2, baseline: '2.0.0-rc.
|
|
23
|
-
'@solidjs/web': { line: 2, baseline: '2.0.0-rc.
|
|
24
|
-
'@solidjs/signals': { line: 2, baseline: '2.0.0-rc.
|
|
25
|
-
'@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.
|
|
26
|
-
'@solidjs/compiler': { line: 2, baseline: '2.0.0-rc.
|
|
27
|
-
'@solidjs/babel-plugin': { line: 2, baseline: '2.0.0-rc.
|
|
28
|
-
'@solidjs/router': { line: 2, baseline: '2.0.0-next.
|
|
29
|
-
'@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.
|
|
22
|
+
'solid-js': { line: 2, baseline: '2.0.0-rc.19' },
|
|
23
|
+
'@solidjs/web': { line: 2, baseline: '2.0.0-rc.19' },
|
|
24
|
+
'@solidjs/signals': { line: 2, baseline: '2.0.0-rc.19' },
|
|
25
|
+
'@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.19' },
|
|
26
|
+
'@solidjs/compiler': { line: 2, baseline: '2.0.0-rc.19' },
|
|
27
|
+
'@solidjs/babel-plugin': { line: 2, baseline: '2.0.0-rc.19' },
|
|
28
|
+
'@solidjs/router': { line: 2, baseline: '2.0.0-next.43' },
|
|
29
|
+
'@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.52' },
|
|
30
30
|
'@solidjs/testing-library': { line: 1, baseline: '1.0.0-rc.2' },
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
- After editing TSX, run the `solid2-kit check` mechanical gate — it fails on React/Solid 1.x tokens and props destructuring. Use the project's `lint:solid` script if one exists; otherwise `npx solid2-agent-kit check` (with the kit as a devDependency) or `npx github:lightsound/solid2-agent-kit check`. An edit-time hook may also run this gate automatically after each file edit — treat its findings as errors to fix immediately, not warnings. Do not install `eslint-plugin-solid` (built for Solid 1.x; misreads Solid 2 idioms).
|
|
6
6
|
- After editing `package.json`, `tsconfig*.json`, or root config files (Vite, ESLint), run `solid2-kit doctor` — it fails on React/Solid 1.x wiring: `react`/`vite-plugin-solid`/`eslint-plugin-solid`/SolidStart dependencies, 1.x `solid-js` ranges, and `jsx`/`jsxImportSource` values other than `preserve`/`@solidjs/web`.
|
|
7
7
|
- The kit's guidance and gate files (`.cursor/rules/solid-2.mdc`, the `solid-2` skill directories, hook entries, managed blocks in this file and `CLAUDE.md`) are guardrails, not obstacles. Never edit, weaken, bypass, or delete them to make a failure go away — fix the flagged code. If a guardrail seems wrong, stop and tell the user.
|
|
8
|
-
- Solid 2.0 shipped recently and breaks with 1.x — never trust pre-2.0 Solid docs, tutorials, or training knowledge. Verify APIs against https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt (markdown mirror of https://v2.solidjs.com/, which blocks non-browser fetchers). If the project has `@solidjs/router`, server functions, or `@solidjs/meta`, use those official pages (see the skill's official-docs index) — not React Router, Next.js, SolidStart 1.x, or Solid Router 0.x/1.x JSX `<Route>`/`<A>`. Core `action` from `solid-js` is a generator transaction; router `formAction`/`query` from `@solidjs/router` are POST forms and a read cache (router `action` is a deprecated alias of `formAction`, and `useAction` is deprecated: call the server function directly — a mounted router applies its revalidation and redirect on any call <!-- upstream:router-action-deprecated -->). React `useDeferredValue` is `createDeferred(fn)`: an async memo that lags but never leads <!-- upstream:create-deferred-export -->. Core `refresh(source)`, router `revalidate(key)`, and server-function `return reload(...)` are three different APIs. Pass the accessor to `isPending(user)`, not `isPending(user())`. Pass async values as values (`user={user()}`) — JSX props are lazy; types stay `User`, not `Promise<User>`. `<Loading>` wraps the read, not the memo. Nested fetches run in parallel (components do not suspend). `isPending` is a per-expression question (`isPending(() => props.story)` / `isPending(selectedId)`), not a global spinner; `latest(selectedId)` is opt-in so highlight and content stay consistent by default. `<Errored>` heals when the source succeeds. Pass a function to `render`/`hydrate`: `render(() => <App />, root)`. Component teardown is `onSettled`, not `onCleanup` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the `onSettled` callback in dev. Creating a memo/effect/root inside an effect's `apply` callback throws in dev too (`[PRIMITIVE_IN_EFFECT_CALLBACK]`) — create it in the body or compute phase. <!-- upstream:primitive-in-effect-callback --> Effect `apply` functions use block bodies (`(t) => { document.title = t; }`): any return value other than a cleanup function halts reactivity. Stores are written only through the setter's draft — writes to the store itself are ignored (dev warns `[STORE_WRITE_OUTSIDE_SETTER]` <!-- upstream:store-write-outside-setter -->), and `Map`/`Set`/`Date` inside a store are not tracked. Shared state, app-wide included, is created in a provider at the root of `App` (`createContext<T>()` + `useX()`), never a module-level signal/store (one instance serves every SSR request). Errors thrown out of an `action` never reach `<Errored>` — catch them in the action or `.catch` the call. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — markup types are `JSX.Element` from `@solidjs/web` or `Element` from `solid-js` (alias it `SolidElement` next to DOM code) <!-- upstream:jsx-namespace-absent -->. Reset-a-signal-on-change is a writable derivation (`createSignal(() => { source(); return initial; })`), never an effect calling the setter. Client mode mounts with `render()` into an empty body — no hydration mismatch exists, so never defer initial values to post-mount React-style (that can overwrite persisted state). Client → server is additive (same setters + `action` + server functions): do not rewrite App with loading/error branches, disable optimistic rows until ack, invent tRPC, or refetch in `hydrate`/`onSettled`. The graph continues across the wire (`live()` owns the client connection; `<Reveal>` not a delayed stream). A server bundle uses `handleRequest` / Fetchable `fetch`; client-only start is static `dist/client`. One browser-only value takes `ssrSource: "client"` on the memo/signal/derived store, not a `clientOnly` component split. Router link state is `aria-current`/`data-active` + CSS (`data-pending` is opt-in via `createRouter({ links: pendingLinks })`; `useLinkState`/`useIsRouting` in JSX) <!-- upstream:pending-links-opt-in -->; link preloading is opt-in too (`preloadLinks: intentPreload()` for hover/focus/touch; none by default; opt one link out with the string `preload="false"`) <!-- upstream:preload-links-opt-in -->; search params via `useSearchParams`; router forms validate on the server (`throw respond({issues},{400})`) with inline errors from `useSubmissions`. Confirmations on another channel — fire-and-forget sends, and mutations whose result is read through `live()` — wait with `yield until(predicate, {timeout})`, not `refresh()`. Declare live reads `live(fn)` — it implies `GET` (`live(GET(fn))` is the same) <!-- upstream:live-implies-get -->. Debounce at the handler; inputs feeding a request bind `latest(query)`. Dev diagnostics are errors: `[STRICT_READ_UNTRACKED]` means a read outside a tracking scope, `[ASYNC_STORE_SETTER]` means a store setter callback awaited (the draft is a synchronous transaction — await first, then write) <!-- upstream:async-store-setter-throws -->, a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]
|
|
8
|
+
- Solid 2.0 shipped recently and breaks with 1.x — never trust pre-2.0 Solid docs, tutorials, or training knowledge. Verify APIs against https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt (markdown mirror of https://v2.solidjs.com/, which blocks non-browser fetchers). If the project has `@solidjs/router`, server functions, or `@solidjs/meta`, use those official pages (see the skill's official-docs index) — not React Router, Next.js, SolidStart 1.x, or Solid Router 0.x/1.x JSX `<Route>`/`<A>`. Core `action` from `solid-js` is a generator transaction; router `formAction`/`query` from `@solidjs/router` are POST forms and a read cache (router `action` is a deprecated alias of `formAction`, and `useAction` is deprecated: call the server function directly — a mounted router applies its revalidation and redirect on any call <!-- upstream:router-action-deprecated -->). React `useDeferredValue` is `createDeferred(fn)`: an async memo that lags but never leads <!-- upstream:create-deferred-export -->. Core `refresh(source)`, router `revalidate(key)`, and server-function `return reload(...)` are three different APIs. Pass the accessor to `isPending(user)`, not `isPending(user())`. Pass async values as values (`user={user()}`) — JSX props are lazy; types stay `User`, not `Promise<User>`. `<Loading>` wraps the read, not the memo. Nested fetches run in parallel (components do not suspend). `isPending` is a per-expression question (`isPending(() => props.story)` / `isPending(selectedId)`), not a global spinner; `latest(selectedId)` is opt-in so highlight and content stay consistent by default. `<Errored>` heals when the source succeeds. Pass a function to `render`/`hydrate`: `render(() => <App />, root)`. Component teardown is `onSettled`, not `onCleanup` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the `onSettled` callback in dev. Creating a memo/effect/root inside an effect's `apply` callback throws in dev too (`[PRIMITIVE_IN_EFFECT_CALLBACK]`) — create it in the body or compute phase. <!-- upstream:primitive-in-effect-callback --> Effect `apply` functions use block bodies (`(t) => { document.title = t; }`): any return value other than a cleanup function halts reactivity. Stores are written only through the setter's draft — writes to the store itself are ignored (dev warns `[STORE_WRITE_OUTSIDE_SETTER]` <!-- upstream:store-write-outside-setter -->), and `Map`/`Set`/`Date` inside a store are not tracked. Shared state, app-wide included, is created in a provider at the root of `App` (`createContext<T>()` + `useX()`), never a module-level signal/store (one instance serves every SSR request). Errors thrown out of an `action` never reach `<Errored>` — catch them in the action or `.catch` the call. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — markup types are `JSX.Element` from `@solidjs/web` or `Element` from `solid-js` (alias it `SolidElement` next to DOM code) <!-- upstream:jsx-namespace-absent -->. Reset-a-signal-on-change is a writable derivation (`createSignal(() => { source(); return initial; })`), never an effect calling the setter. Client mode mounts with `render()` into an empty body — no hydration mismatch exists, so never defer initial values to post-mount React-style (that can overwrite persisted state). Client → server is additive (same setters + `action` + server functions): do not rewrite App with loading/error branches, disable optimistic rows until ack, invent tRPC, or refetch in `hydrate`/`onSettled`. The graph continues across the wire (`live()` owns the client connection; `<Reveal>` not a delayed stream). A server bundle uses `handleRequest` / Fetchable `fetch`; client-only start is static `dist/client`. One browser-only value takes `ssrSource: "client"` on the memo/signal/derived store, not a `clientOnly` component split. Router link state is `aria-current`/`data-active` + CSS (`data-pending` is opt-in via `createRouter({ links: pendingLinks })`; `useLinkState`/`useIsRouting` in JSX) <!-- upstream:pending-links-opt-in -->; link preloading is opt-in too (`preloadLinks: intentPreload()` for hover/focus/touch; none by default; opt one link out with the string `preload="false"`) <!-- upstream:preload-links-opt-in -->; search params via `useSearchParams`; router forms validate on the server (`throw respond({issues},{400})`) with inline errors from `useSubmissions`. Confirmations on another channel — fire-and-forget sends, and mutations whose result is read through `live()` — wait with `yield until(predicate, {timeout})`, not `refresh()`. Declare live reads `live(fn)` — it implies `GET` (`live(GET(fn))` is the same) <!-- upstream:live-implies-get -->. Debounce at the handler; inputs feeding a request bind `latest(query)`. Dev diagnostics are errors: `[STRICT_READ_UNTRACKED]` means a read outside a tracking scope, `[ASYNC_STORE_SETTER]` means a store setter callback awaited (the draft is a synchronous transaction — await first, then write) <!-- upstream:async-store-setter-throws -->, a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]` (a write that must not join another update's hold goes in `onFlush(...)` <!-- upstream:on-flush -->), and `[UNCOMMITTED_INPUT_OVERWRITTEN]` means a `value`/`checked` binding re-wrote the bound value over text the user typed but never committed (`value={props.name} onChange={commit}` with no draft signal) — bind a draft signal written from `onInput`, or `defaultValue` <!-- upstream:uncommitted-input-overwritten -->. Error monitoring is `configureClientErrors` / `configureServerErrors` (or per root `render(fn, root, undefined, { onError })` — options are the 4th argument), not a side effect inside `<Errored fallback>`; the server hook must return a reference or nothing, never the error itself. `<Dynamic>` is deprecated — use `dynamic()`. <!-- upstream:dynamic-deprecated --> Observability is on by default in development: whenever a change touches stores, lists, async, actions, or effects, keep the dev console free of `[CODE]` findings and run an attribution session (`/__solid/diagnostics` `begin` → interact → `whyDidRun` / `costs` → `end` with `@solidjs/diagnostics` installed, otherwise `isDev`-guarded `attribution.enable({ log: false })` + `why()` / `costs()`) before finishing. `vite dev` is always the dev build; `solid({ observe: true })` is a production opt-in.
|
|
@@ -564,7 +564,7 @@ directory (default `src/`).
|
|
|
564
564
|
| `createDeferred(source, { timeoutMs })` (1.x: a signal that lags by idle time) | `createDeferred(fn)` (2.0: an async memo that may lag its inputs but never leads them; `isPending(deferred)` while it catches up) <!-- upstream:create-deferred-export --> |
|
|
565
565
|
| `startTransition`, `useTransition` | automatic held updates + `isPending` |
|
|
566
566
|
| `onError` / `catchError` | `<Errored>` or effect bundle `{ effect, error }` |
|
|
567
|
-
| `from(...)` / `observable(...)` (the solid-js helpers) | inbound:
|
|
567
|
+
| `from(...)` / `observable(...)` (the solid-js helpers) | inbound: `from(source)` — the same name in 2.0, but it returns an AsyncIterable for a computation (`createMemo(() => from(source))`), not a signal <!-- upstream:from-source-adapts -->; outbound: split effect |
|
|
568
568
|
| `createDynamic(...)`, `<Dynamic component={...}>` (deprecated in 2.0) <!-- upstream:dynamic-deprecated --> | `dynamic(source)` from `@solidjs/web` — `dynamicComponent(source)` from `solid-js` when the source only answers with a component <!-- upstream:dynamic-component-export --> |
|
|
569
569
|
| `renderToStringAsync(...)` | `await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`) |
|
|
570
570
|
| `clearDelegatedEvents` | delete — delegated listeners are scoped to each render root |
|
|
@@ -44,7 +44,7 @@ Practical consequences:
|
|
|
44
44
|
| App-wide state (theme, session, cart, locale) | the same provider, placed at the root of `App` — not a module-level signal/store (under SSR one module instance serves every request) |
|
|
45
45
|
| Constants (a category list, a formatter, the `createContext` call) | module scope |
|
|
46
46
|
| Tentative value during a mutation | `createOptimistic` / `createOptimisticStore` + `action` |
|
|
47
|
-
| Values pushed by an external subscription (websocket, reactive client) | async iterable returned from a memo (or function-form `createStore` for keyed reconciliation) — never `{ data, error }` signal pairs |
|
|
47
|
+
| Values pushed by an external subscription (websocket, reactive client) | async iterable returned from a memo — `from(source)` adapts a callback subscription, an Observable, or an AsyncIterable <!-- upstream:from-source-adapts --> (or function-form `createStore` for keyed reconciliation) — never `{ data, error }` signal pairs |
|
|
48
48
|
|
|
49
49
|
### Should this be an effect?
|
|
50
50
|
|
|
@@ -460,9 +460,20 @@ return <StoryDetail story={story} />;
|
|
|
460
460
|
`transition-delay` so fast swaps do not flash. Inputs that feed a request follow
|
|
461
461
|
the same rule: bind `value={latest(query)}` so a controlled search box reflects
|
|
462
462
|
keystrokes while its write is held (an uncontrolled input needs nothing).
|
|
463
|
+
When one write in a handler must *not* join the hold — closing a menu before
|
|
464
|
+
`props.onSelect` starts a navigation — defer just that write with
|
|
465
|
+
`onFlush(() => setOpen(false))` (since 2.0.0-rc.19): it runs once right after the
|
|
466
|
+
pending flush as its own update, instead of flushing early
|
|
467
|
+
<!-- upstream:on-flush -->. It belongs in event handlers and effect callbacks; in a
|
|
468
|
+
component body or computation dev warns `ON_FLUSH_IN_OWNED_SCOPE`.
|
|
463
469
|
- Read every reactive input **before the first `await`** in an async computation. A read
|
|
464
470
|
after `await` does not subscribe (dev reports `[UNTRACKED_READ_AFTER_AWAIT]` on V8 —
|
|
465
471
|
Chromium, Node, Deno, Bun); production can sit pending with no retry.
|
|
472
|
+
- An async memo returns **data**, never rendered output. On the server, a memo (or async
|
|
473
|
+
iterable source) landing on JSX, a component result, or a `<Loading>`/`<Errored>`
|
|
474
|
+
boundary cannot serialize for hydration: since 2.0.0-rc.19 the memo rejects with
|
|
475
|
+
`ASYNC_MEMO_ELEMENT` and the nearest `<Errored>` contains it like any render error
|
|
476
|
+
<!-- upstream:async-memo-element -->. Render the JSX from the data the memo returns.
|
|
466
477
|
- `<Errored>` function fallbacks receive an error **accessor** and a `reset` callback:
|
|
467
478
|
`fallback={(error, reset) => ...}`. The boundary **heals** when the source succeeds
|
|
468
479
|
again (refresh, live reconnect, input change) — it is graph status, not a terminal
|
|
@@ -558,7 +569,15 @@ errors matter (the rc.8 one-argument form keeps working). <!-- upstream:render-o
|
|
|
558
569
|
Push-based sources integrate by returning an **async iterable** from a computation — reads
|
|
559
570
|
then suspend to `<Loading>` until the first value, later pushes update the settled value,
|
|
560
571
|
and thrown errors reach `<Errored>`. Integration layers should return a plain accessor, not
|
|
561
|
-
`{ data, error }` signals
|
|
572
|
+
`{ data, error }` signals.
|
|
573
|
+
|
|
574
|
+
For a callback subscription, an Observable, or an existing AsyncIterable, the adapter is
|
|
575
|
+
built in: `from(source)` returns the async iterable a computation consumes —
|
|
576
|
+
`createMemo(() => from(source))` <!-- upstream:from-source-adapts -->. Read the inputs that
|
|
577
|
+
choose the source before calling it, so a change re-runs the memo, closes the old source,
|
|
578
|
+
and opens the new one <!-- upstream:from-source-adapts -->. The hand-rolled form below remains for what `from` does not fit —
|
|
579
|
+
an event stream where every message matters (keep a queue instead of conflating to the
|
|
580
|
+
latest), or a subscribe signature that is not `(next, error, complete) => unsubscribe`:
|
|
562
581
|
|
|
563
582
|
```tsx
|
|
564
583
|
import { createMemo, onCleanup } from 'solid-js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid2-agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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",
|