create-rivet 0.1.0-alpha.1 → 0.1.0-alpha.2

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.
Files changed (101) hide show
  1. package/README.md +8 -1
  2. package/deploy.d.ts +1 -1
  3. package/deploy.js +4 -4
  4. package/index.d.ts +6 -4
  5. package/index.js +13 -5
  6. package/main.d.ts +4 -1
  7. package/main.js +142 -26
  8. package/output.d.ts +24 -0
  9. package/output.js +66 -0
  10. package/package.json +3 -1
  11. package/prompt.d.ts +1 -0
  12. package/prompt.js +2 -2
  13. package/templates/starter-minimal/.agents/skills/rivet-add-route/SKILL.md +1 -1
  14. package/templates/starter-minimal/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  15. package/templates/starter-minimal/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
  16. package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
  17. package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  18. package/templates/starter-minimal/.claude/skills/rivet-add-route/SKILL.md +1 -1
  19. package/templates/starter-minimal/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  20. package/templates/starter-minimal/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
  21. package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
  22. package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  23. package/templates/starter-minimal/.cursor/rules/rivet-add-route.mdc +1 -1
  24. package/templates/starter-minimal/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
  25. package/templates/starter-minimal/.cursor/rules/rivet-port-react-component.mdc +3 -3
  26. package/templates/starter-minimal/.cursor/rules/rivet.mdc +6 -6
  27. package/templates/starter-minimal/.github/copilot-instructions.md +6 -6
  28. package/templates/starter-minimal/AGENTS.md +56 -78
  29. package/templates/starter-minimal/CLAUDE.md +56 -78
  30. package/templates/starter-minimal/README.md +3 -3
  31. package/templates/starter-minimal/package.json +2 -1
  32. package/templates/starter-minimal/src/document.ts +7 -6
  33. package/templates/starter-server/.agents/skills/rivet-add-route/SKILL.md +1 -1
  34. package/templates/starter-server/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  35. package/templates/starter-server/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
  36. package/templates/starter-server/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
  37. package/templates/starter-server/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  38. package/templates/starter-server/.claude/skills/rivet-add-route/SKILL.md +1 -1
  39. package/templates/starter-server/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  40. package/templates/starter-server/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
  41. package/templates/starter-server/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
  42. package/templates/starter-server/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  43. package/templates/starter-server/.cursor/rules/rivet-add-route.mdc +1 -1
  44. package/templates/starter-server/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
  45. package/templates/starter-server/.cursor/rules/rivet-port-react-component.mdc +3 -3
  46. package/templates/starter-server/.cursor/rules/rivet.mdc +6 -6
  47. package/templates/starter-server/.github/copilot-instructions.md +6 -6
  48. package/templates/starter-server/AGENTS.md +56 -78
  49. package/templates/starter-server/CLAUDE.md +56 -78
  50. package/templates/starter-server/README.md +5 -4
  51. package/templates/starter-server/package.json +2 -1
  52. package/templates/starter-server/serve.js +2 -0
  53. package/templates/starter-server/src/document.ts +7 -6
  54. package/templates/starter-static/.agents/skills/rivet-add-route/SKILL.md +1 -1
  55. package/templates/starter-static/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  56. package/templates/starter-static/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
  57. package/templates/starter-static/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
  58. package/templates/starter-static/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  59. package/templates/starter-static/.claude/skills/rivet-add-route/SKILL.md +1 -1
  60. package/templates/starter-static/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  61. package/templates/starter-static/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
  62. package/templates/starter-static/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
  63. package/templates/starter-static/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  64. package/templates/starter-static/.cursor/rules/rivet-add-route.mdc +1 -1
  65. package/templates/starter-static/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
  66. package/templates/starter-static/.cursor/rules/rivet-port-react-component.mdc +3 -3
  67. package/templates/starter-static/.cursor/rules/rivet.mdc +6 -6
  68. package/templates/starter-static/.github/copilot-instructions.md +6 -6
  69. package/templates/starter-static/AGENTS.md +56 -78
  70. package/templates/starter-static/CLAUDE.md +56 -78
  71. package/templates/starter-static/README.md +6 -5
  72. package/templates/starter-static/package.json +2 -1
  73. package/templates/starter-static/src/components/Counter.tsx +2 -6
  74. package/templates/starter-static/src/components/ReadingPlan.tsx +31 -0
  75. package/templates/starter-static/src/document.ts +7 -6
  76. package/templates/starter-static/src/routes/about.tsx +14 -1
  77. package/templates/starter-static/src/routes/index.tsx +2 -2
  78. package/templates/starter-ui/.agents/skills/rivet-add-route/SKILL.md +1 -1
  79. package/templates/starter-ui/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  80. package/templates/starter-ui/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
  81. package/templates/starter-ui/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
  82. package/templates/starter-ui/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  83. package/templates/starter-ui/.claude/skills/rivet-add-route/SKILL.md +1 -1
  84. package/templates/starter-ui/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
  85. package/templates/starter-ui/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
  86. package/templates/starter-ui/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
  87. package/templates/starter-ui/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
  88. package/templates/starter-ui/.cursor/rules/rivet-add-route.mdc +1 -1
  89. package/templates/starter-ui/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
  90. package/templates/starter-ui/.cursor/rules/rivet-port-react-component.mdc +3 -3
  91. package/templates/starter-ui/.cursor/rules/rivet.mdc +6 -6
  92. package/templates/starter-ui/.github/copilot-instructions.md +6 -6
  93. package/templates/starter-ui/AGENTS.md +56 -78
  94. package/templates/starter-ui/CLAUDE.md +56 -78
  95. package/templates/starter-ui/README.md +5 -4
  96. package/templates/starter-ui/package.json +2 -1
  97. package/templates/starter-ui/src/components/LikeButton.tsx +1 -1
  98. package/templates/starter-ui/src/components/ReadingPreferences.tsx +18 -0
  99. package/templates/starter-ui/src/document.ts +7 -6
  100. package/templates/starter-ui/src/routes/index.tsx +3 -1
  101. package/templates/starter-ui/vite.config.ts +3 -1
