solid2-agent-kit 0.22.0 → 0.22.1

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.
@@ -772,9 +772,9 @@ const CHECKS = [
772
772
  // 2.0.0-next.38 and preloads nothing by default: `true` (the old on switch)
773
773
  // preloads nothing and only warns in dev, and `false` is a no-op.
774
774
  id: 'preload-links-boolean',
775
- pattern: /\bpreloadLinks\s*:\s*(?:true|false)\b/g,
775
+ pattern: /(?<![\w$])["']?preloadLinks["']?\s*:\s*(?:true|false)\b/g,
776
776
  message:
777
- '`preloadLinks` takes link preload strategies, not a boolean, and preloads nothing by default. Hover/focus warming is preloadLinks: intentPreload() (from "@solidjs/router"); to disable it, omit the option.',
777
+ '`preloadLinks` takes link preload strategies, not a boolean, and preloads nothing by default. Hover/focus warming is preloadLinks: intentPreload() (from "@solidjs/router"); for none, omit the option.',
778
778
  },
779
779
  {
780
780
  id: 'history-nav',
@@ -5,4 +5,4 @@
5
5
  - After editing TSX, run the `solid2-kit check` mechanical gate — it fails on React/Solid 1.x tokens and props destructuring. Use the project's `lint:solid` script if one exists; otherwise `npx solid2-agent-kit check` (with the kit as a devDependency) or `npx github:lightsound/solid2-agent-kit check`. An edit-time hook may also run this gate automatically after each file edit — treat its findings as errors to fix immediately, not warnings. Do not install `eslint-plugin-solid` (built for Solid 1.x; misreads Solid 2 idioms).
6
6
  - After editing `package.json`, `tsconfig*.json`, or root config files (Vite, ESLint), run `solid2-kit doctor` — it fails on React/Solid 1.x wiring: `react`/`vite-plugin-solid`/`eslint-plugin-solid`/SolidStart dependencies, 1.x `solid-js` ranges, and `jsx`/`jsxImportSource` values other than `preserve`/`@solidjs/web`.
7
7
  - The kit's guidance and gate files (`.cursor/rules/solid-2.mdc`, the `solid-2` skill directories, hook entries, managed blocks in this file and `CLAUDE.md`) are guardrails, not obstacles. Never edit, weaken, bypass, or delete them to make a failure go away — fix the flagged code. If a guardrail seems wrong, stop and tell the user.
8
- - Solid 2.0 shipped recently and breaks with 1.x — never trust pre-2.0 Solid docs, tutorials, or training knowledge. Verify APIs against https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt (markdown mirror of https://v2.solidjs.com/, which blocks non-browser fetchers). If the project has `@solidjs/router`, server functions, or `@solidjs/meta`, use those official pages (see the skill's official-docs index) — not React Router, Next.js, SolidStart 1.x, or Solid Router 0.x/1.x JSX `<Route>`/`<A>`. Core `action` from `solid-js` is a generator transaction; router `action`/`query` from `@solidjs/router` are POST forms and a read cache. Core `refresh(source)`, router `revalidate(key)`, and server-function `return reload(...)` are three different APIs. Pass the accessor to `isPending(user)`, not `isPending(user())`. Pass async values as values (`user={user()}`) — JSX props are lazy; types stay `User`, not `Promise<User>`. `<Loading>` wraps the read, not the memo. Nested fetches run in parallel (components do not suspend). `isPending` is a per-expression question (`isPending(() => props.story)` / `isPending(selectedId)`), not a global spinner; `latest(selectedId)` is opt-in so highlight and content stay consistent by default. `<Errored>` heals when the source succeeds. Pass a function to `render`/`hydrate`: `render(() => <App />, root)`. Component teardown is `onSettled`, not `onCleanup` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the `onSettled` callback in dev. 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 silently ignored, and `Map`/`Set`/`Date` inside a store are not tracked. Shared state, app-wide included, is created in a provider at the root of `App` (`createContext<T>()` + `useX()`), never a module-level signal/store (one instance serves every SSR request). Errors thrown out of an `action` never reach `<Errored>` — catch them in the action or `.catch` the call. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — markup types are `JSX.Element` from `@solidjs/web` or `Element` from `solid-js` (alias it `SolidElement` next to DOM code) <!-- upstream:jsx-namespace-absent -->. Reset-a-signal-on-change is a writable derivation (`createSignal(() => { source(); return initial; })`), never an effect calling the setter. Client mode mounts with `render()` into an empty body — no hydration mismatch exists, so never defer initial values to post-mount React-style (that can overwrite persisted state). Client → server is additive (same setters + `action` + server functions): do not rewrite App with loading/error branches, disable optimistic rows until ack, invent tRPC, or refetch in `hydrate`/`onSettled`. The graph continues across the wire (`live()` owns the client connection; `<Reveal>` not a delayed stream). A server bundle uses `handleRequest` / Fetchable `fetch`; client-only start is static `dist/client`. One browser-only value takes `ssrSource: "client"` on the memo/signal/derived store, not a `clientOnly` component split. Router link state is `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; none by default) <!-- 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(GET(fn))` (`live()` outermost). Debounce at the handler; inputs feeding a request bind `latest(query)`. Dev diagnostics are errors: `[STRICT_READ_UNTRACKED]` means a read outside a tracking scope, `[ASYNC_STORE_SETTER]` means a store setter callback awaited (the draft is a synchronous transaction — await first, then write) <!-- upstream:async-store-setter-throws -->, and a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]`. Error monitoring is `configureClientErrors` / `configureServerErrors` (or per root `render(fn, root, undefined, { onError })` — options are the 4th argument), not a side effect inside `<Errored fallback>`; the server hook must return a reference or nothing, never the error itself. `<Dynamic>` is deprecated — use `dynamic()`. <!-- upstream:dynamic-deprecated --> Observability is on by default in development: whenever a change touches stores, lists, async, actions, or effects, keep the dev console free of `[CODE]` findings and run an attribution session (`/__solid/diagnostics` `begin` → interact → `whyDidRun` / `costs` → `end` with `@solidjs/diagnostics` installed, otherwise `isDev`-guarded `attribution.enable({ log: false })` + `why()` / `costs()`) before finishing. `vite dev` is always the dev build; `solid({ observe: true })` is a production opt-in.
8
+ - Solid 2.0 shipped recently and breaks with 1.x — never trust pre-2.0 Solid docs, tutorials, or training knowledge. Verify APIs against https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt (markdown mirror of https://v2.solidjs.com/, which blocks non-browser fetchers). If the project has `@solidjs/router`, server functions, or `@solidjs/meta`, use those official pages (see the skill's official-docs index) — not React Router, Next.js, SolidStart 1.x, or Solid Router 0.x/1.x JSX `<Route>`/`<A>`. Core `action` from `solid-js` is a generator transaction; router `action`/`query` from `@solidjs/router` are POST forms and a read cache. Core `refresh(source)`, router `revalidate(key)`, and server-function `return reload(...)` are three different APIs. Pass the accessor to `isPending(user)`, not `isPending(user())`. Pass async values as values (`user={user()}`) — JSX props are lazy; types stay `User`, not `Promise<User>`. `<Loading>` wraps the read, not the memo. Nested fetches run in parallel (components do not suspend). `isPending` is a per-expression question (`isPending(() => props.story)` / `isPending(selectedId)`), not a global spinner; `latest(selectedId)` is opt-in so highlight and content stay consistent by default. `<Errored>` heals when the source succeeds. Pass a function to `render`/`hydrate`: `render(() => <App />, root)`. Component teardown is `onSettled`, not `onCleanup` — return the cleanup; `onCleanup`, memo/effect creation, and `flush()` throw inside the `onSettled` callback in dev. 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 silently ignored, and `Map`/`Set`/`Date` inside a store are not tracked. Shared state, app-wide included, is created in a provider at the root of `App` (`createContext<T>()` + `useX()`), never a module-level signal/store (one instance serves every SSR request). Errors thrown out of an `action` never reach `<Errored>` — catch them in the action or `.catch` the call. One router instance; in-app navigation is `useNavigate` / `<a href>`, not `window.location`. Types: `solid-js` exports no `JSX` namespace — markup types are `JSX.Element` from `@solidjs/web` or `Element` from `solid-js` (alias it `SolidElement` next to DOM code) <!-- upstream:jsx-namespace-absent -->. Reset-a-signal-on-change is a writable derivation (`createSignal(() => { source(); return initial; })`), never an effect calling the setter. Client mode mounts with `render()` into an empty body — no hydration mismatch exists, so never defer initial values to post-mount React-style (that can overwrite persisted state). Client → server is additive (same setters + `action` + server functions): do not rewrite App with loading/error branches, disable optimistic rows until ack, invent tRPC, or refetch in `hydrate`/`onSettled`. The graph continues across the wire (`live()` owns the client connection; `<Reveal>` not a delayed stream). A server bundle uses `handleRequest` / Fetchable `fetch`; client-only start is static `dist/client`. One browser-only value takes `ssrSource: "client"` on the memo/signal/derived store, not a `clientOnly` component split. Router link state is `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(GET(fn))` (`live()` outermost). Debounce at the handler; inputs feeding a request bind `latest(query)`. Dev diagnostics are errors: `[STRICT_READ_UNTRACKED]` means a read outside a tracking scope, `[ASYNC_STORE_SETTER]` means a store setter callback awaited (the draft is a synchronous transaction — await first, then write) <!-- upstream:async-store-setter-throws -->, and a held write needs `isPending`/`latest`/optimistic/`affects` feedback or dev + attribution reports `[SILENT_HOLD]`. Error monitoring is `configureClientErrors` / `configureServerErrors` (or per root `render(fn, root, undefined, { onError })` — options are the 4th argument), not a side effect inside `<Errored fallback>`; the server hook must return a reference or nothing, never the error itself. `<Dynamic>` is deprecated — use `dynamic()`. <!-- upstream:dynamic-deprecated --> Observability is on by default in development: whenever a change touches stores, lists, async, actions, or effects, keep the dev console free of `[CODE]` findings and run an attribution session (`/__solid/diagnostics` `begin` → interact → `whyDidRun` / `costs` → `end` with `@solidjs/diagnostics` installed, otherwise `isDev`-guarded `attribution.enable({ log: false })` + `why()` / `costs()`) before finishing. `vite dev` is always the dev build; `solid({ observe: true })` is a production opt-in.
@@ -399,7 +399,9 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
399
399
  `preload` makes it a Promise), so return a value only for data that stays fixed while
400
400
  the route is matched. Link warming is opt-in: route code and `preload` run ahead of
401
401
  the click only with a strategy — `createRouter({ routes, preloadLinks: intentPreload() })`
402
- for hover/focus (none by default; a boolean `preloadLinks` only warns in dev).
402
+ for hover/focus/touch (none by default; a boolean `preloadLinks` is TS2322 and preloads
403
+ nothing). Opt a link out with the string `preload="false"` — `preload={false}` removes
404
+ the attribute and opts nothing out.
403
405
  <!-- upstream:preload-links-opt-in --> One router instance —
404
406
  Solid Router does not support nested `<Router>` / nested `createRouter`.
405
407
  In-app navigation is `useNavigate` or `<a href={Router.paths...}>`, not
@@ -459,7 +461,9 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
459
461
  reads `PORT` / `HOST`; its exported `listener` mounts in an existing Express /
460
462
  Fastify server) — `server.js` is only the handler module. Client-only start mode is static `dist/client` —
461
463
  there is no handler to wrap. Request middleware is
462
- `start: { middleware: "./src/middleware.ts" }`, not Express `app.use`. Do not return
464
+ `start: { middleware: "./src/middleware.ts" }`, not Express `app.use`: default export
465
+ `(event, next) => Response` (`StartMiddleware`), request at `event.request`, `next()`
466
+ with no arguments <!-- upstream:start-middleware-event -->. Do not return
463
467
  components from `"use server"` unless the project already enabled the
464
468
  experimental `serverFunctions.components` flag. Server-function arguments are JSON:
465
469
  send `Date` / `Map` / `Set` as ISO strings / arrays (they throw otherwise; one `File` /
@@ -241,7 +241,10 @@ exports must use `{ export: "About" }` — a `.then((m) => ({ default: m.About }
241
241
  wrapper hydrates incorrectly (the name is not a call-site literal). A module that is
242
242
  not a component (a route table, a parser) takes `lazyModule(() => import("./x"))` from
243
243
  `solid-js`: call it (or `.preload()`) for the cached promise, `.peek()` for the module
244
- once it has landed; the server preloads it before hydration by module id.
244
+ once it has landed (`undefined` before that, and on the client after a hydration
245
+ miss). A server render that reads `.peek()` registers the module by id so the client
246
+ has it before hydration, holding the shell while it loads; `.preload()` on the server
247
+ only hints assets.
245
248
 
246
249
  ### Dynamic component: `dynamic()` (canonical)
247
250
 
@@ -1141,8 +1144,13 @@ the test — `createRouter({ routes, history: memoryHistory("/orders/42") })`
1141
1144
  Testing Library's `render(…, { location })` is typed but not implemented (1.0.0-beta.3). <!-- upstream:testing-library-location -->
1142
1145
 
1143
1146
  An end-to-end test that must wait for hydration of an SSR page waits on a marker set from
1144
- `onHydrationEnd` (`solid-js`, `@experimental`): register it *after* `hydrate()`, e.g.
1145
- `onHydrationEnd(() => { document.documentElement.dataset.hydrated = ""; })`. It fires when
1147
+ `onHydrationEnd` (`solid-js`, `@experimental`), registered while hydration is running:
1148
+ after `hydrate()` in an authored entry, or — under `start: true`, where the plugin
1149
+ generates the entry and calls `hydrate()` after importing `App` — from the `App`
1150
+ component body (`if (!isServer) onHydrationEnd(() => { document.documentElement.dataset.hydrated = ""; });`;
1151
+ the server version also runs the callback, where `document` does not exist). Registered
1152
+ at `App.tsx` module scope it runs before hydration starts, in the next microtask, ahead
1153
+ of the streamed boundaries it was meant to wait for. It fires when
1146
1154
  the hydration phase ends — including when a module preload failed and the markup was left
1147
1155
  non-interactive — so it is a test-tooling marker, never a branch for what the app renders
1148
1156
  (`isHydrating()` / `onSettled` own that). Outside hydration it runs in the next microtask.
@@ -1662,8 +1670,11 @@ and touch. Without `preloadLinks` nothing preloads ahead of the click. The other
1662
1670
  strategies, all from `@solidjs/router` and combinable as an array: `tapPreload()`
1663
1671
  (pointerdown), `viewportPreload()` and `eagerPreload()` (code only by default, for links
1664
1672
  marked `preload="viewport"` / `preload="eager"`, or every link with `{ all: true }`).
1665
- `preload="false"` on an anchor opts that link out; `usePreloadRoute()` warms a route by
1666
- hand. A boolean `preloadLinks` (the old on/off switch) only warns in dev and preloads
1673
+ `preload="false"` on an anchor opts that link out of every strategy — the **string**:
1674
+ the types also accept `preload={false}`, but a `false` attribute is removed, so that link
1675
+ is still preloaded. `usePreloadRoute()` warms a route by hand (code only;
1676
+ `{ preloadData: true }` also runs its `preload`). A boolean `preloadLinks` (the old
1677
+ on/off switch) is a type error, and at runtime it only warns in dev and preloads
1667
1678
  nothing. <!-- upstream:preload-links-opt-in --> Intent values are `"initial"` /
1668
1679
  `"navigate"` / `"native"` / `"preload"`. One router per app.
1669
1680
 
@@ -2008,8 +2019,13 @@ existing Express / Fastify app, mount the `listener` that file exports — do no
2008
2019
  hand-write a Node↔web bridge (`node dist/server/server.js` starts nothing: it is the
2009
2020
  handler module). Client-only `start: true` is still static `dist/client`
2010
2021
  — there is no handler to wrap. Request middleware is the Vite option
2011
- `start: { middleware: "./src/middleware.ts" }` (web `Request` + `next`), not
2012
- Express `app.use`. Server functions that return components
2022
+ `start: { middleware: "./src/middleware.ts" }`, not Express `app.use`: its default
2023
+ export is `(event, next) => Response` (type `StartMiddleware` from
2024
+ `@solidjs/vite-plugin`), or an array of them. The request is `event.request`; `next()`
2025
+ takes no arguments — assign `event.request` first to substitute it, and set per-request
2026
+ render inputs (`event.nonce`, `event.renderMode`) before calling it. A `(request, next)`
2027
+ middleware written from older examples receives the event as `request`.
2028
+ <!-- upstream:start-middleware-event --> Server functions that return components
2013
2029
  (`serverFunctions: { components: true }`) are experimental preview — do not use
2014
2030
  them unless the project already has that flag on.
2015
2031
 
@@ -2131,7 +2147,8 @@ how to reuse. Prefer the form on the right.
2131
2147
  | `<form method="get" action={update.url}>` for a mutation | GET forms only for idempotent search; mutations are POST |
2132
2148
  | `window.location.href = ...` / `history.pushState` | `useNavigate()` or `<a href={Router.paths...}>` |
2133
2149
  | `class={{ active: location.pathname === "/about" }}` hand-rolled link state | CSS on `aria-current` / `data-active` (+ `data-pending` via `links: pendingLinks`); `useLinkState` in JSX, `useIsRouting()` for progress bars <!-- upstream:pending-links-opt-in --> |
2134
- | `createRouter({ routes, preloadLinks: true })` (or `false`) | `preloadLinks: intentPreload()` for hover/focus warming; omit the option for none — it takes strategies and a boolean preloads nothing <!-- upstream:preload-links-opt-in --> |
2150
+ | `createRouter({ routes, preloadLinks: true })` (or `false`) | `preloadLinks: intentPreload()` for hover/focus/touch warming; omit the option for none — it takes strategies and a boolean preloads nothing <!-- upstream:preload-links-opt-in --> |
2151
+ | `<a href={...} preload={false}>` to skip warming one link | `preload="false"` (the string) — a `false` attribute is removed, so the link is still preloaded <!-- upstream:preload-links-opt-in --> |
2135
2152
  | `new URLSearchParams(location.search)` hand-rolled query parsing | `useSearchParams()` (setter merges, no scroll); typed via a route `search` schema + `useSearchParams(Router.paths.search)` |
2136
2153
  | `Router.paths.about()` / string-concatenated URLs | static nodes are properties (`Router.paths.about`); calls bind params, then search object and hash |
2137
2154
  | `httpStatus(404)` in a click handler | call it bare in the component / error-fallback body (a scope declaration, not a mutation) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid2-agent-kit",
3
- "version": "0.22.0",
3
+ "version": "0.22.1",
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",