create-rivet 0.0.0-stage → 0.1.0-alpha.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.
Files changed (232) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -2
  3. package/cli.d.ts +2 -0
  4. package/cli.js +10 -0
  5. package/deploy.d.ts +32 -0
  6. package/deploy.js +99 -0
  7. package/index.d.ts +118 -0
  8. package/index.js +184 -0
  9. package/main.d.ts +9 -0
  10. package/main.js +169 -0
  11. package/package.json +57 -4
  12. package/prompt.d.ts +24 -0
  13. package/prompt.js +127 -0
  14. package/templates/starter-minimal/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
  15. package/templates/starter-minimal/.agents/skills/rivet-add-route/SKILL.md +36 -0
  16. package/templates/starter-minimal/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  17. package/templates/starter-minimal/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
  18. package/templates/starter-minimal/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  19. package/templates/starter-minimal/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  20. package/templates/starter-minimal/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  21. package/templates/starter-minimal/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
  22. package/templates/starter-minimal/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
  23. package/templates/starter-minimal/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
  24. package/templates/starter-minimal/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
  25. package/templates/starter-minimal/.agents/skills/rivet-ui-design/SKILL.md +41 -0
  26. package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
  27. package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
  28. package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  29. package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  30. package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  31. package/templates/starter-minimal/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
  32. package/templates/starter-minimal/.claude/skills/rivet-add-route/SKILL.md +36 -0
  33. package/templates/starter-minimal/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  34. package/templates/starter-minimal/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
  35. package/templates/starter-minimal/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  36. package/templates/starter-minimal/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  37. package/templates/starter-minimal/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  38. package/templates/starter-minimal/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
  39. package/templates/starter-minimal/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
  40. package/templates/starter-minimal/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
  41. package/templates/starter-minimal/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
  42. package/templates/starter-minimal/.claude/skills/rivet-ui-design/SKILL.md +41 -0
  43. package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
  44. package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
  45. package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  46. package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  47. package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  48. package/templates/starter-minimal/.cursor/rules/rivet-add-route.mdc +37 -0
  49. package/templates/starter-minimal/.cursor/rules/rivet-add-ui-page.mdc +43 -0
  50. package/templates/starter-minimal/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
  51. package/templates/starter-minimal/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
  52. package/templates/starter-minimal/.cursor/rules/rivet-port-react-component.mdc +37 -0
  53. package/templates/starter-minimal/.cursor/rules/rivet-ui-design.mdc +42 -0
  54. package/templates/starter-minimal/.cursor/rules/rivet.mdc +27 -0
  55. package/templates/starter-minimal/.github/copilot-instructions.md +21 -0
  56. package/templates/starter-minimal/AGENTS.md +457 -0
  57. package/templates/starter-minimal/CLAUDE.md +457 -0
  58. package/templates/starter-minimal/README.md +26 -0
  59. package/templates/starter-minimal/_gitignore +3 -0
  60. package/templates/starter-minimal/package.json +28 -0
  61. package/templates/starter-minimal/prerender.js +5 -0
  62. package/templates/starter-minimal/src/document.ts +14 -0
  63. package/templates/starter-minimal/src/routes/index.tsx +16 -0
  64. package/templates/starter-minimal/tsconfig.json +21 -0
  65. package/templates/starter-minimal/vite.config.ts +4 -0
  66. package/templates/starter-server/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
  67. package/templates/starter-server/.agents/skills/rivet-add-route/SKILL.md +36 -0
  68. package/templates/starter-server/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  69. package/templates/starter-server/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
  70. package/templates/starter-server/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  71. package/templates/starter-server/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  72. package/templates/starter-server/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  73. package/templates/starter-server/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
  74. package/templates/starter-server/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
  75. package/templates/starter-server/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
  76. package/templates/starter-server/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
  77. package/templates/starter-server/.agents/skills/rivet-ui-design/SKILL.md +41 -0
  78. package/templates/starter-server/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
  79. package/templates/starter-server/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
  80. package/templates/starter-server/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  81. package/templates/starter-server/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  82. package/templates/starter-server/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  83. package/templates/starter-server/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
  84. package/templates/starter-server/.claude/skills/rivet-add-route/SKILL.md +36 -0
  85. package/templates/starter-server/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  86. package/templates/starter-server/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
  87. package/templates/starter-server/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  88. package/templates/starter-server/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  89. package/templates/starter-server/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  90. package/templates/starter-server/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
  91. package/templates/starter-server/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
  92. package/templates/starter-server/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
  93. package/templates/starter-server/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
  94. package/templates/starter-server/.claude/skills/rivet-ui-design/SKILL.md +41 -0
  95. package/templates/starter-server/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
  96. package/templates/starter-server/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
  97. package/templates/starter-server/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  98. package/templates/starter-server/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  99. package/templates/starter-server/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  100. package/templates/starter-server/.cursor/rules/rivet-add-route.mdc +37 -0
  101. package/templates/starter-server/.cursor/rules/rivet-add-ui-page.mdc +43 -0
  102. package/templates/starter-server/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
  103. package/templates/starter-server/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
  104. package/templates/starter-server/.cursor/rules/rivet-port-react-component.mdc +37 -0
  105. package/templates/starter-server/.cursor/rules/rivet-ui-design.mdc +42 -0
  106. package/templates/starter-server/.cursor/rules/rivet.mdc +27 -0
  107. package/templates/starter-server/.dockerignore +7 -0
  108. package/templates/starter-server/.github/copilot-instructions.md +21 -0
  109. package/templates/starter-server/AGENTS.md +457 -0
  110. package/templates/starter-server/CLAUDE.md +457 -0
  111. package/templates/starter-server/Dockerfile +18 -0
  112. package/templates/starter-server/README.md +30 -0
  113. package/templates/starter-server/_gitignore +3 -0
  114. package/templates/starter-server/package.json +31 -0
  115. package/templates/starter-server/serve.js +6 -0
  116. package/templates/starter-server/src/document.ts +14 -0
  117. package/templates/starter-server/src/routes/index.tsx +41 -0
  118. package/templates/starter-server/src/styles.css +9 -0
  119. package/templates/starter-server/tsconfig.json +21 -0
  120. package/templates/starter-server/vite.config.ts +8 -0
  121. package/templates/starter-static/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
  122. package/templates/starter-static/.agents/skills/rivet-add-route/SKILL.md +36 -0
  123. package/templates/starter-static/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  124. package/templates/starter-static/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
  125. package/templates/starter-static/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  126. package/templates/starter-static/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  127. package/templates/starter-static/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  128. package/templates/starter-static/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
  129. package/templates/starter-static/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
  130. package/templates/starter-static/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
  131. package/templates/starter-static/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
  132. package/templates/starter-static/.agents/skills/rivet-ui-design/SKILL.md +41 -0
  133. package/templates/starter-static/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
  134. package/templates/starter-static/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
  135. package/templates/starter-static/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  136. package/templates/starter-static/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  137. package/templates/starter-static/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  138. package/templates/starter-static/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
  139. package/templates/starter-static/.claude/skills/rivet-add-route/SKILL.md +36 -0
  140. package/templates/starter-static/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  141. package/templates/starter-static/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
  142. package/templates/starter-static/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  143. package/templates/starter-static/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  144. package/templates/starter-static/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  145. package/templates/starter-static/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
  146. package/templates/starter-static/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
  147. package/templates/starter-static/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
  148. package/templates/starter-static/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
  149. package/templates/starter-static/.claude/skills/rivet-ui-design/SKILL.md +41 -0
  150. package/templates/starter-static/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
  151. package/templates/starter-static/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
  152. package/templates/starter-static/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  153. package/templates/starter-static/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  154. package/templates/starter-static/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  155. package/templates/starter-static/.cursor/rules/rivet-add-route.mdc +37 -0
  156. package/templates/starter-static/.cursor/rules/rivet-add-ui-page.mdc +43 -0
  157. package/templates/starter-static/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
  158. package/templates/starter-static/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
  159. package/templates/starter-static/.cursor/rules/rivet-port-react-component.mdc +37 -0
  160. package/templates/starter-static/.cursor/rules/rivet-ui-design.mdc +42 -0
  161. package/templates/starter-static/.cursor/rules/rivet.mdc +27 -0
  162. package/templates/starter-static/.github/copilot-instructions.md +21 -0
  163. package/templates/starter-static/AGENTS.md +457 -0
  164. package/templates/starter-static/CLAUDE.md +457 -0
  165. package/templates/starter-static/README.md +30 -0
  166. package/templates/starter-static/_gitignore +3 -0
  167. package/templates/starter-static/package.json +31 -0
  168. package/templates/starter-static/prerender.js +5 -0
  169. package/templates/starter-static/serve.js +21 -0
  170. package/templates/starter-static/src/components/Counter.tsx +15 -0
  171. package/templates/starter-static/src/document.ts +14 -0
  172. package/templates/starter-static/src/routes/_layout.tsx +18 -0
  173. package/templates/starter-static/src/routes/about.tsx +11 -0
  174. package/templates/starter-static/src/routes/index.tsx +14 -0
  175. package/templates/starter-static/src/styles.css +9 -0
  176. package/templates/starter-static/tsconfig.json +21 -0
  177. package/templates/starter-static/vite.config.ts +9 -0
  178. package/templates/starter-ui/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
  179. package/templates/starter-ui/.agents/skills/rivet-add-route/SKILL.md +36 -0
  180. package/templates/starter-ui/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  181. package/templates/starter-ui/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
  182. package/templates/starter-ui/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  183. package/templates/starter-ui/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  184. package/templates/starter-ui/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  185. package/templates/starter-ui/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
  186. package/templates/starter-ui/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
  187. package/templates/starter-ui/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
  188. package/templates/starter-ui/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
  189. package/templates/starter-ui/.agents/skills/rivet-ui-design/SKILL.md +41 -0
  190. package/templates/starter-ui/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
  191. package/templates/starter-ui/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
  192. package/templates/starter-ui/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  193. package/templates/starter-ui/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  194. package/templates/starter-ui/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  195. package/templates/starter-ui/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
  196. package/templates/starter-ui/.claude/skills/rivet-add-route/SKILL.md +36 -0
  197. package/templates/starter-ui/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
  198. package/templates/starter-ui/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
  199. package/templates/starter-ui/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
  200. package/templates/starter-ui/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
  201. package/templates/starter-ui/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
  202. package/templates/starter-ui/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
  203. package/templates/starter-ui/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
  204. package/templates/starter-ui/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
  205. package/templates/starter-ui/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
  206. package/templates/starter-ui/.claude/skills/rivet-ui-design/SKILL.md +41 -0
  207. package/templates/starter-ui/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
  208. package/templates/starter-ui/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
  209. package/templates/starter-ui/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
  210. package/templates/starter-ui/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
  211. package/templates/starter-ui/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
  212. package/templates/starter-ui/.cursor/rules/rivet-add-route.mdc +37 -0
  213. package/templates/starter-ui/.cursor/rules/rivet-add-ui-page.mdc +43 -0
  214. package/templates/starter-ui/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
  215. package/templates/starter-ui/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
  216. package/templates/starter-ui/.cursor/rules/rivet-port-react-component.mdc +37 -0
  217. package/templates/starter-ui/.cursor/rules/rivet-ui-design.mdc +42 -0
  218. package/templates/starter-ui/.cursor/rules/rivet.mdc +27 -0
  219. package/templates/starter-ui/.github/copilot-instructions.md +21 -0
  220. package/templates/starter-ui/AGENTS.md +457 -0
  221. package/templates/starter-ui/CLAUDE.md +457 -0
  222. package/templates/starter-ui/README.md +32 -0
  223. package/templates/starter-ui/_gitignore +3 -0
  224. package/templates/starter-ui/package.json +34 -0
  225. package/templates/starter-ui/prerender.js +5 -0
  226. package/templates/starter-ui/serve.js +21 -0
  227. package/templates/starter-ui/src/components/LikeButton.tsx +12 -0
  228. package/templates/starter-ui/src/document.ts +14 -0
  229. package/templates/starter-ui/src/routes/index.tsx +39 -0
  230. package/templates/starter-ui/src/styles.css +11 -0
  231. package/templates/starter-ui/tsconfig.json +21 -0
  232. package/templates/starter-ui/vite.config.ts +9 -0