@@ -6,15 +6,15 @@ Read this before editing this project. Rivet looks like React / Solid JSX but is
6
6
 
7
7
  ## 1. What Rivet is
8
8
 
9
- Rivet is a compiled, resumable JSX framework. A Rust compiler turns `.tsx` into server HTML (streamed) and DOM templates. A component function runs **once, on the server**; the browser never re-runs it. It **resumes** the page from the HTML plus serialized state: only event handlers, bindings that read signals, and client effects run in the browser, and their code loads on intent / visibility / idle. State is fine-grained signals (`@rivetjs/reactivity`). Routing is file-based (`@rivetjs/router`), i18n is compile-time (`@rivetjs/i18n`), forms are progressive (`@rivetjs/forms`), UI is `@rivetjs/ui` (styled compound components) on headless `@rivetjs/cdk`.
9
+ Rivet is a compiled, resumable JSX framework. A Rust compiler turns `.tsx` into server HTML (streamed) and DOM templates. A component function renders **on the server**. The browser **resumes** an interactive boundary by running its ordinary compiled DOM function over the existing HTML on its first event, signal wake or client trigger, using the recorded signal values. The loader can fetch code ahead of use; client effects can also start boundaries without a user event. State is fine-grained signals (`@rivetjs/reactivity`). Routing is file-based (`@rivetjs/router`), i18n is compile-time (`@rivetjs/i18n`), forms are progressive (`@rivetjs/forms`), UI is `@rivetjs/ui` (styled compound components) on headless `@rivetjs/cdk`.
10
10
 
11
11
  ## 2. Differences from React / Solid
12
12
 
13
13
  ### Mental model
14
14
 
15
- - No virtual DOM, no re-renders. A signal change updates only the text / attribute / row that reads it. No `useMemo`, `useCallback`, dependency arrays, or hooks rules: `signal()` / `computed()` can be created anywhere.
16
- - Component body = server code. `window`, `document`, `localStorage` there are a compile error (RV2001). `console.log` in the body prints in the terminal.
17
- - `typeof window !== "undefined"` branches in a component never run in the browser. Use a handler, `clientEffect`, `clientEvent` or `clientOnly`.
15
+ - No virtual DOM, no re-renders. A signal change updates only the text / attribute / row that reads it. No `useMemo`, `useCallback`, dependency arrays, or React hooks ordering rules. In resumable components, create per-item state in a child component rather than calling `signal()` / `createStore()` in a loop (RV2104).
16
+ - Component bodies run during SSR and again when their boundary resumes. Browser-global reads (`window`, `document`, `localStorage`) during server rendering are errors (RV2001). Keep browser-only work in handlers or client helpers; a body log can run on both sides.
17
+ - Do not branch rendered markup on `typeof window`: the two sides must render the same structure. Use a handler, `clientEffect`, `clientEvent` or `clientOnly` for browser-only work.
18
18
 
19
19
  ### JSX
20
20
 
@@ -34,8 +34,6 @@ Rivet is a compiled, resumable JSX framework. A Rust compiler turns `.tsx` into
34
34
  ```
35
35
 
36
36
  - Two-way form binding: `<input model:value={name} />`, `<input type="checkbox" model:checked={agree} />`, also `<select model:value={choice}>`. The value must be a signal name/property.
37
- - No JSX inside handlers, effects or computeds (RV2006): render JSX in the component, switch it with a signal.
38
- - No `this` in handlers (RV2007): use `event.currentTarget`.
39
37
 
40
38
  ### Signals (`@rivetjs/reactivity`)
41
39
 
@@ -50,15 +48,16 @@ untrack(count); // read without subscribing (there is no .peek())
50
48
  ```
51
49
 
52
50
  - Avoid `count(5)` (not a setter) and `count.value`.
51
+ - In an `effect` / `computed` / `clientEffect`, write from the current value with `update`: `count.set(count() + 1)` there subscribes the effect to its own write and re-runs it until the 10000-run cutoff (RV1017). In event handlers `count.set(count() + 1)` is fine (nothing tracks there).
53
52
  - `computed` / `effect` must be synchronous (an async fn is a type error).
