solid2-agent-kit 0.21.0 → 0.22.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
@@ -18,14 +18,14 @@
18
18
  // pulls them in — so doctor also checks an undeclared install of any package
19
19
  // here, which is where a lockfile holds an older compiler.
20
20
  export const BASELINE = {
21
- 'solid-js': { line: 2, baseline: '2.0.0-rc.14' },
22
- '@solidjs/web': { line: 2, baseline: '2.0.0-rc.14' },
23
- '@solidjs/signals': { line: 2, baseline: '2.0.0-rc.14' },
24
- '@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.14' },
25
- '@solidjs/compiler': { line: 2, baseline: '2.0.0-rc.14' },
26
- '@solidjs/babel-plugin': { line: 2, baseline: '2.0.0-rc.14' },
27
- '@solidjs/router': { line: 2, baseline: '2.0.0-next.37' },
28
- '@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.47' },
21
+ 'solid-js': { line: 2, baseline: '2.0.0-rc.15' },
22
+ '@solidjs/web': { line: 2, baseline: '2.0.0-rc.15' },
23
+ '@solidjs/signals': { line: 2, baseline: '2.0.0-rc.15' },
24
+ '@solidjs/diagnostics': { line: 2, baseline: '2.0.0-rc.15' },
25
+ '@solidjs/compiler': { line: 2, baseline: '2.0.0-rc.15' },
26
+ '@solidjs/babel-plugin': { line: 2, baseline: '2.0.0-rc.15' },
27
+ '@solidjs/router': { line: 2, baseline: '2.0.0-next.39' },
28
+ '@solidjs/vite-plugin': { line: 3, baseline: '3.0.0-next.50' },
29
29
  '@solidjs/testing-library': { line: 1, baseline: '1.0.0-beta.3' },
30
30
  };
31
31
 
@@ -767,6 +767,15 @@ const CHECKS = [
767
767
  message:
768
768
  'Lowercase `on*` is a plain attribute in Solid 2 (dev warns [LOWERCASE_EVENT_ATTRIBUTE]), not an event binding. Use the camelCase prop (onClick).',
769
769
  },
770
+ {
771
+ // `preloadLinks` takes link preload strategies since @solidjs/router
772
+ // 2.0.0-next.38 and preloads nothing by default: `true` (the old on switch)
773
+ // preloads nothing and only warns in dev, and `false` is a no-op.
774
+ id: 'preload-links-boolean',
775
+ pattern: /\bpreloadLinks\s*:\s*(?:true|false)\b/g,
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.',
778
+ },
770
779
  {
771
780
  id: 'history-nav',
772
781
  pattern: /\b(?:history\.pushState|history\.replaceState|window\.location\.href\s*=)/g,
@@ -71,7 +71,9 @@ Steps:
71
71
  automatic `aria-current` / `data-active` attributes (+ CSS) — `data-pending` is
72
72
  opt-in via `createRouter({ links: pendingLinks })` — or
73
73
  `useLinkState` / `useIsRouting`; hand-rolled query parsing instead of
74
- `useSearchParams`; <!-- upstream:pending-links-opt-in -->
74
+ `useSearchParams`; <!-- upstream:pending-links-opt-in --> route `preload`
75
+ functions written to run on hover in a router without `preloadLinks: intentPreload()`
76
+ (link preloading is opt-in; a boolean `preloadLinks` preloads nothing) <!-- upstream:preload-links-opt-in -->;
75
77
  - search inputs bound to a held value without `latest`, effect-based debounce, fully
76
78
  controlled forms without `name`s, `{ success: false }` result objects instead of
77
79
  thrown failures, or router forms without server-side validation (`throw respond(...)`)
@@ -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 -->; 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; 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.
@@ -397,7 +397,10 @@ Claude Code) for full patterns, decision tables, and official documentation URLs
397
397
  `createMemo(() => getProduct(props.params.id))`. `props.data` is whatever `preload`
398
398
  returned when the route first matched: it does not follow param changes (and an async
399
399
  `preload` makes it a Promise), so return a value only for data that stays fixed while
400
- the route is matched. One router instance —
400
+ the route is matched. Link warming is opt-in: route code and `preload` run ahead of
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).
403
+ <!-- upstream:preload-links-opt-in --> One router instance —
401
404
  Solid Router does not support nested `<Router>` / nested `createRouter`.
402
405
  In-app navigation is `useNavigate` or `<a href={Router.paths...}>`, not