@@ -0,0 +1,457 @@
1
+ <!-- Generated by libs/mcp/ai/sync.ts from libs/mcp/ai/agents.md in the Rivet repository: edit the source there and run the script. In an app created from a starter this file is yours to edit. -->
2
+
3
+ # Rivet app: instructions for AI assistants
4
+
5
+ Read this before editing this project. Rivet looks like React / Solid JSX but is neither: the habits below are compile errors or silent bugs. When unsure, look the API up (MCP server or `llms.txt`, section 11) instead of guessing, and run `npm run check` after every change.
6
+
7
+ ## 1. What Rivet is
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`.
10
+
11
+ ## 2. Differences from React / Solid
12
+
13
+ ### Mental model
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`.
18
+
19
+ ### JSX
20
+
21
+ - `class` (`className` also works; never both on one element). `for` (`htmlFor` also works). Some attrs are camelCase in types: `autoComplete`, `autoFocus`, `tabIndex`.
22
+ - Class object / array: `class={{ active: on() }}`, `class={["a", on() && "b"]}`; toggle one: `class:active={on()}`.
23
+ - `style` must be an object or `style:prop`. Style strings are compile errors (RV5003).
24
+ - Events: `on` + PascalCase from lib.dom (`onClick`, `onInput`, `onKeyDown`, `onFocusOut`). Lowercase `onclick` = error (RV1012). Text inputs: `onInput` (or `model:value`), not React's `onChange`.
25
+ - `window:onResize={fn}` / `document:onKeyDown={fn}` listen globally; `on:my-event={fn}` for custom events; `onClickCapture` for capture phase.
26
+ - Not supported: `dangerouslySetInnerHTML`, `innerHTML`, `srcdoc` (RV1101), `<script>` / `<style>` in JSX (RV1007), children spread `{...items}` (RV1002), `javascript:` / `data:` URLs.
27
+ - Handler value must be a function that does not read state when bound (RV1016):
28
+
29
+ ```tsx
30
+ // Avoid: evaluated once at render
31
+ <button onClick={mode() === "a" ? onA : onB}>Go</button>
32
+ // Prefer
33
+ <button onClick={(e) => (mode() === "a" ? onA(e) : onB(e))}>Go</button>
34
+ ```
35
+
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
+
40
+ ### Signals (`@rivetjs/reactivity`)
41
+
42
+ ```tsx
43
+ import { signal, computed, effect, batch, untrack, onCleanup } from "@rivetjs/reactivity";
44
+
45
+ const count = signal(0); // read: count()
46
+ count.set(5); // write
47
+ count.update((n) => n + 1); // derive from current
48
+ const double = computed(() => count() * 2); // no dependency list; read: double()
49
+ untrack(count); // read without subscribing (there is no .peek())
50
+ ```
51
+
52
+ - Avoid `count(5)` (not a setter) and `count.value`.
53
+ - `computed` / `effect` must be synchronous (an async fn is a type error).
54
+ - Stores: `createStore` from `@rivetjs/reactivity/store`.
55
+
56
+ ### Effects, lifecycle, cleanup
57
+
58
+ - 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.
60
+ - `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`.
62
+ - Async `clientEffect`: only signals read before the first `await` are tracked; returned cleanup works only from non-async effects.
63
+
64
+ ```tsx
65
+ // Avoid (server-only run, document undefined → RV2001)
66
+ document.title = `${unread()} unread`;
67
+ // Prefer
68
+ clientEffect(() => {
69
+ document.title = `${unread()} unread`;
70
+ });
71
+ ```
72
+
73
+ - Browser listeners: `clientEvent(window, "resize", () => width.set(innerWidth))`.
74
+ - Browser-only libraries (maps, charts): `const MapView = clientOnly(() => import("../components/Map.tsx"), undefined, { fallback: () => <p>Loading…</p> });` — importer and selector must be inline.
75
+
76
+ ### Props are live (Solid-style)
77
+
78
+ - `<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`.
80
+ - Object spread `{ ...props }` / `Object.assign` reads every getter once; JSX spreads `<button {...props} />` stay live.
81
+ - Defaults / forwarding: `mergeProps`, `splitProps` from `@rivetjs/runtime/props`.
82
+ - `count={count}` passes the signal itself (child calls `props.count()` / writes it). Use for two-way props.
83
+ - Type `children` (and other JSX-holding props) as `Content`: `import type { Content } from "@rivetjs/runtime/jsx-runtime"`; `props: { title: string; children?: Content }`.
84
+ - A prop holding JSX (`icon={<Star />}`) creates new nodes on every read: read once, or pass a render function.
85
+
86
+ ```tsx
87
+ // Avoid
88
+ export function Badge({ count }: { count: number }) {
89
+ return <b>{count}</b>;
90
+ }
91
+ // Prefer
92
+ export function Badge(props: { count: number }) {
93
+ return <b>{props.count}</b>;
94
+ }
95
+ ```
96
+
97
+ ### Control flow, lists (all from `@rivetjs/runtime/dom`, named imports only — no `rt.For`)
98
+
99
+ ```tsx
100
+ import { For, Show, Switch, Match } from "@rivetjs/runtime/dom";
101
+
102
+ <For each={todos} by={(t) => t.id}>{(todo, index) => <li>{index() + 1}. {todo().text}</li>}</For>
103
+ <Show when={open()} fallback={<p>Closed</p>}>…</Show>
104
+ <Switch fallback={<p>None</p>}><Match when={a()}>A</Match><Match when={b()}>B</Match></Switch>
105
+ ```
106
+
107
+ - `For`: `by` key is required; `item` and `index` are **accessors** (`todo()`, `index()`). Use for lists that change. `.map()` is fine for fixed data (loader data, constants).
108
+ - Conditionals: `cond() && <p />`, ternary, or `Show`. Always read the signal (`cond()`, not `cond`).
109
+ - Other helpers: `Async`, `Suspense`, `ErrorBoundary`, `Dynamic`, `Portal`, `Provider`, `lazyComponent`, `clientOnly`.
110
+ - A function child `{() => …}` re-runs when the signals its body reads change.
111
+
112
+ ### Refs
113
+
114
+ ```tsx
115
+ import { ref } from "@rivetjs/runtime/dom";
116
+ const input = ref<HTMLInputElement>();
117
+ <input ref={input} />; // input() → element | undefined (signal-backed)
118
+ let el!: HTMLCanvasElement;
119
+ <canvas ref={(e) => (el = e)} />; // callback must assign a local (RV2002)
120
+ ```
121
+
122
+ ### Context
123
+
124
+ ```tsx
125
+ // theme.ts
126
+ import { createContext, signal, type Signal } from "@rivetjs/reactivity";
127
+ export const ThemeContext = createContext<Signal<"light" | "dark">>(signal("light"));
128
+ // provider
129
+ import { Provider } from "@rivetjs/runtime/dom";
130
+ <Provider token={ThemeContext} value={theme}>
131
+ …
132
+ </Provider>;
133
+ // reader: at the top of the component, never in a handler or after await
134
+ const theme = useContext(ThemeContext); // import { useContext } from "@rivetjs/reactivity"
135
+ ```
136
+
137
+ - The provided value is read once: provide a signal for changing values. It must be serializable (data / signals, no service objects).
138
+
139
+ ## 3. Resumability rules
140
+
141
+ Everything a handler, effect, computed or binding uses from its component (its **captures**) is written into the HTML. It must be serializable.
142
+
143
+ - OK: primitives, plain objects/arrays, `Date`, `URL`, `URLSearchParams`, `Map`, `Set`, `RegExp`, typed arrays, `ArrayBuffer`, `DataView`, `Error`, 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`, `Error`, `.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(...)`.
159
+ - `.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`).
162
+ - 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
+
164
+ ## 4. File routes
165
+
166
+ ```
167
+ src/
168
+ document.ts HTML shell (DocumentShell, headTags)
169
+ styles.css listed in rivetRouter({ styles })
170
+ routes/
171
+ _layout.tsx wraps its folder and below
172
+ index.tsx /
173
+ about.tsx /about
174
+ blog/[slug].tsx /blog/:slug → params.slug
175
+ docs/[[lang]]/x.tsx optional param
176
+ files/[...path].tsx rest param
177
+ (marketing)/p.tsx /p (group: no segment, shares a layout)
178
+ api/time.ts endpoint (exports onGet/onPost…, returns Response)
179
+ _404.tsx _error.tsx
180
+ ```
181
+
182
+ - Files/folders starting with `_` (except `_layout`, `_404`, `_error`), `.`, `*.test.*`, `*.d.ts` are ignored. `*.server.ts` = server-only module (cannot be imported by browser code, RV3008).
183
+ - `.rivet/` is generated (route types `routes.d.ts`, editor manifest): never edit; `rivet sync` regenerates it.
184
+
185
+ ### Page module exports
186
+
187
+ ```tsx
188
+ import { redirect, notFound, type PageProps, type RequestEvent } from "@rivetjs/router";
189
+
190
+ export const serverLoad = async ({ params }: RequestEvent<"blog/[slug]">) => {
191
+ const post = await findPost(params.slug);
192
+ if (!post) throw notFound();
193
+ return { post };
194
+ };
195
+
196
+ export const serverActions = {
197
+ // <form method="post" action="?/comment">
198
+ comment: async ({ request }: RequestEvent<"blog/[slug]">) => {
199
+ const text = String((await request.formData()).get("text") ?? "");
200
+ await saveComment(text);
201
+ return redirect("/blog", 303);
202
+ },
203
+ };
204
+
205
+ export const head = { title: "Post" }; // or ({ data, params, url }) => Head
206
+
207
+ export default function Post({ data, params }: PageProps<"blog/[slug]">) {
208
+ return <h1>{data.post.title}</h1>;
209
+ }
210
+ ```
211
+
212
+ - Server exports: `serverLoad`, `serverActions`, `serverGuard` (throw/return `redirect()`, `deny()`, `notFound()`, `error()`), `serverHeaders`, `serverEntries` (prerender params), `prerender`, `onRequest` / `onGet` / `onPost` …. The compiler strips them (and code only they use) from the browser build.
213
+ - `event`: `request`, `url`, `params`, `locals`, `signal`, `cookies`, `setHeader`.
214
+ - Page props: `{ data, params, url }` — plain values. Layout props (`LayoutProps`): `data()`, `params()`, `url()` are **accessors**, plus `children`.
215
+
216
+ ```tsx
217
+ // Avoid in a layout
218
+ <header>{data.user}</header>;
219
+ // Prefer
220
+ export default function Layout({ data, children }: LayoutProps<typeof serverLoad>) {
221
+ return (
222
+ <>
223
+ <header>{data().user}</header>
224
+ {children}
225
+ </>
226
+ );
227
+ }
228
+ ```
229
+
230
+ ### Live pages
231
+
232
+ - `export const live = true` + `LivePageProps<typeof serverLoad>`: `data` becomes an accessor (`data().customers`). Same-URL revalidation (a `@rivetjs/forms` submit, `invalidate()`, search-param change) updates `data` in place: the page is not re-rendered, signals (an open drawer, typed input, focus) survive. Use it for forms in drawers / dialogs.
233
+ - Don't copy `data()` into a signal (it never updates). Render lists with `<For each={data().items} by={…}>`.
234
+
235
+ ### Links and navigation
236
+
237
+ - Plain `<a href="/about">` works (client navigation). Typed: `import { Link } from "@rivetjs/router/link"`; `<Link to="/items/42">`.
238
+ - `import { navigate, invalidate, useSearchParams, setSearchParams, beforeLeave, actionResult } from "@rivetjs/router/client"`.
239
+ - Under a base path, plain anchors need `withBase("/x")` from `@rivetjs/router`.
240
+ - Never call `history.pushState` yourself: the router does not see it.
241
+
242
+ ### Modes (`vite.config.ts`)
243
+
244
+ - `rivetRouter()` = `mode: "server"` (render per request; `npm run build` → `dist/server`, `npm start`).
245
+ - `rivetRouter({ mode: "static" })` = prerender to HTML files (`prerender.js` / `prerenderBuild`). `serverLoad` 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.
246
+ - `mode: "csr"` = SPA, no server code.
247
+
248
+ ### Forms (`@rivetjs/forms`)
249
+
250
+ ```ts
251
+ // src/forms/contact.ts
252
+ import { defineForm } from "@rivetjs/forms";
253
+ import { z } from "zod"; // any Standard Schema library
254
+ export const contactForm = defineForm({ name: "contact", schema: z.object({ email: z.string().email() }), validateOn: "blur" });
255
+ ```
256
+
257
+ ```tsx
258
+ // src/routes/contact.tsx
259
+ import { action, invalid } from "@rivetjs/forms/server";
260
+ import { actionResult } from "@rivetjs/router/client";
261
+ import { createForm, fieldId, formId, onInput, onBlur, onSubmit, defaultValue, isInvalid, describedBy } from "@rivetjs/forms";
262
+ import { FieldError, ErrorSummary } from "@rivetjs/forms/components";
263
+
264
+ export const serverActions = {
265
+ contact: action(contactForm, async (value) => {
266
+ /* typed, valid */
267
+ }),
268
+ };
269
+
270
+ export default function Contact() {
271
+ const form = createForm(contactForm, { result: actionResult() });
272
+ return (
273
+ <form
274
+ id={formId(form)}
275
+ method="post"
276
+ action="?/contact"
277
+ onInput={(e) => onInput(contactForm, form, e)}
278
+ onFocusOut={(e) => onBlur(contactForm, form, e)}
279
+ onSubmit={(e) => onSubmit(contactForm, form, e)}
280
+ >
281
+ <label for={fieldId(form, "email")}>Email</label>
282
+ <input
283
+ id={fieldId(form, "email")}
284
+ name="email"
285
+ value={defaultValue(form, "email")}
286
+ aria-invalid={isInvalid(form, "email")}
287
+ aria-describedby={describedBy(form, "email")}
288
+ />
289
+ <FieldError of={form} name="email" />
290
+ <button>Send</button>
291
+ </form>
292
+ );
293
+ }
294
+ ```
295
+
296
+ - Works without JS (plain post, 400 re-render with messages). Server-side rejection: `return invalid({ email: "Taken" })`.
297
+ - A successful submit re-renders a normal page (its signals reset: a "thanks" flag set after `await onSubmit(…)` is lost). Keep client state across a submit with `export const live = true`.
298
+ - In an overlay: live page + `onSubmit={async (e) => (await onSubmit(def, form, e)) && open.set(false)}`.
299
+ - Keeping native `required`? Add `onInvalidCapture={(e) => onInvalid(def, form, e)}`.
300
+
301
+ ## 5. i18n (`@rivetjs/i18n`)
302
+
303
+ - Compile-time: `t("key")` is replaced by the string for the locale being built; no runtime catalog. One client build per locale.
304
+ - `i18n.config.ts` (`locales`, `defaultLocale`, `catalogs: "locales/{locale}.json"`, `routing`); Vite: `plugins: [i18n(config), rivetRouter()]` — `i18n()` from `@rivetjs/i18n/vite`, **before** the router.
305
+ - Catalogs: JSON, nested keys → dotted, values ICU MessageFormat (`"Hello, {name}!"`, plural / select).
306
+
307
+ ```tsx
308
+ import { t } from "@rivetjs/i18n";
309
+ <h1>{t("cart.title")}</h1>
310
+ <p>{t("cart.greeting", { name: props.name })}</p>
311
+ <p>{t("terms", { link: (c) => <a href="/terms">{c}</a> })}</p>
312
+ ```
313
+
314
+ - Key = string literal; params = inline object literal. Unknown key RV4001, missing / extra param RV4002, bad args RV4003, tags in attributes RV4004.
315
+ - Server code outside rendering (emails in actions): `t.in("de", "cart.title")`. Never `String(t(…))` on the server.
316
+ - Generated types: `src/rivet-i18n.d.ts` (do not edit). Locale URLs: `[[lang]]` route folder.
317
+
318
+ ## 6. Styles
319
+
320
+ - 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`.
321
+ - 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)`.
322
+ - Dynamic values: `style={{ "--x": n(), marginTop: "1px" }}` or `style:color={c()}`; `data-*` attributes for state styling.
323
+
324
+ ## 7. Icons (mandatory rule)
325
+
326
+ - Never hand-write SVG paths. Never use Unicode glyphs (✓ × → ★ …) as icons.
327
+ - Use lucide through Rivet's `Icon`:
328
+
329
+ ```tsx
330
+ import { Icon } from "@rivetjs/kit/icon";
331
+ import { Check } from "lucide";
332
+ <Icon icon={Check} size={16} class="text-success" /> // decorative (aria-hidden)
333
+ <Icon icon={Check} title="Done" /> // accessible name
334
+ ```
335
+
336
+ - Props: `icon` (lucide data), `size` (default 24), `color`, `strokeWidth`, `absoluteStrokeWidth`, `title`, any `<svg>` attribute. Requires `lucide` + `@rivetjs/kit` dependencies (starter-ui has both).
337
+
338
+ ## 8. UI components
339
+
340
+ - `@rivetjs/ui`: styled compound components. Import per component subpath (`@rivetjs/ui/button`, `@rivetjs/ui/select`, `@rivetjs/ui/modal`, `@rivetjs/ui/card`, …); the root barrel exists, prefer subpaths. Styles: `@import "@rivetjs/ui/styles";` after Tailwind (tokens, base, utilities; other looks = `@rivetjs/ui/theme` presets); each component module brings its own stylesheet, never import component CSS by hand.
341
+ - Compound parts: `Card.Header`, `Select.Trigger`, `Modal.Content` …; wrapper parts with no own choice fold (`Modal.Content` = Backdrop + Container + Dialog + close button; `<Table aria-label columns items />` = ScrollContainer + Content + header + rows; explicit parts stay for custom layouts). Event props follow React Aria: `onPress` (not `onClick`), `onSelectionChange`, `onOpenChange`.
342
+ - Two-way props take a signal: `isOpen={open}`, `value={selected}`, `selectedKeys={keys}`.
343
+ - State props are live: `isDisabled={busy()}`, `isInvalid={!ok()}`, `variant={v()}`, a plain `value={v()}` (controlled) update in place, focus stays; attributes too (TextField `type={shown() ? "text" : "password"}`, `placeholder`, NumberField `step`, Button `type`). Read once ("Read once at mount" in the API docs): handlers, `id`, `name`, element-picking props (`elementType`, `orientation` of Separator), `autoFocus`, state options (`default*`, `selectionMode`, a collection's `disabledKeys`), MenuTrigger / DialogTrigger `trigger`: re-create with `<Show keyed>` to change them. Slider `minValue` / `maxValue` / `step` / `orientation` and Tabs `orientation` are live.
344
+
345
+ ```tsx
346
+ import { signal } from "@rivetjs/reactivity";
347
+ import { Button } from "@rivetjs/ui/button";
348
+ import { Modal } from "@rivetjs/ui/modal";
349
+
350
+ const open = signal(false);
351
+ <Button onPress={() => open.set(true)}>Open</Button>
352
+ <Modal.Content isOpen={open}>
353
+ <Modal.Header>Title</Modal.Header>
354
+ <Modal.Body>…</Modal.Body>
355
+ <Modal.Footer><Button slot="close">Close</Button></Modal.Footer>
356
+ </Modal.Content>
357
+ ```
358
+
359
+ ```tsx
360
+ import { Label } from "@rivetjs/ui/label";
361
+ import { ListBox } from "@rivetjs/ui/list-box";
362
+ import { Select } from "@rivetjs/ui/select";
363
+ <Select placeholder="Select one" value={state}>
364
+ <Label>State</Label>
365
+ <Select.Trigger>
366
+ <Select.Value />
367
+ <Select.Indicator />
368
+ </Select.Trigger>
369
+ <Select.Popover>
370
+ <ListBox>
371
+ <ListBox.Item id="ca" textValue="California">
372
+ California
373
+ <ListBox.ItemIndicator />
374
+ </ListBox.Item>
375
+ </ListBox>
376
+ </Select.Popover>
377
+ </Select>;
378
+ ```
379
+
380
+ - `@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.
381
+ - 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.
382
+
383
+ ## 9. Commands
384
+
385
+ | Script | Templates | Does |
386
+ | ----------------- | ---------- | ---------------------------------------------------------------------------------- |
387
+ | `npm run dev` | all | Vite dev server (pages resumed; DevTools panel Alt+Shift+R) |
388
+ | `npm run build` | all | `vite build && vite build --ssr` (+ `node prerender.js` for static / ui / minimal) |
389
+ | `npm start` | server | `node serve.js` (`PORT`, `HOST`) |
390
+ | `npm run preview` | static, ui | serve `dist/client` |
391
+ | `npm run check` | all | `rivet sync && rivet-check` |
392
+
393
+ - Create: `npm create rivet@latest my-app -- --template static|server|ui|minimal --yes`.
394
+ - `rivet sync` (bin of `@rivetjs/router`): regenerates `.rivet/` (route types + `.rivet/editor.json`). Run on a fresh clone before `tsc`.
395
+ - `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 .`.
396
+ - Editor: `@rivetjs/typescript-plugin` in `tsconfig.json` shows the same diagnostics + quick fixes while typing.
397
+ - Measure in a build (`npm run build` + start/preview), not dev.
398
+ - Tests (optional setup): `rivetTest()` from `@rivetjs/testing/vite`; `render`, `fire`, `flush` from `@rivetjs/runtime/testing`; `renderToString` from `@rivetjs/runtime/testing/server`.
399
+
400
+ ## 10. RV diagnostic codes
401
+
402
+ - 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.
403
+ - 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.
404
+ - Ranges: 1xxx JSX / attributes, 2xxx resume, 3xxx imports / helpers / route modules, 4xxx i18n, 5xxx styles, 6xxx slots, 7xxx config, 9xxx internal.
405
+
406
+ | Code | Meaning | Fix |
407
+ | --------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
408
+ | RV1000 | Parse error | fix syntax |
409
+ | RV1005 | HTML nesting the parser would repair (`<div>` in `<p>`) | valid nesting |
410
+ | RV1012 | Unknown native event (`onclick`, `onTransitionend`) | `on` + PascalCase |
411
+ | RV1015 | Handler is a literal, not a function | pass a function |
412
+ | RV1016 | Handler expression reads state when it binds | wrap in an arrow |
413
+ | RV1101 | Unsafe / unsupported attribute or URL (`innerHTML`, `javascript:`) | remove; build-time assets |
414
+ | RV1102 | Invalid `preload` value | `eager` / `idle` / `visible` / `intent` / `interaction` |
415
+ | RV2001 | Browser global read during server rendering | handler / `clientEffect` / `clientEvent` / `clientOnly` |
416
+ | RV2002 | Ref callback must assign a local | `ref={(el) => (local = el)}` or `ref()` |
417
+ | RV2003 | Captured class instance | pass data; inside handler; `reconstruct(…)` |
418
+ | RV2102 | Captured instance of a module class that is not a data class (`#private`, base, resource) | inside handler; `reconstruct(…)`; plain fields |
419
+ | RV2006 | JSX inside handler / effect / computed | JSX in component + signal |
420
+ | RV2007 | Resumable code captures `this` | `event.currentTarget` |
421
+ | RV2009 | Two module references share a browser name | rename one import |
422
+ | RV2011 | Mutually recursive functions split by an early exit | move them together before the exit |
423
+ | RV2012–RV2014 | (warning; error on a provable function) `.bind` that may not resume | `const g = f.bind(t, …)`; arrow for other callees |
424
+ | RV2015 | (warning) component returns a function that cannot resume (anon default / wrapper / `let`) | `export function Name(props) { return () => …; }` |
425
+ | RV2101 | (warning) captured value not serializable (type-aware) | as RV2003 |
426
+ | RV3003 / RV3004 | Structural helper namespace import / alias | `import { For } from "@rivetjs/runtime/dom"` |
427
+ | RV3006 | `clientOnly` erasure shape | inline `() => import("…")` |
428
+ | RV3007 | Route module server export shape (`export *` in a route) | declare server exports directly |
429
+ | RV3008 | Server-only code reached from browser code | keep in `serverLoad` / `*.server.ts` |
430
+ | RV3101 | (warning) Link matches no route | fix the path |
431
+ | RV4001–4004 | i18n key / param / argument / tag errors | see section 5 |
432
+ | RV5001–5003 | ``css`…` `` syntax / usage; invalid or unsafe class / style (style string, two class attrs) | style object, one `class` |
433
+ | RV6001–6004 | Slot errors (`<slot>`, `r:name`, `r:slot`) | see JSX reference, slots |
434
+ | RV7101 / RV7102 | `.rivet/editor.json` missing / stale | `rivet sync` |
435
+ | RV9000 / RV9001 | Unclassified / internal compiler error | report it |
436
+
437
+ - Not a code: render-time `… is not serializable` (see section 3).
438
+
439
+ ## 11. Links
440
+
441
+ - MCP server `@rivetjs/mcp` (stdio, offline; docs of all three sites bundled): `npx -y @rivetjs/mcp`, or `npx rivet mcp` when installed in the app. Tools: `search_docs(query, site?)`, `get_doc(path)`, `list_components(package?)`, `get_component(name)` (import path, props / parts, example source), `check_code(code, filename?)`, `explain_diagnostic(code)`. Loop: look up → write → `check_code` → fix every RV error → `npm run check`.
442
+
443
+ ```json
444
+ { "mcpServers": { "rivet": { "command": "npx", "args": ["-y", "@rivetjs/mcp"] } } }
445
+ ```
446
+
447
+ - Claude Code: `claude mcp add rivet -- npx -y @rivetjs/mcp`. Cursor: the JSON above in `.cursor/mcp.json`. VS Code: `.vscode/mcp.json` with `{ "servers": { "rivet": { "type": "stdio", "command": "npx", "args": ["-y", "@rivetjs/mcp"] } } }`.
448
+ - Docs for models: every Rivet docs site (framework, cdk, ui) serves `/llms.txt` (index), `/llms-full.txt` (every page, examples as source) and `<page>.md` next to each HTML page. Guide: "AI assistants" (`docs:/guides/tooling/ai/` in the MCP server).
449
+ - Skills (`.agents/skills/` for Codex/Cursor, `.claude/skills/` for Claude Code) and editor rules (`.cursor/rules/`, `.github/copilot-instructions.md`) in this project cover common tasks: UI/CDK design, add a route, form in a drawer, port a React component, page with `@rivetjs/ui` components, fix a resumability error. `rivet-ui-design` covers color roles, spacing, typography, structure, app layouts, composition and interactions; its references work without MCP. Portable bundles are distributed from the Rivet repository's `skills/` directory.
450
+
451
+ ## Checklist before reporting done
452
+
453
+ - `npm run check` exits 0; `npm run build` passes (prerender / SSR catches non-serializable captures).
454
+ - No destructured props in components that receive changing values; lists that change use `For` with `by`.
455
+ - No browser globals outside handlers / `clientEffect` / `clientEvent` / `clientOnly`.
456
+ - Icons only via `<Icon icon={…} />` + lucide.
457
+ - Never edit `.rivet/`, `src/rivet-i18n.d.ts` or other `Generated … Do not edit` files.
@@ -0,0 +1,30 @@
1
+ # Rivet static starter
2
+
3
+ Pages in `src/routes` become HTML files at build time. Interactive parts keep working in the browser without re-running your components.
4
+
5
+ ```sh
6
+ npm install
7
+ npm run dev # dev server with hot reload
8
+ npm run build # HTML files in dist/client
9
+ npm run preview # serve dist/client
10
+ npm run check # Rivet compiler diagnostics (CI)
11
+ ```
12
+
13
+ - `npm run dev` resumes pages like production until you edit a file; after an edit the page renders in the browser so hot reload keeps state. Code is not bundled, and client-rendered parts use the dev DOM output (production builds use a faster one). Use `npm run build && npm run preview` to judge speed and size.
14
+ - Deploy `dist/client` to any static host.
15
+
16
+ ## Next steps
17
+
18
+ - Edit `src/routes/index.tsx` (home) and `src/routes/about.tsx`; `src/routes/_layout.tsx` wraps every page. `npm run dev` reloads them.
19
+ - Add a page: a new file in `src/routes/` (`blog.tsx` → `/blog`, `blog/[slug].tsx` → `/blog/:slug`). Docs "File routes" (`guides/routing/file-routes`).
20
+ - Deploy: `npm run build`, then upload `dist/client` to any static host (docs "Deploy", `guides/deploy/static-hosts`). Vercel / Netlify / Cloudflare: docs `guides/deploy/<target>` (one `adapter` option in `vite.config.ts`; new projects: `npm create rivet@latest -- --deploy <target>`).
21
+ - Learn the model: docs "Signals" (`learn/signals`), "Resuming" (`learn/resuming`), "Server first" (`learn/server-first`).
22
+ - Docs: https://github.com/rivetjs/rivet/tree/main/apps/docs/content (guides, reference, recipes).
23
+
24
+ ## Tooling
25
+
26
+ - Node.js 24 or newer (`engines` in `package.json`).
27
+ - `npm run check` runs `rivet sync` (writes the gitignored `.rivet/` route types + editor manifest from `vite.config.ts`; also run it before `tsc` on a fresh clone) then `rivet-check`: the Rivet compiler's diagnostics (browser globals read during server rendering, handlers that cannot resume, …) for every `.tsx` file of `tsconfig.json`. Exits 1 on an error; use it in CI.
28
+ - `@rivetjs/devtools` is a dev dependency, so `npm run dev` shows the DevTools panel (Alt+Shift+R or the round **R** button). Builds never include it. Turn it off with `rivetRouter({ devtools: false })`.
29
+ - `tsconfig.json` lists `@rivetjs/typescript-plugin`: editors that use the project's TypeScript show compiler errors while you type. `tsc` ignores the entry.
30
+ - `vite.config.ts` passes options to `rivetRouter()`: `mode`, `styles`, `resume`, `devtools`, `dev.resume`, `transitions`, … All options: the docs page "Configuration" (`reference/config`).
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ .rivet/
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "rivet-starter-static",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=24"
8
+ },
9
+ "scripts": {
10
+ "dev": "vite",
11
+ "build": "vite build && vite build --ssr && node prerender.js",
12
+ "preview": "node serve.js",
13
+ "check": "rivet sync && rivet-check"
14
+ },
15
+ "dependencies": {
16
+ "@rivetjs/reactivity": "experimental",
17
+ "@rivetjs/router": "experimental",
18
+ "@rivetjs/runtime": "experimental"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^24.0.0",
22
+ "@rivetjs/compiler": "experimental",
23
+ "@rivetjs/devtools": "experimental",
24
+ "@rivetjs/language-core": "experimental",
25
+ "@rivetjs/typescript-plugin": "experimental",
26
+ "@tailwindcss/vite": "^4.3.3",
27
+ "tailwindcss": "^4.3.3",
28
+ "typescript": "^5.9.3",
29
+ "vite": "^8.3.2"
30
+ }
31
+ }
@@ -0,0 +1,5 @@
1
+ // Render every page to an HTML file in dist/client (run after `vite build` and `vite build --ssr`).
2
+ import { prerenderBuild } from "@rivetjs/router/static";
3
+
4
+ const report = await prerenderBuild({ dist: "dist" });
5
+ console.log(`Prerendered ${report.pages.length} page(s) into dist/client`);
@@ -0,0 +1,21 @@
1
+ // Preview the built site: serves dist/client like a static host (folders → index.html, 404.html).
2
+ import { readFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { serve } from "@rivetjs/router/node";
5
+
6
+ const client = resolve("dist/client");
7
+ const page = (path) => readFile(resolve(client, "." + path)).catch(() => null);
8
+
9
+ const serving = await serve({
10
+ client,
11
+ port: Number(process.env.PORT ?? 4173),
12
+ handler: async (request) => {
13
+ const { pathname } = new URL(request.url);
14
+ // Files are served before the handler; here only folders and missing paths arrive.
15
+ if (!pathname.endsWith("/") && (await page(pathname + "/index.html"))) return Response.redirect(new URL(pathname + "/", request.url), 308);
16
+ const html = (await page(pathname.replace(/\/?$/, "/") + "index.html")) ?? null;
17
+ if (html) return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
18
+ return new Response((await page("/404.html")) ?? "Not found", { status: 404, headers: { "content-type": "text/html; charset=utf-8" } });
19
+ },
20
+ });
21
+ console.log(`Preview: ${serving.url}`);
@@ -0,0 +1,15 @@
1
+ import { signal } from "@rivetjs/reactivity";
2
+
3
+ /** A button that counts clicks. Its code loads when you first point at it. */
4
+ export function Counter() {
5
+ const count = signal(0);
6
+ return (
7
+ <button
8
+ type="button"
9
+ class="mt-4 rounded-md bg-teal-700 px-4 py-2 font-semibold text-white"
10
+ onClick={() => count.update((n) => n + 1)}
11
+ >
12
+ Clicked {count()} times
13
+ </button>
14
+ );
15
+ }
@@ -0,0 +1,14 @@
1
+ import { headTags, type DocumentShell } from "@rivetjs/router/server";
2
+
3
+ /**
4
+ * The HTML around every page. The route `head` (title, meta, links, scripts) is written in full; `input` carries the
5
+ * stylesheets, the CSP nonce (server pages) and the client-managed marks of `@rivetjs/kit` head components.
6
+ */
7
+ const document: DocumentShell = (input) => ({
8
+ start:
9
+ '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">' +
10
+ headTags({ ...input.head, title: input.head?.title ?? "Rivet app" }, input) +
11
+ "</head><body>",
12
+ end: "</body></html>",
13
+ });
14
+ export default document;
@@ -0,0 +1,18 @@
1
+ import type { LayoutProps } from "@rivetjs/router";
2
+
3
+ /** Wraps every page. */
4
+ export default function Layout({ children }: LayoutProps) {
5
+ return (
6
+ <div class="mx-auto max-w-2xl p-6">
7
+ <nav class="mb-6 flex gap-4 text-sm">
8
+ <a class="text-teal-700 underline" href="/">
9
+ Home
10
+ </a>
11
+ <a class="text-teal-700 underline" href="/about">
12
+ About
13
+ </a>
14
+ </nav>
15
+ <main>{children}</main>
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,11 @@
1
+ export const head = { title: "About" };
2
+
3
+ /** The page at `/about`. */
4
+ export default function About() {
5
+ return (
6
+ <>
7
+ <h1 class="text-2xl font-bold">About</h1>
8
+ <p class="mt-2 text-slate-600">Each file in src/routes is a page. This one is src/routes/about.tsx.</p>
9
+ </>
10
+ );
11
+ }