54
- - Stores: `createStore` from `@rivetjs/reactivity/store`.
53
+ - Stores: `createStore`, `read` and `write` from `@rivetjs/reactivity/store`: `const state = createStore({ done: false })`, `read(state, "done")`, `write(state, "done", (done) => !done)`. Writes copy data; do not mutate a value returned by `read`.
55
54
 
56
55
  ### Effects, lifecycle, cleanup
57
56
 
58
57
  - In components use `clientEffect` from `@rivetjs/runtime/dom`: recorded on the server, runs only in the browser, re-runs when its signals change. Return a function for cleanup.
59
- - `effect` from `@rivetjs/reactivity` runs immediately where called — in a component that means on the server only. Use it in plain TS / tests.
58
+ - `effect` from `@rivetjs/reactivity` runs immediately where called, including during SSR and when the component resumes. Use `clientEffect` for browser-only work.
60
59
  - `onCleanup(fn)` (reactivity) registers cleanup for the current effect / root; throws outside one.
61
- - Options: `clientEffect(fn, { when: "idle" | "load" | "visible" | "interaction" | "media:(…)" })`. Default `visible` if it uses a ref, else `idle`.
60
+ - Options: `clientEffect(fn, { when: "idle" | "load" | "visible" | "interaction" | "media:(…)" })`. On derived pages, the default and `load` schedule idle adoption; `visible` observes the boundary's first element intersecting the viewport. `interaction` / `media:` adopt on the first event in the boundary, not on a media-query change. Once the boundary runs, the effect starts in a microtask.
62
61
  - Async `clientEffect`: only signals read before the first `await` are tracked; returned cleanup works only from non-async effects.
63
62
 
64
63
  ```tsx
@@ -76,7 +75,7 @@ clientEffect(() => {
76
75
  ### Props are live (Solid-style)
77
76
 
78
77
  - `<Badge count={count()} />` passes a getter; the child sees updates when it reads `props.count` where it is shown.
79
- - Destructuring reads once: on a resumed page a destructuring child is re-created on the first change (local signals reset). Read `props.x`.
78
+ - Destructuring reads once: a destructuring child never sees later values. Read `props.x`.
80
79
  - Object spread `{ ...props }` / `Object.assign` reads every getter once; JSX spreads `<button {...props} />` stay live.
81
80
  - Defaults / forwarding: `mergeProps`, `splitProps` from `@rivetjs/runtime/props`.
82
81
  - `count={count}` passes the signal itself (child calls `props.count()` / writes it). Use for two-way props.
@@ -116,7 +115,7 @@ import { ref } from "@rivetjs/runtime/dom";
116
115
  const input = ref<HTMLInputElement>();
117
116
  <input ref={input} />; // input() → element | undefined (signal-backed)
118
117
  let el!: HTMLCanvasElement;
119
- <canvas ref={(e) => (el = e)} />; // callback must assign a local (RV2002)
118
+ <canvas ref={(e) => (el = e)} />; // or a callback
120
119
  ```
121
120
 
122
121
  ### Context
@@ -134,31 +133,20 @@ import { Provider } from "@rivetjs/runtime/dom";
134
133
  const theme = useContext(ThemeContext); // import { useContext } from "@rivetjs/reactivity"