403
406
  `window.location` / `history.pushState`. Link state needs no component code:
@@ -519,7 +522,7 @@ directory (default `src/`).
519
522
  | `startTransition`, `useTransition` | automatic held updates + `isPending` |
520
523
  | `onError` / `catchError` | `<Errored>` or effect bundle `{ effect, error }` |
521
524
  | `from(...)` / `observable(...)` (the solid-js helpers) | inbound: async iterable from a memo; outbound: split effect |
522
- | `createDynamic(...)`, `<Dynamic component={...}>` (deprecated in 2.0) <!-- upstream:dynamic-deprecated --> | `dynamic(source)` from `@solidjs/web` — `dynamicComponent(source)` when the source only answers with a component <!-- upstream:dynamic-component-export --> |
525
+ | `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 --> |
523
526
  | `renderToStringAsync(...)` | `await renderToStream(() => <App />)` (one consumer: `pipe` / `pipeTo` / `readable`) |
524
527
  | `clearDelegatedEvents` | delete — delegated listeners are scoped to each render root |
525
528
  | `vite-plugin-solid` / `jsxImportSource: "solid-js"` | `@solidjs/vite-plugin` / `"jsxImportSource": "@solidjs/web"` |
@@ -69,7 +69,7 @@ Most React `useEffect` code should NOT become `createEffect`:
69
69
  | Inspect, count, or iterate children | `children(() => props.children)` then `.toArray()` |
70
70
  | Code-split a component | `lazy(() => import("./X"))` read under `<Loading>` |
71
71
  | Named export from a lazy module | `lazy(() => import("./pages"), { export: "About" })` |
72
- | Pick a component/tag from reactive state | `dynamic(() => ...)` from `@solidjs/web` (stable identity; `<Dynamic>` is deprecated) <!-- upstream:dynamic-deprecated --> — `dynamicComponent(() => ...)` when the source only answers with a component, never a tag <!-- upstream:dynamic-component-export --> |
72
+ | Pick a component/tag from reactive state | `dynamic(() => ...)` from `@solidjs/web` (stable identity; `<Dynamic>` is deprecated) <!-- upstream:dynamic-deprecated --> — `dynamicComponent(() => ...)` from `solid-js` when the source only answers with a component, never a tag <!-- upstream:dynamic-component-export --> |
73
73
  | Overlay / modal | `<Portal>` — hoist async reads *above* the portal (reads inside start on the client) |
74
74
  | Async value used several layers down | Create the memo high; pass `value={memo()}` through intermediates (they do not wait); put `<Loading>` around the leaf read |
75
75
  | Nested child with its own fetch | Leave it nested — it runs in parallel. Sequential only when the second call needs the first response (`fetchAuthor(story().authorId)`) |
@@ -238,7 +238,10 @@ const About = lazy(() => import('./pages'), { export: 'About' });
238
238
  Never `React.lazy` / `<Suspense>`. The lazy component suspends through
239
239
  `<Loading>` on first render; `.preload()` starts the import early. Named
240
240
  exports must use `{ export: "About" }` — a `.then((m) => ({ default: m.About }))`
241
- wrapper hydrates incorrectly (the name is not a call-site literal).
241
+ wrapper hydrates incorrectly (the name is not a call-site literal). A module that is
242
+ not a component (a route table, a parser) takes `lazyModule(() => import("./x"))` from
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.
242
245
 
243
246
  ### Dynamic component: `dynamic()` (canonical)
244
247
 
@@ -269,7 +272,8 @@ still shipped in 2.0. <!-- upstream:dynamic-deprecated --> Hoist a `dynamic()` p
269
272
  constant tag) instead.
270
273
 
271
274
  When the source only ever answers with a component — never a tag name — use
272
- `dynamicComponent()` instead: same contract and hydration shape minus the tag arm, so
275
+ `dynamicComponent()` from `solid-js` instead (`@solidjs/web` re-exports it): same
276
+ contract and hydration shape minus the tag arm, so
273
277
  a tag name is a compile error and a page whose only dynamic mounts are components
274
278
  retains no element runtime (`createElement` / `spread` / the namespace tables). It
275
279
  is the documented way to mount a server component: `dynamicComponent(() =>
@@ -600,7 +604,8 @@ pair — `[ASYNC_WATERFALL]` under attribution.
600
604
 
601
605
  Clients that share one subscription per query-and-args and hand its current value to
602
606
  every new subscriber (Convex, most reactive database clients) prefetch by subscribing
603
- **early**: in the route's `preload` on link intent, held open for a short grace period
607
+ **early**: in the route's `preload` on link intent (`preloadLinks: intentPreload()` on
608
+ the router — without a strategy nothing preloads on hover <!-- upstream:preload-links-opt-in -->), held open for a short grace period
604
609
  (seconds) and released at once on error, so a hover while signed out leaves no failure
