create-rindle 0.10.4 → 0.10.5

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 CHANGED
@@ -1,90 +1,17 @@
1
1
  # create-rindle
2
2
 
3
- Scaffold a new **SQL-first [Rindle](https://github.com/rindle-sh/rindle) app on
4
- [TanStack Start](https://tanstack.com/start)** in one command.
3
+ Create a [Rindle](https://rindle.sh) application with TanStack Start, live queries,
4
+ optimistic writes, SQL migrations, and local guidance for coding agents.
5
5
 
6
6
  ```bash
7
7
  npm create rindle@latest my-app
8
- # or
9
- npx create-rindle my-app
10
- # or: pnpm create rindle my-app · yarn create rindle my-app · bun create rindle my-app
11
- ```
12
-
13
- Then:
14
-
15
- ```bash
16
8
  cd my-app
17
- pnpm dev
18
- ```
19
-
20
- Full docs — what the template contains and how to grow it:
21
- **[rindle.sh/docs/create-rindle](https://rindle.sh/docs/create-rindle)** · markdown
22
- mirror: [`create-rindle.md`](https://rindle.sh/docs/create-rindle.md) · for agents:
23
- [llms.txt](https://rindle.sh/llms.txt)
24
-
25
- ## What you get
26
-
27
- A minimal but real three-tier Rindle app (a tiny forum-of-rooms with **live message counts**):
28
-
29
- - **Browser** — a TanStack Start SPA whose data layer is Rindle's in-process wasm IVM engine: local
30
- instant reads, optimistic writes, fragment-rooted `useRoot` reads, and a dev-only Rindle devtools
31
- pane.
32
- - **API server** — the authority: named-query resolution, authoritative SQL mutators, and policy (a
33
- `"spam"` rejection demo shows the optimistic snap-back + toast).
34
- - **Daemon** (`rindled`) — owns the SQLite data + live incremental views, streamed to subscribers.
35
-
36
- The schema is **SQL-first**: `migrations/*.sql` is the source of truth, and the `@rindle/client`
37
- schema is generated from it into `shared/schema.gen.ts` (`rindle up --migrate --gen shared/schema.gen.ts --watch` in
38
- the dev loop), so the TypeScript can't drift from the DDL.
39
-
40
- The prebuilt `rindle` + `rindled` binaries come from `@rindle/cli` (per-platform, installed as a dev
41
- dependency) — **no Rust toolchain required**.
42
-
43
- ## Fragment flow in the template
44
-
45
- The generated routes use the current co-located fragment pattern:
46
-
47
- - `src/components/*.queries.ts` defines each component's `defineFragment` beside the named
48
- `defineQuery` that roots it.
49
- - The home route declares `roomsQuery()` through `@rindle/tanstack`'s `rindle.loader`, which seeds
50
- SSR and waits for the same live query on client navigation, then calls
51
- `useRoot(roomsQuery, RoomCardFragment)` to receive opaque room refs.
52
- - Row components call `useFragment(RoomCardFragment, room)` to open narrow local reads without a
53
- new server subscription.
54
- - The room detail route calls `useRoot(roomDetailQuery, id)` when the route itself owns the root
55
- fields and passes child message refs down with `fragmentKey(ref)` list keys.
56
-
57
- ## Usage
58
-
59
- ```
60
- create-rindle [directory] [options]
61
-
62
- Options:
63
- --no-install Don't run the package-manager install after scaffolding
64
- --pm <name> Package manager: npm | pnpm | yarn | bun (default: auto-detected)
65
- --link Internal: wire @rindle/* to this monorepo's workspace (for apps/* in the repo)
66
- -h, --help Show help
9
+ npm run dev
67
10
  ```
68
11
 
69
- `--link` is for developing the template *inside* the Rindle monorepo: it rewrites the `@rindle/*`
70
- dependencies to `workspace:*` and switches `vite.config.ts` / `tsconfig.json` to the `@rindle/source`
71
- aliases the in-repo examples use, so a scaffold dropped into `apps/` typechecks against source. CI
72
- uses it to typecheck the generated app on every change.
73
-
74
-
75
- ## Devtools in generated apps
76
-
77
- The default template mounts `@rindle/react-devtools` in development and attaches
78
- `@rindle/devtools` to the generated Rindle client. Click the floating **🌊 Rindle** launcher to
79
- inspect the optimistic mutation timeline, live queries, and raw delta stream. Both packages are
80
- imported behind `import.meta.env.DEV`, so they tree-shake out of production builds.
81
-
82
- ## Templates
83
-
84
- | name | description |
85
- |---|---|
86
- | `minimal` (default) | rooms + messages with live counts, optimistic writes, SSR, the auth seam |
87
-
88
- ## Requirements
12
+ Published scaffold releases use matching Rindle packages and documentation.
13
+ Use `--no-agents` to skip local documentation and skill installation.
89
14
 
90
- The generated app runs the `.ts` server files via Node's built-in type stripping — **Node ≥ 22.18**.
15
+ - [Scaffold guide](https://rindle.sh/docs/create-rindle)
16
+ - [Agent installation and version checks](https://rindle.sh/docs/for-agents)
17
+ - [Documentation index for agents](https://rindle.sh/llms.txt)
package/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // `npm create rindle@latest [dir]` / `npx create-rindle [dir]` — scaffold a SQL-first Rindle app on
3
- // TanStack Start. Zero runtime dependencies: argument parsing, a minimal interactive prompt, and the
4
- // template copy all run on Node built-ins, so this boots with nothing installed.
3
+ // TanStack Start. The bundled agent context and scaffold use Node built-ins. No native
4
+ // binaries are needed until the generated application's development server starts.
5
5
  //
6
6
  // create-rindle my-app # scaffold ./my-app (the only template today: "minimal")
7
7
  // create-rindle my-app --no-install # skip the package-manager install
@@ -12,6 +12,7 @@ import { spawnSync } from "node:child_process";
12
12
  import { createInterface } from "node:readline/promises";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import { dirname, join, resolve } from "node:path";
15
+ import { setupAgentContext } from "@rindle/agent-context";
15
16
 
16
17
  import { isValidPackageName, packageNameFromDir, scaffold } from "./lib/scaffold.mjs";
17
18
 
@@ -19,11 +20,12 @@ const here = dirname(fileURLToPath(import.meta.url));
19
20
  const TEMPLATE = "minimal";
20
21
 
21
22
  function parseArgs(argv) {
22
- const opts = { dir: undefined, install: true, pm: undefined, link: false, help: false };
23
+ const opts = { dir: undefined, install: true, agents: true, pm: undefined, link: false, help: false };
23
24
  for (let i = 0; i < argv.length; i++) {
24
25
  const a = argv[i];
25
26
  if (a === "--help" || a === "-h") opts.help = true;
26
27
  else if (a === "--no-install") opts.install = false;
28
+ else if (a === "--no-agents") opts.agents = false;
27
29
  else if (a === "--link") opts.link = true;
28
30
  else if (a === "--pm") opts.pm = argv[++i];
29
31
  else if (a.startsWith("--pm=")) opts.pm = a.slice("--pm=".length);
@@ -31,6 +33,10 @@ function parseArgs(argv) {
31
33
  else if (opts.dir === undefined) opts.dir = a;
32
34
  else fail(`unexpected argument: ${a}`);
33
35
  }
36
+ if (opts.pm !== undefined && !["npm", "pnpm", "yarn", "bun"].includes(opts.pm)) {
37
+ fail("--pm must be npm, pnpm, yarn, or bun");
38
+ }
39
+ if (argv.includes("--pm") && opts.pm === undefined) fail("--pm requires a package manager");
34
40
  return opts;
35
41
  }
36
42
 
@@ -47,6 +53,7 @@ Usage:
47
53
 
48
54
  Options:
49
55
  --no-install Don't run the package-manager install after scaffolding
56
+ --no-agents Skip local documentation, skill, and agent instruction setup
50
57
  --pm <name> Package manager to use (npm | pnpm | yarn | bun); default: auto-detect
51
58
  --link Internal: wire @rindle/* to this monorepo's workspace (for apps/* in the repo)
52
59
  -h, --help Show this help
@@ -94,7 +101,7 @@ async function main() {
94
101
  process.stdout.write(`\n Scaffolding a Rindle app in ${targetDir}\n`);
95
102
  let created;
96
103
  try {
97
- created = await scaffold({ templateDir, targetDir, projectName, link: opts.link });
104
+ created = await scaffold({ templateDir, targetDir, projectName, link: opts.link, agents: opts.agents });
98
105
  } catch (err) {
99
106
  fail(err instanceof Error ? err.message : String(err));
100
107
  }
@@ -105,8 +112,12 @@ async function main() {
105
112
  process.stdout.write(`\n Installing dependencies with ${pm}…\n\n`);
106
113
  const install = spawnSync(pm, ["install"], { cwd: targetDir, stdio: "inherit", shell: process.platform === "win32" });
107
114
  if (install.status !== 0) {
108
- process.stdout.write(`\n Install failed or was skipped — run \`${pm} install\` yourself.\n`);
115
+ process.stderr.write(`\n The project was created, but dependency installation failed${install.error ? `: ${install.error.message}` : "."}\n`);
116
+ process.stderr.write(` Run \`${pm} install\` in ${targetDir}${opts.agents ? `, then \`${pm === "npm" ? "npx" : `${pm} exec`} rindle agents setup\` to verify its documentation` : ""}.\n`);
117
+ process.exitCode = 1;
118
+ return;
109
119
  }
120
+ if (opts.agents) await setupAgentContext({ projectDir: targetDir });
110
121
  }
111
122
 
112
123
  const run = pm === "npm" ? "npm run" : pm;
@@ -114,11 +125,15 @@ async function main() {
114
125
  `\n Done. Next steps:\n\n` +
115
126
  ` cd ${dir}\n` +
116
127
  (opts.install ? "" : ` ${pm} install\n`) +
128
+ (!opts.install && opts.agents ? ` ${pm === "npm" ? "npx" : `${pm} exec`} rindle agents setup\n` : "") +
117
129
  ` ${run} dev\n\n` +
118
130
  ` The migrations in migrations/*.sql are the source of truth — \`${run} dev\` applies them,\n` +
119
131
  ` generates shared/schema.gen.ts, and boots the topology pair + Vite (app + /api/rindle routes).\n` +
120
132
  ` Open two browser windows, create a room, and watch writes sync live.\n\n`,
121
133
  );
134
+ if (opts.agents) {
135
+ process.stdout.write(" Agent guidance is in AGENTS.md and CLAUDE.md. Local docs: .rindle/agent-context/index.md\n");
136
+ }
122
137
  }
123
138
 
124
139
  main().catch((err) => fail(err instanceof Error ? err.stack ?? err.message : String(err)));
package/lib/scaffold.mjs CHANGED
@@ -1,15 +1,17 @@
1
1
  // The scaffold engine: copy a template tree into a target directory, substituting the project name
2
2
  // and renaming the `_`-prefixed dotfiles npm refuses to publish (`_gitignore` → `.gitignore`). Pure
3
- // Node built-ins, no dependencies — so `npm create rindle` runs with nothing installed.
3
+ // Node built-ins plus the bundled agent-context package; no native tools are needed.
4
4
  //
5
5
  // `--link` mode (internal/dev) rewrites the generated app to consume the @rindle/* packages from the
6
6
  // surrounding pnpm WORKSPACE instead of npm: deps become `workspace:*`, and vite/tsconfig switch to the
7
7
  // `@rindle/source` aliases the in-repo examples use. That lets us scaffold straight into `apps/` and
8
8
  // typecheck the output against the real source — the "CI typechecks the generated app" discipline.
9
9
 
10
- import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
11
- import { existsSync } from "node:fs";
10
+ import { cp, lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
12
11
  import { basename, join, relative } from "node:path";
12
+ import { setupAgentContext } from "@rindle/agent-context";
13
+
14
+ const SCAFFOLD_VERSION = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")).version;
13
15
 
14
16
  /** The token the template files carry wherever the project's package name belongs. */
15
17
  export const PROJECT_NAME_TOKEN = "__PROJECT_NAME__";
@@ -143,17 +145,42 @@ export async function applyWorkspaceLinks(targetDir) {
143
145
  await writeFile(join(targetDir, "tsconfig.json"), WORKSPACE_TSCONFIG);
144
146
  }
145
147
 
146
- /** Full scaffold: validate the target, copy the template, optionally apply workspace links. */
147
- export async function scaffold({ templateDir, targetDir, projectName, link = false }) {
148
- if (existsSync(targetDir)) {
149
- const entries = await readdir(targetDir).catch(() => []);
148
+ /** Pin the generated app to the same release as its template and documentation. */
149
+ export async function pinRindleVersion(targetDir, version) {
150
+ if (version === "0.0.0") return; // An unreleased source checkout has no registry release to pin.
151
+ const pkgPath = join(targetDir, "package.json");
152
+ const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
153
+ for (const field of ["dependencies", "devDependencies"]) {
154
+ for (const name of Object.keys(pkg[field] ?? {})) {
155
+ if (name.startsWith("@rindle/")) pkg[field][name] = version;
156
+ }
157
+ }
158
+ await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
159
+ }
160
+
161
+ /** Copy a new application, then install its bundled agent documentation. */
162
+ export async function scaffold({ templateDir, targetDir, projectName, link = false, agents = true, rindleVersion = SCAFFOLD_VERSION }) {
163
+ const targetStat = await lstat(targetDir).catch(error => {
164
+ if (error.code === "ENOENT") return null;
165
+ throw error;
166
+ });
167
+ if (targetStat) {
168
+ if (targetStat.isSymbolicLink() || !targetStat.isDirectory()) {
169
+ throw new Error(`target must be a real directory, not a file or symlink: ${targetDir}`);
170
+ }
171
+ const entries = await readdir(targetDir);
150
172
  if (entries.length > 0) {
151
173
  throw new Error(`target directory is not empty: ${targetDir}`);
152
174
  }
153
175
  }
154
176
  const created = await copyTemplate(templateDir, targetDir, projectName);
155
177
  if (link) await applyWorkspaceLinks(targetDir);
156
- return created;
178
+ else await pinRindleVersion(targetDir, rindleVersion);
179
+ if (agents) {
180
+ const context = await setupAgentContext({ projectDir: targetDir, allowUninstalled: true, deferVersionCheck: true });
181
+ created.push(...context.files);
182
+ }
183
+ return [...new Set(created)].sort();
157
184
  }
158
185
 
159
186
  // Re-exported for the CLI's "is this a sane package name?" check.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rindle",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,9 @@
21
21
  "engines": {
22
22
  "node": ">=22"
23
23
  },
24
+ "dependencies": {
25
+ "@rindle/agent-context": "0.10.5"
26
+ },
24
27
  "keywords": [
25
28
  "rindle",
26
29
  "tanstack",
@@ -1,94 +1,46 @@
1
- # AGENTS.md — working on __PROJECT_NAME__
1
+ # Working on __PROJECT_NAME__
2
2
 
3
- Guidance for coding agents (and new humans). This is a **Rindle** app — an
4
- incremental-view-maintenance (IVM) engine keeps every registered query's result
5
- exact on each write instead of re-running it. Three tiers: a TanStack Start SPA
6
- running the wasm engine in-process, an API authority, and the data tier — the one
7
- topology (design 214): a `rindle-replicator` write-master + a `rindled`
8
- read-follower (`followers = 1` = the colocated pair). The correctness contract
9
- everywhere is **view-after-write == fresh-query**.
3
+ This project uses TanStack Start and Rindle. The browser has optimistic writes.
4
+ The application's API authorizes named queries and mutations. `rindle.ncl`
5
+ selects a replicated data tier with one write master and one read follower.
10
6
 
11
- Rindle docs are served as raw markdown for LLMs: index at
12
- <https://rindle.sh/llms.txt>, the whole app track in one file at
13
- <https://rindle.sh/llms-app.txt>.
7
+ ## Project commands
14
8
 
15
- ## Commands
9
+ - `pnpm dev` starts the data tier and application, applies migrations, and generates schema types.
10
+ - `pnpm typecheck` generates the route tree and checks TypeScript.
11
+ - `pnpm migrate` applies the files in `migrations/` to the configured local data tier.
12
+ - `pnpm rindle:deploy` deploys the data tier to Rindle Cloud after `rindle login`.
13
+ - `pnpm rindle:migrate:cloud` applies migrations to the linked Cloud database.
16
14
 
17
- - `pnpm dev` — the one lifecycle command: `rindle dev` evaluates `rindle.ncl`
18
- once, supervises the `rindle-replicator` write-master + `rindled` follower +
19
- fleet edge (prebuilt binaries from `@rindle/cli`, no Rust toolchain), waits for
20
- them, applies `migrations/*.sql`, regenerates `shared/schema.gen.ts`, then runs
21
- Vite on :3000 with `RINDLE_URL` + `RINDLE_DATABASE_TOKEN`. It watches migration
22
- and follower-schema changes and tears down the whole process tree together.
23
- - `pnpm typecheck` — regenerates the route tree, then `tsc --noEmit`.
24
- - `pnpm migrate` — one-shot `rindle migrate apply` against the unified ingress derived
25
- from `rindle.ncl` (the follower's `/migrate` is write-fenced). The dev loop already
26
- applies on boot + on every `migrations/` change.
27
- - `pnpm rindle:deploy` / `pnpm rindle:migrate:cloud` — deploy the data tier to
28
- Rindle Cloud (reads `rindle.ncl`, the same file `rindle up` runs locally; run
29
- `rindle login` once first) and push `migrations/*.sql` to the deployed master.
15
+ With npm, use `npm run` followed by the script name.
30
16
 
31
- ## Rules that keep the app correct
17
+ ## Application files
32
18
 
33
- Break one of these and the app goes subtly wrong. Treat violations as bugs in
34
- review:
35
-
36
- 1. **SQL is the source of truth.** Change the schema by **adding** a
37
- `migrations/*.sql` file (SQL DDL: `CREATE TABLE` / `ADD COLUMN` /
38
- `CREATE INDEX`, plus the destructive `DROP TABLE` / `DROP COLUMN` /
39
- `DROP INDEX` — no `RENAME`; every table a single primary key). **Never
40
- hand-edit `shared/schema.gen.ts`** — it is generated from the live daemon
41
- and overwritten on the next migration.
42
- 2. **Mutators are one isomorphic body, deterministic and replayable**
43
- (`shared/app-def.ts`): a generator that `yield`s logical ops
44
- (`yield tx.insert(...)`), paired with its zod arg schema via
45
- `shared(args, gen)`. No `Date.now()`, no `Math.random()`, no I/O — the client
46
- re-invokes the body on every rebase. Generate ids and timestamps at the
47
- callsite and pass them in as args; the acting user is `ctx.user` (injected
48
- per tier), never an arg.
49
- 3. **The server drives the same body** — `sharedApiMutators` in
50
- `server/app-api.ts` auto-wraps the whole registry (parse the untrusted wire
51
- args through each mutator's `.args`, inject the authenticated principal,
52
- render every op to SQL). Add an explicit entry ONLY for server-only
53
- authority the client must not predict (a policy guard, a raw `tx.exec`
54
- relational gate). Only `(name, args)` ever crosses the wire; `throw` to
55
- hard-reject (the optimistic write snaps back on its own; write no rollback
56
- code).
57
- 4. **Remote subscriptions must be named.** Define queries with `defineQuery` in
58
- `src/components/*.queries.ts` and register them in `server/app-api.ts`. An
59
- ad-hoc `store.query.…` builder resolves **locally only** — it never opens a
60
- server subscription.
61
- 5. **Database tokens are server-only.** `rindle dev` injects the one application connection as
62
- `RINDLE_URL` + `RINDLE_DATABASE_TOKEN`; the bearer must never reach the browser. The browser
63
- learns only the public WebSocket endpoint + placement ticket from its query-lease response.
64
- Never copy topology ports into package scripts or add a browser config endpoint.
65
- 6. **Subscribe to windows, not whole tables** — order + `limit`, and ratchet
66
- `limit` up for "load more". The engine keeps the window (and any `countAs`)
67
- exact as rows enter and leave.
68
- 7. **Keep `*.queries.ts` modules React-free** — no `.tsx` imports. The browser,
69
- the API authority, and the SSR loader all import these same modules.
70
- 8. **Declare route reads through `rindle.loader(...)`** (`src/rindle-tanstack.ts`).
71
- It owns server preloading, client navigation readiness/cancellation, and
72
- blocking stale reloads. Keep `rindle.Provider` at the root; do not recreate
73
- the old `useMatches()` merge or a separate `<RindleSSR>` wrapper.
74
-
75
- ## File map
76
-
77
- | Path | What it is |
19
+ | Path | Purpose |
78
20
  | --- | --- |
79
- | `migrations/*.sql` | the real schema — the only place DDL lives |
80
- | `shared/schema.gen.ts` | **generated** table schema — do not edit |
81
- | `shared/app-def.ts` | the shared contract: relationships, query builder, isomorphic mutators |
82
- | `src/components/*.queries.ts` | named queries + fragments, co-located with their components |
83
- | `src/rindle-client.ts` | the one-call browser wire-up (`createRindleClient`) |
84
- | `src/rindle-tanstack.ts` | the shared `rindle.loader` + `rindle.Provider` integration |
85
- | `server/app-api.ts` | the authority: `registerQueries` + `sharedApiMutators` + server-only policy |
86
- | `src/routes/api.rindle.*.tsx` | TanStack Start server routes exposing the authority over HTTP |
87
- | `rindle.ncl` | the one topology (the colocated pair) — `rindle up` runs it locally, `rindle deploy` provisions it |
88
- | `src/ssr.ts` | SSR preload of the same named queries for first paint |
89
-
90
- ## Reading more
91
-
92
- Per-page markdown mirrors live at `https://rindle.sh/docs/<slug>.md`. Most
93
- relevant here: `synced-app-quickstart`, `client`, `api-server`, `schema`,
94
- `fragments`, `ssr`, `supported-queries-ts`, `change-model`.
21
+ | `migrations/*.sql` | Ordered database migrations |
22
+ | `shared/schema.gen.ts` | Generated schema types. Do not edit this file. |
23
+ | `shared/app-def.ts` | Relationships, query builder, and shared mutators |
24
+ | `src/components/*.queries.ts` | Named queries and component fragments |
25
+ | `src/rindle-client.ts` | Browser client initialization |
26
+ | `src/rindle-tanstack.ts` | Route loaders and the shared Rindle provider |
27
+ | `src/routes/__root.tsx` | Application root with `rindle.Provider` |
28
+ | `src/ssr.ts` | Server preloading through the application's API authority |
29
+ | `server/app-api.ts` | Query registration and authoritative mutation handlers |
30
+ | `server/auth-dev.ts` | Demonstration identity provider |
31
+ | `src/routes/api.rindle.*.tsx` | Browser-facing API routes |
32
+ | `rindle.ncl` | Local and Cloud data-tier configuration |
33
+
34
+ Keep shared query modules independent of React components and server secrets.
35
+ Use the existing `rindle.loader` and `rindle.Provider` integration for route reads.
36
+
37
+ ## Demonstration identity
38
+
39
+ `server/auth-dev.ts` accepts an unverified `x-rindle-user` header.
40
+ Reads are public. The header lets the demonstration create writes without an account.
41
+ Before production, replace this provider with verified application authentication and review access policies.
42
+
43
+ ## Product documentation
44
+
45
+ The agent setup command adds the local documentation index and skill below.
46
+ If setup was skipped, start with <https://rindle.sh/docs/for-agents.md>.
@@ -20,10 +20,10 @@ Three tiers, same as the Rindle flagship examples:
20
20
  reaches it through TanStack Start **server routes** (`src/routes/api.rindle.{query,read,mutate}.tsx`,
21
21
  via `server/rindle-http.ts`) that run in the same server as the app, and SSR calls the very same
22
22
  factory **in-process** (no network hop).
23
- - **Data tier** — the one topology (design 214): a `rindle-replicator` **write-master** plus a
23
+ - **Data tier** — the topology selected by this template: a `rindle-replicator` **write-master** plus a
24
24
  `rindled` **read-follower** that owns the live IVM and streams normalized deltas to subscribers.
25
25
  Writes land on the master; the follower serves reads. `followers = 1` is the *colocated pair* —
26
- both processes on one box, the smallest shape.
26
+ both processes on one box.
27
27
 
28
28
  ## SQL is the source of truth
29
29
 
@@ -65,7 +65,7 @@ see the rejection path (the optimistic write snaps back + a toast).
65
65
 
66
66
  ## Deploy
67
67
 
68
- `rindle.ncl` describes the **one topology** — the same file `rindle up` runs locally. To run the
68
+ `rindle.ncl` describes this application’s **data topology** — the same file `rindle up` runs locally. To run the
69
69
  **data tier** on Rindle Cloud:
70
70
 
71
71
  ```bash
@@ -85,6 +85,23 @@ public WebSocket endpoint from that same URL, so there is no browser-side topolo
85
85
  the deployment has one follower or many. Set the server-only `RINDLE_WS_URL` override only when a
86
86
  host exposes WebSocket ingress at a different origin.
87
87
 
88
+ ## Coding agents
89
+
90
+ The default scaffold installs a local documentation snapshot and the Rindle skill.
91
+ `AGENTS.md` contains this application's commands and file map. Its managed block
92
+ and `CLAUDE.md` point to `.rindle/agent-context/index.md`.
93
+
94
+ After installing or upgrading Rindle packages, run:
95
+
96
+ ```bash
97
+ npx rindle agents setup
98
+ npx rindle agents setup --check
99
+ ```
100
+
101
+ The second command checks the installed files and package versions without writing files.
102
+ If you used `--no-agents`, setup installs the guidance when you need it.
103
+ See [agent setup](https://rindle.sh/docs/for-agents) for the compatibility rules.
104
+
88
105
  ## Devtools
89
106
 
90
107
  In development a floating **🌊 Rindle** devtools pane is mounted (`src/devtools.tsx`): a live view of the