135
134
  ```
136
135
 
137
- - The provided value is read once: provide a signal for changing values. It must be serializable (data / signals, no service objects).
136
+ - The provided value is read once: provide a signal for changing values.
138
137
 
139
138
  ## 3. Resumability rules
140
139
 
141
- Everything a handler, effect, computed or binding uses from its component (its **captures**) is written into the HTML. It must be serializable.
140
+ The server writes the HTML plus a short record per interactive component (it has a handler, reads a signal in its markup, or uses an effect): where it is, its signal values, its props when they are plain data. The first event, signal wake or client trigger runs the boundary's compiled DOM function over the existing HTML. Handlers, functions, service objects and JSX props have no capture-serialization rules: the browser reconstructs them when the component runs.
142
141
 
143
- - OK: primitives, plain objects/arrays, `Date`, `URL`, `URLSearchParams`, `Map`, `Set`, `RegExp`, typed arrays, `ArrayBuffer`, `DataView`, `Error`, `bigint`, signals, `computed`, and functions Rivet compiled: inline arrows, local `const` functions, inline callback props, functions in object literals, returned functions, inline functions written into a value a handler uses (`api.inc = () => …`, `signal(() => …)`, `[() => …]`), local functions stored by name into such a value (`api.open = open`, `list.push(open)`), module-level or imported functions used by name inside a component (called by a handler, passed as a prop, kept in a local const, put in an object / array / call argument).
144
- - Not OK: class instances (`new Intl.NumberFormat()`, `new Api()` → RV2003; a data class declared in the component module resumes — no `#private` field, no base other than a data class of that module, no decorator, no `window` / `document` read while constructing, else RV2102; `reconstruct(() => new Api(…))` from `@rivetjs/runtime/dom` builds it in the browser on first read (no value in the page); a resource holder resumes with `[serialize]()` + `static [revive](data)`, a package class with `registerClass(Cls, { id, serialize, revive })` from `@rivetjs/runtime/resume-format`), `Promise`, `WeakMap`, `Symbol`, `.bind(...)` written inside an object literal (RV2014; inline in a prop / handler or a local `const inc = add.bind(null, 2)` of a compiled function used by name is OK; `.bind(ctx)` of a `this` function stays RV2007), methods / getters, functions using `this`, functions created at runtime by libraries (`memoize(fn)`), module-level / imported functions put into objects only outside a component (module level, hooks, helpers).
145
-
146
- ```tsx
147
- // Avoid: captures a class instance
148
- const fmt = new Intl.NumberFormat("en", { style: "currency", currency: "EUR" });
149
- <button onClick={() => alert(fmt.format(n()))}>Pay</button>
150
- // Prefer: create it where it runs
151
- <button onClick={() => alert(new Intl.NumberFormat("en", { style: "currency", currency: "EUR" }).format(n()))}>Pay</button>
152
- ```
153
-
154
- - Wrap foreign functions in an arrow: `onPress={() => analytics.track("save")}`.
155
- - Imports are not captures: import modules at the top, create objects inside the handler, keep browser objects (EventSource, chart instances) inside `clientEffect`.
156
- - Render props and item renderers take functions: `({ close }) => …` on Dialog / Modal / Popover, `{(item) => …}` on ListBox / Select / Tabs, Tabs `panel`. Plain JSX children are fine elsewhere: a closed overlay renders nothing on the server and creates its content when it opens.
157
- - Render a function child as `{props.children}` directly; a copy in a local (`const c = props.children`) is not tracked on resumed pages.
158
- - `For each={xs}` (accessor) keeps resumed rows and branches better than `xs().map(...)`.
142
+ - Render the same thing on both sides: no `Date.now()` / `Math.random()` / `new Date()` / `crypto.randomUUID()` in a render (RV2103) — keep it in a signal (`signal(Date.now())`, the server value ships) or compute it in a handler / `clientEffect`. One signal per creation site: no `signal()` inside a loop (RV2104 warning). Browser globals only in handlers / `clientEffect` / `clientEvent` / `clientOnly` (RV2001).
143
+ - Keep signal/store values as plain JSON data (null, booleans, finite numbers, strings, arrays and plain objects). In this derived implementation a recorded non-JSON signal value is omitted; adoption uses its browser initializer and a structural mismatch falls back to browser rendering. Do not rely on it retaining server state. The richer resume-format encoding below applies to route data.
144
+ - Plain-data props can let the build make a component independent. Functions, services and JSX props are allowed; a component given non-data props adopts with its parent. There is no handler-capture restriction.
145
+ - Route loader/action data must be encodable (RV2107 runtime warning; the page renders on the server but cannot resume): no functions, symbols, promises, unregistered class instances. Resume-format data also supports `Date`, `Map`, `Set`, `URL`, `bigint` and typed arrays. Classes: `registerClass(Cls, { id, serialize, revive })` from `@rivetjs/runtime/resume-format`, imported on server and browser.
146
+ - Render a function child as `{props.children}` directly. `For each={xs}` with `by` for lists that change.
159
147
  - `.ts` files are not server-rewritten: import `@rivetjs/runtime/dom` (`For`, `Show`, `clientEffect`, …) only from `.tsx` files, else the DOM runtime is bundled into SSR.
160
- - Components outside `src/routes` need no registration: `rivetRouter` resumes every `.tsx` / `.jsx` file a route imports.
161
- - Errors: build/prerender prints `Cannot resume <file>#<Component>$<hash>: function is not serializable`. The dev server names the capture (``capture `<name>` ``); dev builds (`NODE_ENV=development vite build`) give its position (`capture #<i>`) and the value path. Fixes: build the object inside the component; a library object holding functions → `reconstruct(() => createChart(…))` (`@rivetjs/runtime/dom`; module level = one server instance per process, no request state in it); a class instance → `reconstruct(() => new X(…))`, `[serialize]()` + `static [revive](data)`, or for a package class `registerClass(X, { id, serialize, revive })` (`@rivetjs/runtime/resume-format`).
148
+ - With the default `resume.appModules: true`, project `.tsx` / `.jsx` files imported by routes need no registration (outside `node_modules` and within the project root).
149
+ - A component whose markup differs in the browser renders there instead of reusing the server nodes (dev prints a warning naming it).
162
150
  - Playwright tests: the first trusted mouse click per document flushes DOM updates one task later. After a page's first `page.click`, wait (`expect(...).toHaveText` retries / `waitForFunction`), don't read the DOM synchronously.
163
151
 
164
152
  ## 4. File routes
@@ -179,7 +167,7 @@ src/
179
167
  _404.tsx _error.tsx