605
610
  for the page to inherit. Skip `intent === "initial"` there — on the first match the page
606
611
  subscribes on mount anyway, and an identity-gated read would run before the session is
@@ -1135,6 +1140,14 @@ write and `flush()`: `const { setA, b, dispose } = createRoot((dispose) => { …
1135
1140
  the test — `createRouter({ routes, history: memoryHistory("/orders/42") })` — because
1136
1141
  Testing Library's `render(…, { location })` is typed but not implemented (1.0.0-beta.3). <!-- upstream:testing-library-location -->
1137
1142
 
1143
+ 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
1146
+ the hydration phase ends — including when a module preload failed and the markup was left
1147
+ non-interactive — so it is a test-tooling marker, never a branch for what the app renders
1148
+ (`isHydrating()` / `onSettled` own that). Outside hydration it runs in the next microtask.
1149
+ <!-- upstream:hydration-end-experimental -->
1150
+
1138
1151
  `jsxImportSource` for tests and app code is `"@solidjs/web"`, not `"solid-js"`.
1139
1152
  Vite plugin is `@solidjs/vite-plugin`, not `vite-plugin-solid`.
1140
1153
 
@@ -1595,7 +1608,7 @@ Session location is `useLocation` / `useParams`,
1595
1608
  not `Router.paths`.
1596
1609
 
1597
1610
  **Route `preload` starts reads; the page reads them in a memo.** `preload` runs when
1598
- the route matches (and on link hover), so it should kick off the query and return
1611
+ the route matches (and ahead of a click under a `preloadLinks` strategy <!-- upstream:preload-links-opt-in -->), so it should kick off the query and return
1599
1612
  nothing; the component reads the same query through a memo that tracks the params:
1600
1613
 
1601
1614
  ```tsx
@@ -1643,9 +1656,16 @@ data that stays fixed while the route is matched. The factory `preload` on
1643
1656
  undefined` per key) — wrap a route in `defineRoute` when `preload` or the component
1644
1657
  needs `params.id: string`.
1645
1658
 
1646
- Link warming: `preload="false"` skips that link's *data* preload; `preloadLinks: false`
1647
- disables automatic link preloading. Intent values are `"initial"` / `"navigate"` /
1648
- `"native"` / `"preload"`. One router per app.
1659
+ Link warming is **opt-in**: `createRouter({ routes, preloadLinks: intentPreload() })`
1660
+ warms a link's route code and runs its `preload` on hover (after a short delay), focus
1661
+ and touch. Without `preloadLinks` nothing preloads ahead of the click. The other
1662
+ strategies, all from `@solidjs/router` and combinable as an array: `tapPreload()`
1663
+ (pointerdown), `viewportPreload()` and `eagerPreload()` (code only by default, for links
1664
+ 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
1667
+ nothing. <!-- upstream:preload-links-opt-in --> Intent values are `"initial"` /
1668
+ `"navigate"` / `"native"` / `"preload"`. One router per app.
1649
1669
 
1650
1670
  Link state needs no component code: the router sets `aria-current="page"` (same path
1651
1671
  and same query — parameter order and hash aside), `data-active` (exact or descendant
@@ -2111,6 +2131,7 @@ how to reuse. Prefer the form on the right.
2111
2131
  | `<form method="get" action={update.url}>` for a mutation | GET forms only for idempotent search; mutations are POST |
2112
2132
  | `window.location.href = ...` / `history.pushState` | `useNavigate()` or `<a href={Router.paths...}>` |
2113
2133
  | `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 --> |
2114
2135
  | `new URLSearchParams(location.search)` hand-rolled query parsing | `useSearchParams()` (setter merges, no scroll); typed via a route `search` schema + `useSearchParams(Router.paths.search)` |
2115
2136
  | `Router.paths.about()` / string-concatenated URLs | static nodes are properties (`Router.paths.about`); calls bind params, then search object and hash |
2116
2137
  | `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.21.0",
3
+ "version": "0.22.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",