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.
- package/README.md +8 -1
- package/deploy.d.ts +1 -1
- package/deploy.js +4 -4
- package/index.d.ts +6 -4
- package/index.js +13 -5
- package/main.d.ts +4 -1
- package/main.js +142 -26
- package/output.d.ts +24 -0
- package/output.js +66 -0
- package/package.json +3 -1
- package/prompt.d.ts +1 -0
- package/prompt.js +2 -2
- package/templates/starter-minimal/.agents/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-minimal/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-minimal/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-minimal/.claude/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-minimal/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-minimal/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-minimal/.cursor/rules/rivet-add-route.mdc +1 -1
- package/templates/starter-minimal/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
- package/templates/starter-minimal/.cursor/rules/rivet-port-react-component.mdc +3 -3
- package/templates/starter-minimal/.cursor/rules/rivet.mdc +6 -6
- package/templates/starter-minimal/.github/copilot-instructions.md +6 -6
- package/templates/starter-minimal/AGENTS.md +56 -78
- package/templates/starter-minimal/CLAUDE.md +56 -78
- package/templates/starter-minimal/README.md +3 -3
- package/templates/starter-minimal/package.json +2 -1
- package/templates/starter-minimal/src/document.ts +7 -6
- package/templates/starter-server/.agents/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-server/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-server/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-server/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-server/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-server/.claude/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-server/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-server/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-server/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-server/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-server/.cursor/rules/rivet-add-route.mdc +1 -1
- package/templates/starter-server/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
- package/templates/starter-server/.cursor/rules/rivet-port-react-component.mdc +3 -3
- package/templates/starter-server/.cursor/rules/rivet.mdc +6 -6
- package/templates/starter-server/.github/copilot-instructions.md +6 -6
- package/templates/starter-server/AGENTS.md +56 -78
- package/templates/starter-server/CLAUDE.md +56 -78
- package/templates/starter-server/README.md +5 -4
- package/templates/starter-server/package.json +2 -1
- package/templates/starter-server/serve.js +2 -0
- package/templates/starter-server/src/document.ts +7 -6
- package/templates/starter-static/.agents/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-static/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-static/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-static/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-static/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-static/.claude/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-static/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-static/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-static/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-static/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-static/.cursor/rules/rivet-add-route.mdc +1 -1
- package/templates/starter-static/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
- package/templates/starter-static/.cursor/rules/rivet-port-react-component.mdc +3 -3
- package/templates/starter-static/.cursor/rules/rivet.mdc +6 -6
- package/templates/starter-static/.github/copilot-instructions.md +6 -6
- package/templates/starter-static/AGENTS.md +56 -78
- package/templates/starter-static/CLAUDE.md +56 -78
- package/templates/starter-static/README.md +6 -5
- package/templates/starter-static/package.json +2 -1
- package/templates/starter-static/src/components/Counter.tsx +2 -6
- package/templates/starter-static/src/components/ReadingPlan.tsx +31 -0
- package/templates/starter-static/src/document.ts +7 -6
- package/templates/starter-static/src/routes/about.tsx +14 -1
- package/templates/starter-static/src/routes/index.tsx +2 -2
- package/templates/starter-ui/.agents/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-ui/.agents/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-ui/.agents/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-ui/.agents/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-ui/.agents/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-ui/.claude/skills/rivet-add-route/SKILL.md +1 -1
- package/templates/starter-ui/.claude/skills/rivet-fix-resumability-error/SKILL.md +7 -12
- package/templates/starter-ui/.claude/skills/rivet-port-react-component/SKILL.md +2 -2
- package/templates/starter-ui/.claude/skills/rivet-ui-design/references/component-composition.md +3 -3
- package/templates/starter-ui/.claude/skills/rivet-ui-design/references/rivet-implementation.md +1 -1
- package/templates/starter-ui/.cursor/rules/rivet-add-route.mdc +1 -1
- package/templates/starter-ui/.cursor/rules/rivet-fix-resumability-error.mdc +7 -12
- package/templates/starter-ui/.cursor/rules/rivet-port-react-component.mdc +3 -3
- package/templates/starter-ui/.cursor/rules/rivet.mdc +6 -6
- package/templates/starter-ui/.github/copilot-instructions.md +6 -6
- package/templates/starter-ui/AGENTS.md +56 -78
- package/templates/starter-ui/CLAUDE.md +56 -78
- package/templates/starter-ui/README.md +5 -4
- package/templates/starter-ui/package.json +2 -1
- package/templates/starter-ui/src/components/LikeButton.tsx +1 -1
- package/templates/starter-ui/src/components/ReadingPreferences.tsx +18 -0
- package/templates/starter-ui/src/document.ts +7 -6
- package/templates/starter-ui/src/routes/index.tsx +3 -1
- package/templates/starter-ui/vite.config.ts +3 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rivet-port-react-component
|
|
3
|
-
description: "Port a React (or Solid / Next.js) component or page to Rivet idioms - signals instead of useState, clientEffect instead of useEffect, For with a key, live props, class,
|
|
3
|
+
description: "Port a React (or Solid / Next.js) component or page to Rivet idioms - signals instead of useState, clientEffect instead of useEffect, For with a key, live props, class, ordinary function handlers. Use when the user pastes React code or asks to convert / migrate a component to Rivet."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<!-- Generated by libs/mcp/ai/sync.ts from libs/mcp/ai/skills/port-react-component.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. -->
|
|
@@ -30,7 +30,7 @@ Map each construct (full table: `get_doc("docs:/start/coming-from-other-framewor
|
|
|
30
30
|
Then fix what resuming requires:
|
|
31
31
|
|
|
32
32
|
1. Move browser reads (`window`, `document`, `localStorage`, `matchMedia`) out of the component body into handlers or `clientEffect` (RV2001).
|
|
33
|
-
2. Handlers: inline arrows
|
|
33
|
+
2. Handlers: plain functions (inline arrows, local consts, imported functions); nothing they use needs to serialize. Keep values that differ between server and browser (`Date.now()`, random ids) in a signal or a handler.
|
|
34
34
|
3. Render props and item renderers stay functions (`{(item) => …}`).
|
|
35
35
|
4. Third-party React UI → `@rivetjs/ui` / `@rivetjs/cdk` equivalents: `list_components`, `get_component(name)`; React Aria-style names carry over (`onPress`, `isOpen={signal}`).
|
|
36
36
|
5. `check_code` the result, fix every RV code (`explain_diagnostic`), then `npm run check`.
|
package/templates/starter-static/.claude/skills/rivet-ui-design/references/component-composition.md
CHANGED
|
@@ -40,13 +40,13 @@ Avoid nested interactive targets: an entire clickable card with nested buttons,
|
|
|
40
40
|
|
|
41
41
|
**Collection page:** heading + main create action → labeled search/filter controls → result count and collection → pagination. Show bulk actions only when selection exists; `ActionBar` or `Toolbar` may fit. Align filters and table columns to the same content edges. Provide initial-empty and filtered-empty states separately.
|
|
42
42
|
|
|
43
|
-
**Settings page:** heading/description → grouped `Fieldset` sections → controls/help/errors → save feedback/actions. Group related rows within a section with consistent spacing or separators. Do not wrap every preference in a card. Choose either explicit save or immediate updates for a region and make that behavior visible.
|
|
43
|
+
**Settings page:** heading/description → grouped `Fieldset` sections → controls/help/errors → save feedback/actions. Group related rows within a section with consistent spacing or separators; `CellSwitch`, `CellSelect` and `CellSlider` are full-row settings cells. Do not wrap every preference in a card. Choose either explicit save or immediate updates for a region and make that behavior visible.
|
|
44
44
|
|
|
45
45
|
**Detail/edit page:** summary and status → related content sections → contextual actions. Use a drawer for short contextual edits, a page for long/deep edits, and inline editing for small well-bounded values. Destructive actions belong near the affected object with clear labels and proportionate confirmation.
|
|
46
46
|
|
|
47
|
-
**Dashboard:** lead with metrics that support a decision, then the trend or collection needed to act. `Kpi`, `TrendChip`, and the chart exports may fit. Every chart needs a readable explanation or data alternative; meaningful units, time range and empty states matter more than decorative chart variety.
|
|
47
|
+
**Dashboard:** lead with metrics that support a decision, then the trend or collection needed to act. `Kpi`, `KpiGroup`, `Widget`, `NumberValue`, `TrendChip`, and the chart exports may fit; `ItemCard` / `ItemCardGroup` for settings rows, `Timeline` for activity history. Every chart needs a readable explanation or data alternative; meaningful units, time range and empty states matter more than decorative chart variety.
|
|
48
48
|
|
|
49
|
-
**Command picker:**
|
|
49
|
+
**Command picker:** `Command` (`@rivetjs/ui/command`) is the modal palette: a search field filtering grouped commands with virtual focus, Enter to run, Escape to clear then close; put extra search words in each item's `textValue`. For an inline picker, compose a labeled search field with `Autocomplete` + `Menu`. A command menu and a value-selecting list box are different tasks. Add pending, no-match, error and clear-query behavior; do not write a manual arrow-key loop. For actions on a right click, use `ContextMenu`.
|
|
50
50
|
|
|
51
51
|
## Reuse at the pattern boundary
|
|
52
52
|
|
package/templates/starter-static/.claude/skills/rivet-ui-design/references/rivet-implementation.md
CHANGED
|
@@ -57,7 +57,7 @@ const failures = ["light", "dark"].flatMap((mode) => auditContrast(theme, mode a
|
|
|
57
57
|
- Preserve live props: read `props.name`, and use `splitProps`/`mergeProps` from `@rivetjs/runtime/props` for forwarding. Destructuring live props can read them once.
|
|
58
58
|
- Dynamic collections use `For` from `@rivetjs/runtime/dom` with a stable `by` key; its item and index arguments are accessors. Component render-prop state values can also be accessors; inspect the example/type before reading them.
|
|
59
59
|
- For renderable props use `Content` from `@rivetjs/runtime/jsx-runtime`. For deferred closed overlays/providers on resumable pages use render-function children `{() => …}` instead of already-created JSX. `AppLayout` named slots also take render functions, not JSX values.
|
|
60
|
-
-
|
|
60
|
+
- Handlers are plain functions; keep render output the same on server and browser (no `Date.now()` / random values in render). Browser reads (`window`, `document`, storage) belong in handlers or `clientEffect`, not server render. Import browser runtime helpers from `.tsx` modules for resume rewriting.
|
|
61
61
|
- Icons: `import { Icon } from "@rivetjs/kit/icon"` and icon data from `lucide`, then `<Icon icon={Search} />`. Keep decorative icons hidden and icon-only controls named. Do not invent SVG paths or use text glyphs as icons.
|
|
62
62
|
- CDK is headless: documented `rv-*` classes and `data-*` state support custom CSS, while behavior-critical styles remain inline. Use its overlay primitives, not a hand-built/native-dialog replacement with a different focus/dismissal model.
|
|
63
63
|
|
|
@@ -33,5 +33,5 @@ export default function Post({ data }: PageProps<"blog/[slug]">) {
|
|
|
33
33
|
- Static mode (`rivetRouter({ mode: "static" })`): `load(…)` runs at build time; dynamic `[param]` pages list their params in `entries(…)`.
|
|
34
34
|
- Server-only helpers go in `*.server.ts` files (browser code cannot import them: RV3008).
|
|
35
35
|
|
|
36
|
-
3. Link to it: plain `<a href="/blog/hello">` (client navigation), or typed `<Link to="/blog/hello">` from `@rivetjs/router/link`; programmatic `navigate()` from `@rivetjs/router/client`.
|
|
36
|
+
3. Link to it: plain `<a href="/blog/hello">` (client navigation when the page has the client router; document navigation otherwise), or typed `<Link to="/blog/hello">` from `@rivetjs/router/link`; programmatic `navigate()` from `@rivetjs/router/client`.
|
|
37
37
|
4. Run `npm run check` (`rivet sync` regenerates the typed routes in `.rivet/`, `rivet-check` reports `RV` codes; RV3101 = a link matches no route). Fix every diagnostic (`explain_diagnostic(code)`), then `npm run build`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Diagnose and fix Rivet resumability errors - RV2xxx compiler diagnostics, RV1016 handler errors,
|
|
2
|
+
description: "Diagnose and fix Rivet resumability errors - RV2xxx compiler diagnostics, RV1016 handler errors, RV2107 route data warnings, browser globals during server rendering, a component that renders in the browser instead of resuming. Use when a Rivet build, check or page fails with such an error."
|
|
3
3
|
globs:
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -8,20 +8,15 @@ alwaysApply: false
|
|
|
8
8
|
|
|
9
9
|
# Fix a resumability error
|
|
10
10
|
|
|
11
|
-
A Rivet page is rendered on the server and resumed in the browser:
|
|
11
|
+
A Rivet page is rendered on the server and resumed in the browser: the first time a component is used, the browser runs its code once over the server HTML, starting from the signal values the server wrote. Errors come from code that renders differently on the two sides or from state/data that cannot be written into the page. Keep signal/store values as plain JSON data; the richer resume-format encoding (including registered classes) applies to route data.
|
|
12
12
|
|
|
13
13
|
1. Get the exact diagnostic: `npm run check` (`file:line:col - error RVnnnn`), the Vite overlay (`[RVnnnn] file:line:col`), or `check_code(source, filename)`. For the fix: `explain_diagnostic("RVnnnn")`; `check_code` also returns the quick-fixed source when one applies.
|
|
14
14
|
2. Common codes:
|
|
15
15
|
- RV2001 browser global (`window`, `document`, `localStorage`) read during server rendering → move it into a handler, `clientEffect(() => …)`, `clientEvent(window, "resize", …)` or `clientOnly(() => import("…"))`.
|
|
16
|
-
- RV2003 captured class instance (`new Intl.NumberFormat`, an SDK client) → capture plain data; create the object inside the handler or `clientEffect`, or build it with `reconstruct(() => new X(…))` from `@rivetjs/runtime/dom` (the browser runs the factory on first read). A data class declared in the component module (plain fields) resumes as is.
|
|
17
|
-
- RV2102 captured instance of a module class that cannot resume as data (`#private` field, a decorator, a field arrow using `super`, a base that is not a data class of the module, `window` / `document` / `WebSocket` read while constructing) → create it inside the handler, build it with `reconstruct(() => new X(…))`, keep only plain fields, or add `[serialize]()` + `static [revive](data)` (from `@rivetjs/runtime/resume-format`; package classes: `registerClass(Cls, { id, serialize, revive })` in a module imported by the server code and the resume entry). At render, `RV2102: X has [serialize] but no static [revive]` → add the `static [revive]`.
|
|
18
16
|
- RV1016 handler expression reads state when it binds (`onClick={a() ? f : g}`) → wrap: `onClick={(e) => (a() ? f(e) : g(e))}`.
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- RV2101 (warning, type-aware) a captured value's type does not serialize → as RV2003.
|
|
24
|
-
3. Build / prerender error `Cannot resume <file>#<Component>$<hash>: function is not serializable` (dev server: ``capture `<name>` function is not serializable``; dev build: `capture #<i> …`): a captured function Rivet did not compile (created at runtime by a library such as `memoize(fn)`, a `.bind(…)` written inside an object literal (keep `const inc = add.bind(null, 2)` in a local constant and store `inc`; `.bind(ctx)` of a function using `this` stays RV2007), a method, or held by an object built outside a component: at module level such as `const table = { format: formatEuro }`, or by your own hook / helper such as `function useFormat() { return { format: formatEuro }; }`). Wrap it in an inline arrow, or build the object inside the component (module-level and imported functions used by name there resume by reference). A library object holding functions (a chart, editor, socket client from a package): build it with `reconstruct(() => createChart(…))` from `@rivetjs/runtime/dom` — no value in the page, the browser runs the factory on first read (module level: one server instance per process, never read request state in it). A class instance at render (`X instance is not serializable`): your class → `reconstruct(() => new X(…))` or `[serialize]()` + `static [revive](data)`; a package class → `registerClass(X, { id, serialize, revive })` from `@rivetjs/runtime/resume-format`. `NODE_ENV=development vite build` names the value path. Rules: `get_doc("docs:/best-practices/serializable-captures/")`.
|
|
25
|
-
4. State resets on the first change after resume: a child destructures its props (`function C({ value })`) and is re-created → read `props.value`. Copies of `props.children` in a local are not tracked → render `{props.children}` directly.
|
|
17
|
+
- RV2103 render is not deterministic (`Date.now()`, `Math.random()`, `crypto.randomUUID()` read while rendering) → keep the value in keyed state (`signal(Date.now())`) or read it in a handler / `clientEffect`.
|
|
18
|
+
- RV2104 (warning) keyed state created in a loop → create one signal / store outside the loop and keep the per-item values in it.
|
|
19
|
+
3. RV2107 (runtime warning) route data not encodable: a loader / action / error value holds a function, symbol, promise or an instance of an unregistered class; the page renders on the server but cannot resume. Return plain data; for a class use `registerClass(X, { id, serialize, revive })` from `@rivetjs/runtime/resume-format`, imported on server and browser.
|
|
20
|
+
4. Dev warning that a component's markup differs and it renders in the browser: its render reads something that differs between server and browser (a browser global, a time, a random value, data the browser loads differently) → keep it in a signal, or move it into a `clientEffect`.
|
|
26
21
|
5. `.ts` module importing `@rivetjs/runtime/dom` (bundles the DOM runtime into SSR) → move that code into a `.tsx` file.
|
|
27
|
-
6. Re-run `npm run check` and `npm run build
|
|
22
|
+
6. Re-run `npm run check` and `npm run build`; both must be clean.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Port a React (or Solid / Next.js) component or page to Rivet idioms - signals instead of useState, clientEffect instead of useEffect, For with a key, live props, class,
|
|
2
|
+
description: "Port a React (or Solid / Next.js) component or page to Rivet idioms - signals instead of useState, clientEffect instead of useEffect, For with a key, live props, class, ordinary function handlers. Use when the user pastes React code or asks to convert / migrate a component to Rivet."
|
|
3
3
|
globs:
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -24,14 +24,14 @@ Map each construct (full table: `get_doc("docs:/start/coming-from-other-framewor
|
|
|
24
24
|
| `style="…"` strings | `style={{ color: c() }}` or `style:color={c()}` |
|
|
25
25
|
| `function Badge({ count })` | `function Badge(props: { count: number })` + `props.count` (props are live; destructuring reads once) |
|
|
26
26
|
| `React.lazy`, `<Suspense>` | `lazyComponent`, `Async`, `Suspense` (`@rivetjs/runtime/dom`) |
|
|
27
|
-
| Next.js `getServerSideProps`, Remix `loader` / `action` | `export const loader = load(…)` / `actions(…)` in the route file
|
|
27
|
+
| Next.js `getServerSideProps`, Remix `loader` / `action` | `export const loader = load(…)` / `actions(…)` in the route file |
|
|
28
28
|
| `useRouter().push`, `useSearchParams` | `navigate()`, `useSearchParams()` from `@rivetjs/router/client` |
|
|
29
29
|
| icon components / inline SVG | `<Icon icon={Check} />` from `@rivetjs/kit/icon` + `import { Check } from "lucide"` |
|
|
30
30
|
|
|
31
31
|
Then fix what resuming requires:
|
|
32
32
|
|
|
33
33
|
1. Move browser reads (`window`, `document`, `localStorage`, `matchMedia`) out of the component body into handlers or `clientEffect` (RV2001).
|
|
34
|
-
2. Handlers: inline arrows
|
|
34
|
+
2. Handlers: plain functions (inline arrows, local consts, imported functions); nothing they use needs to serialize. Keep values that differ between server and browser (`Date.now()`, random ids) in a signal or a handler.
|
|
35
35
|
3. Render props and item renderers stay functions (`{(item) => …}`).
|
|
36
36
|
4. Third-party React UI → `@rivetjs/ui` / `@rivetjs/cdk` equivalents: `list_components`, `get_component(name)`; React Aria-style names carry over (`onPress`, `isOpen={signal}`).
|
|
37
37
|
5. `check_code` the result, fix every RV code (`explain_diagnostic`), then `npm run check`.
|
|
@@ -10,18 +10,18 @@ alwaysApply: true
|
|
|
10
10
|
|
|
11
11
|
This project uses Rivet, a compiled, resumable JSX framework. It is NOT React and NOT Solid. Full instructions: `AGENTS.md` at the project root (read it before editing). Docs: the Rivet MCP server (`npx -y @rivetjs/mcp`: `search_docs`, `get_doc`, `get_component`, `check_code`, `explain_diagnostic`) or each docs site's `/llms.txt`.
|
|
12
12
|
|
|
13
|
-
- Components run
|
|
14
|
-
- Component
|
|
13
|
+
- Components run on the server; the browser resumes the page and runs a component once, over the server HTML, when it is first used. No hooks, no re-renders, no dependency arrays.
|
|
14
|
+
- Component bodies render on the server and run again on resume: no `window` / `document` / `localStorage` there (RV2001). Browser code goes in handlers, `clientEffect` / `clientEvent` / `clientOnly` from `@rivetjs/runtime/dom`.
|
|
15
15
|
- Signals (`@rivetjs/reactivity`): `const n = signal(0)`; read `n()`; write `n.set(1)` / `n.update((v) => v + 1)`; `computed(() => n() * 2)`; `untrack(n)` (no `.peek`).
|
|
16
16
|
- JSX: `class` (not `className`), `for`, events `onClick` / `onInput` (PascalCase; not `onclick`, not React's `onChange` for text), `model:value={signal}` for two-way inputs, style objects (no style strings).
|
|
17
17
|
- Props are live: read `props.x` where it is shown; destructuring reads once. `mergeProps` / `splitProps` from `@rivetjs/runtime/props`.
|
|
18
18
|
- Lists: `<For each={items} by={(i) => i.id}>{(item, index) => … item() … index()}</For>`; `Show`, `Switch` / `Match`, `ref()` from `@rivetjs/runtime/dom` (named imports).
|
|
19
|
-
-
|
|
19
|
+
- Resume: render the same thing on both sides (no `Date.now()` / `Math.random()` in a render, RV2103: keep it in a signal); no `signal()` in a loop (RV2104); keep signal/store values as plain JSON data; route loader/action data must be resume-format encodable (RV2107 runtime warning; classes via `registerClass`). No capture rules: a component given functions / services / JSX props adopts with its parent.
|
|
20
20
|
- Render props and item renderers are functions (`{(item) => …}`, `({ close }) => …`).
|
|
21
21
|
- Import `@rivetjs/runtime/dom` only from `.tsx` files, never from `.ts`.
|
|
22
22
|
- Routes: `src/routes/**.tsx`; `_layout.tsx`, `[param]`, `_404.tsx`; page exports `load(…)`, `actions(…)`, `head`, `live`; page `data` is a value, layout `data()` / `params()` / `url()` are accessors; `export const live = true` makes page `data` an accessor updated in place (forms in drawers).
|
|
23
|
-
- Forms: `@rivetjs/forms` (`defineForm`, `createForm`, `onSubmit
|
|
24
|
-
- i18n: `t("key", { param })` from `@rivetjs/i18n`, compile-time;
|
|
23
|
+
- Forms: `@rivetjs/forms` (`defineForm`, `createForm`, `onSubmit`); `FieldError` from `@rivetjs/forms/components`; server `action` / `invalid` from `@rivetjs/forms/server`.
|
|
24
|
+
- i18n: `t("key", { param })` from `@rivetjs/i18n`, compile-time; prefer literal keys (fixed-prefix template keys also support plain text without params).
|
|
25
25
|
- Icons: `<Icon icon={Check} />` from `@rivetjs/kit/icon` with lucide (`import { Check } from "lucide"`). Never inline SVG paths, never Unicode glyphs as icons.
|
|
26
26
|
- UI: `@rivetjs/ui/<component>` (styled compound parts `Select.Trigger`, `onPress`, two-way props take a signal); headless `@rivetjs/cdk/<module>`.
|
|
27
|
-
- After every change: `npm run check` (`rivet sync && rivet-check`) and fix every `RVnnnn` diagnostic; never edit `.rivet/` or `Generated … Do not edit` files.
|
|
27
|
+
- After every change: `npm run check` (`rivet sync && rivet-check && tsgo`) and fix every `RVnnnn` diagnostic; never edit `.rivet/` or `Generated … Do not edit` files.
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
This project uses Rivet, a compiled, resumable JSX framework. It is NOT React and NOT Solid. Full instructions: `AGENTS.md` at the project root (read it before editing). Docs: the Rivet MCP server (`npx -y @rivetjs/mcp`: `search_docs`, `get_doc`, `get_component`, `check_code`, `explain_diagnostic`) or each docs site's `/llms.txt`.
|
|
6
6
|
|
|
7
|
-
- Components run
|
|
8
|
-
- Component
|
|
7
|
+
- Components run on the server; the browser resumes the page and runs a component once, over the server HTML, when it is first used. No hooks, no re-renders, no dependency arrays.
|
|
8
|
+
- Component bodies render on the server and run again on resume: no `window` / `document` / `localStorage` there (RV2001). Browser code goes in handlers, `clientEffect` / `clientEvent` / `clientOnly` from `@rivetjs/runtime/dom`.
|
|
9
9
|
- Signals (`@rivetjs/reactivity`): `const n = signal(0)`; read `n()`; write `n.set(1)` / `n.update((v) => v + 1)`; `computed(() => n() * 2)`; `untrack(n)` (no `.peek`).
|
|
10
10
|
- JSX: `class` (not `className`), `for`, events `onClick` / `onInput` (PascalCase; not `onclick`, not React's `onChange` for text), `model:value={signal}` for two-way inputs, style objects (no style strings).
|
|
11
11
|
- Props are live: read `props.x` where it is shown; destructuring reads once. `mergeProps` / `splitProps` from `@rivetjs/runtime/props`.
|
|
12
12
|
- Lists: `<For each={items} by={(i) => i.id}>{(item, index) => … item() … index()}</For>`; `Show`, `Switch` / `Match`, `ref()` from `@rivetjs/runtime/dom` (named imports).
|
|
13
|
-
-
|
|
13
|
+
- Resume: render the same thing on both sides (no `Date.now()` / `Math.random()` in a render, RV2103: keep it in a signal); no `signal()` in a loop (RV2104); keep signal/store values as plain JSON data; route loader/action data must be resume-format encodable (RV2107 runtime warning; classes via `registerClass`). No capture rules: a component given functions / services / JSX props adopts with its parent.
|
|
14
14
|
- Render props and item renderers are functions (`{(item) => …}`, `({ close }) => …`).
|
|
15
15
|
- Import `@rivetjs/runtime/dom` only from `.tsx` files, never from `.ts`.
|
|
16
16
|
- Routes: `src/routes/**.tsx`; `_layout.tsx`, `[param]`, `_404.tsx`; page exports `load(…)`, `actions(…)`, `head`, `live`; page `data` is a value, layout `data()` / `params()` / `url()` are accessors; `export const live = true` makes page `data` an accessor updated in place (forms in drawers).
|
|
17
|
-
- Forms: `@rivetjs/forms` (`defineForm`, `createForm`, `onSubmit
|
|
18
|
-
- i18n: `t("key", { param })` from `@rivetjs/i18n`, compile-time;
|
|
17
|
+
- Forms: `@rivetjs/forms` (`defineForm`, `createForm`, `onSubmit`); `FieldError` from `@rivetjs/forms/components`; server `action` / `invalid` from `@rivetjs/forms/server`.
|
|
18
|
+
- i18n: `t("key", { param })` from `@rivetjs/i18n`, compile-time; prefer literal keys (fixed-prefix template keys also support plain text without params).
|
|
19
19
|
- Icons: `<Icon icon={Check} />` from `@rivetjs/kit/icon` with lucide (`import { Check } from "lucide"`). Never inline SVG paths, never Unicode glyphs as icons.
|
|
20
20
|
- UI: `@rivetjs/ui/<component>` (styled compound parts `Select.Trigger`, `onPress`, two-way props take a signal); headless `@rivetjs/cdk/<module>`.
|
|
21
|
-
- After every change: `npm run check` (`rivet sync && rivet-check`) and fix every `RVnnnn` diagnostic; never edit `.rivet/` or `Generated … Do not edit` files.
|
|
21
|
+
- After every change: `npm run check` (`rivet sync && rivet-check && tsgo`) and fix every `RVnnnn` diagnostic; never edit `.rivet/` or `Generated … Do not edit` files.
|
|
@@ -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
|
|
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
|
|
16
|
-
- Component
|
|
17
|
-
-
|
|
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
|
|
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:(…)" })`.
|
|
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:
|
|
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)} />; //
|
|
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.
|
|
136
|
+
- The provided value is read once: provide a signal for changing values.
|
|
138
137
|
|
|
139
138
|
## 3. Resumability rules
|
|
140
139
|
|
|
141
|
-
|
|
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
|
-
-
|
|
144
|
-
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
-
|
|
161
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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`
|
|
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 `
|
|
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
|
|
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 (
|
|
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
|
|
408
|
-
| --------------- |
|
|
409
|
-
| RV1000 | Parse error
|
|
410
|
-
| RV1005 | HTML nesting the parser would repair (`<div>` in `<p>`)
|
|
411
|
-
| RV1012 | Unknown native event (`onclick`, `onTransitionend`)
|
|
412
|
-
| RV1015 | Handler is a literal, not a function
|
|
413
|
-
| RV1016 | Handler expression reads state when it binds
|
|
414
|
-
| RV1101 | Unsafe / unsupported attribute or URL (`innerHTML`, `javascript:`)
|
|
415
|
-
| RV1102 | Invalid `preload` value
|
|
416
|
-
| RV2001 | Browser global read during server rendering
|
|
417
|
-
|
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
|
421
|
-
|
|
|
422
|
-
|
|
|
423
|
-
|
|
|
424
|
-
|
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
429
|
-
|
|
|
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
|
|
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.
|