180
168
  ```
181
169
 
182
- - Files/folders starting with `_` (except `_layout`, `_404`, `_error`), `.`, `*.test.*`, `*.d.ts` are ignored. `*.server.ts` = server-only module: browser code cannot use it (RV3008 in a route module's browser part); a page that imports one itself becomes a document route (full page load), a component, layout or error page that reaches one fails the build (RV3011).
170
+ - Files/folders starting with `_` (except `_layout`, `_404`, `_error`), `.`, `*.test.*`, `*.spec.*`, `*.d.ts` are ignored. `*.server.ts` = server-only module: browser code cannot use it (RV3008 in a route module's browser part); a page that imports one itself becomes a document route (full page load), a component, layout or error page that reaches one fails the build (RV3011).
183
171
  - `.rivet/` is generated (route types `routes.d.ts`, editor manifest): never edit; `rivet sync` regenerates it.
184
172
 
185
173
  ### Page module exports
@@ -210,7 +198,7 @@ export default function Post({ data, params }: PageProps<"blog/[slug]">) {
210
198
  ```
211
199
 
212
200
  - Server exports: `load(…)`, `actions(…)`, `guard(…)` (throw/return `redirect()`, `deny()`, `notFound()`, `error()`), `headers(…)`, `entries(…)` (prerender params), `prerender`, `onRequest(…)` / `onGet(…)` / `onPost(…)` …. `load(fn, { defer: true })` = the page streams without waiting: `data` is a `Deferred` handle, render it only through `<Async load={() => data}>` (layouts: `data()`) or `resource(() => data)`, never `data.field`; it cannot set headers / cookies. The compiler strips them (and code only they use) from the browser build.
213
- - `event`: `request`, `url`, `params`, `locals`, `signal`, `cookies`, `setHeader`.
201
+ - `event`: `request`, `url`, `params`, `locals`, `signal`, `cookies`, `setHeader`, `appendHeader`.
214
202
  - Page props: `{ data, params, url }` — plain values. Layout props (`LayoutProps`): `data()`, `params()`, `url()` are **accessors**, plus `children`.
215
203
 
216
204
  ```tsx
@@ -234,7 +222,7 @@ export default function Layout({ data, children }: LayoutProps<typeof loader>) {
234
222
 
235
223
  ### Links and navigation
236
224
 
237
- - Plain `<a href="/about">` works (client navigation). Typed: `import { Link } from "@rivetjs/router/link"`; `<Link to="/items/42">`.
225
+ - Plain `<a href="/about">` works; it uses client navigation when the page has the client router and document navigation otherwise. Typed: `import { Link } from "@rivetjs/router/link"`; `<Link to="/items/42">`.
238
226
  - `import { navigate, invalidate, useSearchParams, setSearchParams, beforeLeave, actionResult } from "@rivetjs/router/client"`.
239
227
  - Under a base path, plain anchors need `withBase("/x")` from `@rivetjs/router`.
240
228
  - Never call `history.pushState` yourself: the router does not see it.
@@ -242,7 +230,7 @@ export default function Layout({ data, children }: LayoutProps<typeof loader>) {
242
230
  ### Modes (`vite.config.ts`)
243
231
 
244
232
  - `rivetRouter()` = `mode: "server"` (render per request; `npm run build` → `dist/server`, `npm start`).
245
- - `rivetRouter({ mode: "static" })` = prerender to HTML files (`prerender.js` / `prerenderBuild`). `load(…)` runs at build time. A page with nothing to resume (no handlers, signals in markup, client effects, forms) ships zero JS and its links load documents; `clientRouter: true` keeps in-place navigation on every page.
233
+ - `rivetRouter({ mode: "static" })` = prerender to HTML files (`prerender.js` / `prerenderBuild`). `load(…)` runs at build time. A page with nothing to resume (no handlers, signals in markup, client effects, forms) ships zero JS and its links load documents (any mode: the client router is opt-in); `clientRouter: true` keeps in-place navigation on every page.
246
234
  - `mode: "csr"` = SPA, no server code.
247
235
 
248
236
  ### Forms (`@rivetjs/forms`)
@@ -304,6 +292,7 @@ export default function Contact() {
304
292
  - Compile-time: `t("key")` is replaced by the string for the locale being built; no runtime catalog. One client build per locale.
305
293
  - `i18n.config.ts` (`locales`, `defaultLocale`, `catalogs: "locales/{locale}.json"`, `routing`); Vite: `plugins: [i18n(config), rivetRouter()]` — `i18n()` from `@rivetjs/i18n/vite`, **before** the router.
306
294
  - Catalogs: JSON, nested keys → dotted, values ICU MessageFormat (`"Hello, {name}!"`, plural / select).
295
+ - Component strings (`@rivetjs/cdk` / `@rivetjs/ui` labels, date / number formats) ship every locale unless the app declares the ones it serves: `rivetRouter({ locales: ["en-US", "de-DE"] })` (default: `i18n()` `locales`). Declared, other languages load their strings on first use and may show `en-US` text until then: list every language you serve.
307
296
 
308
297
  ```tsx
309
298
  import { t } from "@rivetjs/i18n";
@@ -312,13 +301,14 @@ import { t } from "@rivetjs/i18n";
312
301
  <p>{t("terms", { link: (c) => <a href="/terms">{c}</a> })}</p>
313
302
  ```
314
303
 
315
- - Key = string literal; params = inline object literal. Unknown key RV4001, missing / extra param RV4002, bad args RV4003, tags in attributes RV4004.
304
+ - Use a literal key and inline object params. A fixed-prefix template key such as ``t(`nav.${id}`)`` is also supported for plain-text messages without params. Unknown key RV4001, missing / extra param RV4002, bad args RV4003, tags in attributes RV4004.
316
305
  - Server code outside rendering (emails in actions): `t.in("de", "cart.title")`. Never `String(t(…))` on the server.
306
+ - Current language: `locale()` from `@rivetjs/i18n` (call during render; loaders / actions: `event.locals.locale`). `t()` values work in attributes (`Picture alt`), route `head` and kit `<Title>` / `<Meta>`.
317
307
  - Generated types: `src/rivet-i18n.d.ts` (do not edit). Locale URLs: `[[lang]]` route folder.
318
308
 
319
309
  ## 6. Styles
320
310
 
321
- - Templates use Tailwind v4: `src/styles.css` (`@import "tailwindcss"; @source "./**/*.{ts,tsx}";`), listed in `rivetRouter({ styles: ["src/styles.css"] })`, plus `tailwindcss()` Vite plugin. Write utilities in `class`.
311
+ - Static, server and UI starters use Tailwind v4 (minimal has no styling tools): `src/styles.css` (`@import "tailwindcss"; @source "./**/*.{ts,tsx}";`), listed in `rivetRouter({ styles: ["src/styles.css"] })`, plus `tailwindcss()` Vite plugin. Write utilities in `class`.
322
312
  - Scoped CSS (experimental): ``const styles = css`.card { … }` `` at module top level, `import { css, cx } from "@rivetjs/runtime/css"`; use `class={styles.card}`, combine with `cx(styles.card, on() && styles.selected, props.class)`.
323
313
  - Dynamic values: `style={{ "--x": n(), marginTop: "1px" }}` or `style:color={c()}`; `data-*` attributes for state styling.
324
314
 
@@ -379,7 +369,7 @@ import { Select } from "@rivetjs/ui/select";
379
369
  ```
380
370
 
381
371
  - `@rivetjs/cdk`: headless 1:1 ports of React Aria / React Stately / RAC / Angular CDK, one subpath per module (`@rivetjs/cdk/select`, `@rivetjs/cdk/dialog`, `@rivetjs/cdk/overlay`, …). Unstyled: default `rv-*` classes + `data-*` state attributes. Overlays use a div layer in a portal (no native `<dialog>` / `popover`). Use it for custom-styled widgets; prefer `@rivetjs/ui` when its look fits.
382
- - Animations: `@rivetjs/motion` (Motion / motion.dev API, peer `motion` 14.x, no React): `<Motion.div initial={…} animate={…} exit={…} whileHover={…} whilePress={…}>` (`whilePress`, not `whileTap`), `Presence` / `PresenceFor` from `/presence` (not `AnimatePresence`; cdk / ui overlays and kit `Transition` already are presences), `layout` / `layoutId` + `animateLayout(scope, update)` from `/layout` (layout animates inside `animateLayout`, not on every render), `createScroll` / `createInView` / `prefersReducedMotion` accessors. Entrances render final styles + CSS on the server (no JS); values Motion's mini engine cannot animate (`rotateX`, `pathLength`, …) are a dev error: use `animate` from `@rivetjs/motion/animate` in a handler or `clientEffect`. Gestures with physics (drag, flick, carousel, sheet) stay cdk `motion` (`@rivetjs/cdk/motion`); there is no `drag` prop.
372
+ - Animations: `@rivetjs/motion` (Motion / motion.dev API, peer `motion` 14.x, no React): `<Motion.div initial={…} animate={…} exit={…} whileHover={…} whilePress={…}>` (`whilePress`, not `whileTap`), `Presence` / `PresenceFor` from `/presence` (not `AnimatePresence`; cdk / ui overlays and kit `Transition` already are presences), `layout` / `layoutId` + `animateLayout(scope, update)` from `/layout` (layout animates inside `animateLayout`, not on every render), `createScroll` from `/scroll`, `createInView` from `/in-view`, and `prefersReducedMotion` from `/reduced-motion` return accessors. Entrances render final styles + CSS on the server (no JS); values Motion's mini engine cannot animate (`rotateX`, `pathLength`, …) are a dev error: use `animate` from `@rivetjs/motion/animate` in a handler or `clientEffect`. Gestures with physics (drag, flick, carousel, sheet) stay cdk `motion` (`@rivetjs/cdk/motion`); there is no `drag` prop.
383
373
 
384
374
  ## 9. Commands
385
375
 
@@ -389,56 +379,44 @@ import { Select } from "@rivetjs/ui/select";
389
379
  | `npm run build` | all | `vite build && vite build --ssr` (+ `node prerender.js` for static / ui / minimal) |
390
380
  | `npm start` | server | `node serve.js` (`PORT`, `HOST`) |
391
381
  | `npm run preview` | static, ui | serve `dist/client` |
392
- | `npm run check` | all | `rivet sync && rivet-check` |
382
+ | `npm run check` | all | `rivet sync && rivet-check && tsgo` |
393
383
 
394
384
  - Create: `npm create rivet@latest my-app -- --template static|server|ui|minimal --yes`.
395
- - `rivet sync` (bin of `@rivetjs/router`): regenerates `.rivet/` (route types + `.rivet/editor.json`). Run on a fresh clone before `tsc`.
396
- - `rivet-check` (bin of `@rivetjs/language-core`): compiler diagnostics for both targets + type-aware checks for every `.tsx` / `.jsx` of `tsconfig.json`. Flags: `-p <tsconfig|dir>` (repeatable), `--no-types`, `--no-manifest`, `--format text|json`. Exit 0 clean (warnings ok), 1 errors, 2 setup error. Run it after every change and before reporting done; also `npx tsc -p .`.
385
+ - `rivet sync` (bin of `@rivetjs/router`): regenerates `.rivet/` (route types + `.rivet/editor.json`). Run on a fresh clone before `tsgo`. `--quiet` hides status output; `--json` returns one result; `--verbose` includes error stacks. Exit 0 success, 1 failure, 2 usage error.
386
+ - `rivet-check` (bin of `@rivetjs/language-core`): compiler diagnostics for both targets + type-aware checks for every `.tsx` / `.jsx` of `tsconfig.json`. Flags: `-p <tsconfig|dir>` (repeatable), `--no-types`, `--no-manifest`, `--format text|json` / `--json`, `--quiet` (errors only; JSON keeps totals), `--verbose` (stacks; also `DEBUG=rivet`). Repeated warnings share one code/file report with a count; JSON retains their locations. Exit 0 clean (warnings ok), 1 errors/setup failure, 2 invalid arguments. Run it after every change and before reporting done. RV3101 (a link matching no route) stands down when `to` is typed with the generated route union: `tsgo` (TypeScript 7 native, part of `npm run check`) reports TS2820 then.
397
387
  - Editor: `@rivetjs/typescript-plugin` in `tsconfig.json` shows the same diagnostics + quick fixes while typing.
398
388
  - Measure in a build (`npm run build` + start/preview), not dev.
399
389
  - Tests (optional setup): `rivetTest()` from `@rivetjs/testing/vite`; `render`, `fire`, `flush` from `@rivetjs/runtime/testing`; `renderToString` from `@rivetjs/runtime/testing/server`.
400
390
 
401
391
  ## 10. RV diagnostic codes
402
392
 
403
- - Format `RVnnnn`. Compiler errors fail the build; compiler warnings (RV2012–RV2014 on a `.bind` receiver that may be an object with its own `bind`, RV2015 on a returned accessor that cannot resume) print and the module still compiles; the language service adds type-aware warnings.
393
+ - Format `RVnnnn`. Compiler errors fail the build; compiler warnings (RV2104: keyed state created in a loop) print and the module still compiles; the language service adds route-link warnings (RV3101).
404
394
  - Where: Vite (terminal + overlay) as `[RV1016] src/x.tsx:12:8: message`; `rivet-check` as `file:line:col - error RV1016: message`; editor via the TS plugin. The MCP tool `explain_diagnostic("RV1016")` gives the meaning, the mechanical fix and an Avoid / Prefer pair; `check_code` returns the fix and the fixed source for a whole file.
405
395
  - Ranges: 1xxx JSX / attributes, 2xxx resume, 3xxx imports / helpers / route modules, 4xxx i18n, 5xxx styles, 6xxx slots, 7xxx config, 9xxx internal.
406
396
 
407
- | Code | Meaning | Fix |
408
- | --------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
409
- | RV1000 | Parse error | fix syntax |
410
- | RV1005 | HTML nesting the parser would repair (`<div>` in `<p>`) | valid nesting |
411
- | RV1012 | Unknown native event (`onclick`, `onTransitionend`) | `on` + PascalCase |
412
- | RV1015 | Handler is a literal, not a function | pass a function |
413
- | RV1016 | Handler expression reads state when it binds | wrap in an arrow |
414
- | RV1101 | Unsafe / unsupported attribute or URL (`innerHTML`, `javascript:`) | remove; build-time assets |
415
- | RV1102 | Invalid `preload` value | `eager` / `idle` / `visible` / `intent` / `interaction` |
416
- | RV2001 | Browser global read during server rendering | handler / `clientEffect` / `clientEvent` / `clientOnly` |
417
- | RV2002 | Ref callback must assign a local | `ref={(el) => (local = el)}` or `ref()` |
418
- | RV2003 | Captured class instance | pass data; inside handler; `reconstruct(…)` |
419
- | RV2102 | Captured instance of a module class that is not a data class (`#private`, base, resource) | inside handler; `reconstruct(…)`; plain fields |
420
- | RV2006 | JSX inside handler / effect / computed | JSX in component + signal |
421
- | RV2007 | Resumable code captures `this` | `event.currentTarget` |
422
- | RV2009 | Two module references share a browser name | rename one import |
423
- | RV2011 | Mutually recursive functions split by an early exit | move them together before the exit |
424
- | RV2012–RV2014 | (warning; error on a provable function) `.bind` that may not resume | `const g = f.bind(t, …)`; arrow for other callees |
425
- | RV2015 | (warning) component returns a function that cannot resume (anon default / wrapper / `let`) | `export function Name(props) { return () => …; }` |
426
- | RV2016 | (warning) `resource()` that cannot resume (destructured, `let`, not a `const` initializer) | one `const user = resource(…)`; quick fix |
427
- | RV2101 | (warning) captured value not serializable (type-aware) | as RV2003 |
428
- | RV2103 / RV2104 | `resume: { model: "derived" }` only: `Date.now()` / `Math.random()` while rendering / (warning) signal created in a loop | keyed `signal(Date.now())` or `clientEffect`; one signal outside the loop |
429
- | RV3003 / RV3004 | Structural helper namespace import / alias | `import { For } from "@rivetjs/runtime/dom"` |
430
- | RV3006 | `clientOnly` erasure shape | inline `() => import("…")` |
431
- | RV3007 | Route module server export shape (`export *` in a route) | declare server exports directly |
432
- | RV3008 | Server-only code reached from browser code | keep in `load(…)` / `*.server.ts` |
433
- | RV3011 | Server-only module reached from a component, layout or error page (router build) | pass it as props from the page's `load(…)` |
434
- | RV3101 | (warning) Link matches no route | fix the path |
435
- | RV4001–4004 | i18n key / param / argument / tag errors | see section 5 |
436
- | RV5001–5003 | ``css`…` `` syntax / usage; invalid or unsafe class / style (style string, two class attrs) | style object, one `class` |
437
- | RV6001–6004 | Slot errors (`<slot>`, `r:name`, `r:slot`) | see JSX reference, slots |
438
- | RV7101 / RV7102 | `.rivet/editor.json` missing / stale | `rivet sync` |
439
- | RV9000 / RV9001 | Unclassified / internal compiler error | report it |
440
-
441
- - Not a code: render-time `… is not serializable` (see section 3).
397
+ | Code | Meaning | Fix |
398
+ | --------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
399
+ | RV1000 | Parse error | fix syntax |
400
+ | RV1005 | HTML nesting the parser would repair (`<div>` in `<p>`) | valid nesting |
401
+ | RV1012 | Unknown native event (`onclick`, `onTransitionend`) | `on` + PascalCase |
402
+ | RV1015 | Handler is a literal, not a function | pass a function |
403
+ | RV1016 | Handler expression reads state when it binds | wrap in an arrow |
404
+ | RV1101 | Unsafe / unsupported attribute or URL (`innerHTML`, `javascript:`) | remove; build-time assets |
405
+ | RV1102 | Invalid `preload` value | `eager` / `idle` / `visible` / `intent` / `interaction` |
406
+ | RV2001 | Browser global read during server rendering | handler / `clientEffect` / `clientEvent` / `clientOnly` |
407
+ | RV2103 / RV2104 | resume modules: `Date.now()` / `Math.random()` while rendering / (warning) signal created in a loop | keyed `signal(Date.now())` or `clientEffect`; one signal outside the loop |
408
+ | RV2107 | (runtime warning) Route data not encodable (function, symbol, promise, unregistered class) | plain data; `registerClass` |
409
+ | RV3003 / RV3004 | Structural helper namespace import / alias | `import { For } from "@rivetjs/runtime/dom"` |
410
+ | RV3006 | `clientOnly` erasure shape | inline `() => import("…")` |
411
+ | RV3007 | Route module server export shape (`export *` in a route) | declare server exports directly |
412
+ | RV3008 | Server-only code reached from browser code | keep in `load(…)` / `*.server.ts` |
413
+ | RV3011 | Server-only module reached from a component, layout or error page (router build) | pass it as props from the page's `load(…)` |
414
+ | RV3101 | (warning) Link matches no route | fix the path |
415
+ | RV4001–4004 | i18n key / param / argument / tag errors | see section 5 |
416
+ | RV5001–5003 | ``css`…` `` syntax / usage; invalid or unsafe class / style (style string, two class attrs) | style object, one `class` |
417
+ | RV6001–6004 | Slot errors (`<slot>`, `r:name`, `r:slot`) | see JSX reference, slots |
418
+ | RV7101 / RV7102 | `.rivet/editor.json` missing / stale | `rivet sync` |
419
+ | RV9000 / RV9001 | Unclassified / internal compiler error | report it |
442
420
 
443
421
  ## 11. Links
444
422
 
@@ -454,7 +432,7 @@ import { Select } from "@rivetjs/ui/select";
454
432
 
455
433
  ## Checklist before reporting done
456
434
 
457
- - `npm run check` exits 0; `npm run build` passes (prerender / SSR catches non-serializable captures).
435
+ - `npm run check` exits 0; `npm run build` passes.
458
436
  - No destructured props in components that receive changing values; lists that change use `For` with `by`.
459
437
  - No browser globals outside handlers / `clientEffect` / `clientEvent` / `clientOnly`.
460
438
  - Icons only via `<Icon icon={…} />` + lucide.