From 43041c38906e4fd880c59543bb73c31de4091934 Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:46 +0000 Subject: [PATCH 01/16] Docs: move pages into their new sections Before this change, the docs were organized around where an application runs: `full_stack_apps/`, `ai_chat_apps/`, and a catch-all `learn_more/`. That layout presented Reboot as a framework for AI chat apps with a web option on the side, and it kept the reference pages away from the guides that lead into them. Reboot is a general-purpose application framework, and its docs should be organized around what an application is: how its API is defined, implemented, and called, who its users are, and the surfaces it serves. This commit is the pure re-layout, kept free of prose changes so that it reviews as renames (`git diff -M`) and so that the rewrites in the following commits can be read against the moved text. Old URLs keep resolving through redirects, because generated servicer boilerplate, example READMEs, error messages, and reboot.dev link to them. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- .../docs/{learn_more => }/agents.mdx | 6 +- documentation/docs/ai_chat_apps/examples.mdx | 4 +- .../ai_chat_apps/get_started_claude_code.mdx | 8 +- .../docs/ai_chat_apps/get_started_codex.mdx | 8 +- .../{learn_more => }/call/from_mcp_client.mdx | 6 +- .../call/from_outside_your_app.mdx | 12 +- .../docs/{learn_more => }/call/from_react.mdx | 90 +++---- .../call/from_within_your_app.mdx | 46 ++-- .../docs/{learn_more => }/call/overview.mdx | 32 +-- .../docs/{learn_more => }/call/via_http.mdx | 4 +- .../docs/{learn_more => }/define/methods.mdx | 6 +- .../docs/{learn_more => }/define/overview.mdx | 20 +- .../docs/{learn_more => }/define/protobuf.mdx | 14 +- .../docs/{learn_more => }/define/pydantic.mdx | 12 +- .../docs/{learn_more => }/define/zod.mdx | 4 +- documentation/docs/deploy_on_reboot_cloud.md | 2 +- .../docs/deploy_operate/import_export.md | 2 +- documentation/docs/develop_locally.md | 6 +- .../docs/{learn_more => }/errors.mdx | 38 +-- .../claude_code.mdx} | 18 +- .../codex.mdx} | 18 +- .../examples.md | 20 +- .../python.mdx | 20 +- .../quickstart.mdx} | 8 +- .../react.mdx | 18 +- .../typescript.mdx | 22 +- .../docs/{learn_more => }/idempotency.mdx | 12 +- .../application.mdx} | 10 +- .../{learn_more => }/implement/readers.mdx | 0 .../{learn_more => }/implement/servicers.mdx | 20 +- .../implement/transactions.mdx | 22 +- .../{learn_more => }/implement/workflows.mdx | 42 +-- .../{learn_more => }/implement/writers.mdx | 2 +- documentation/docs/learn_more/mcp_apps.mdx | 8 +- .../docs/library_services/mailgun.md | 2 +- .../library_services/oauth_token_manager.mdx | 4 +- .../docs/library_services/ordered_map.mdx | 6 +- .../docs/library_services/overview.md | 2 +- .../docs/{learn_more => }/nonlocal.md | 0 documentation/docs/overview.mdx | 14 +- .../docs/{tools/cli.md => rbt_cli.md} | 0 .../docs/{learn_more => }/secrets.mdx | 2 +- .../docs/{learn_more => }/side_effects.md | 6 +- .../what_is.mdx => surfaces/ai_chat.mdx} | 10 +- .../implement => surfaces}/ui_methods.mdx | 14 +- documentation/docs/{learn_more => }/tasks.mdx | 32 +-- .../docs/{learn_more => }/testing.md | 30 +-- .../auth.mdx => users/authorization.mdx} | 20 +- .../external_apis.mdx} | 12 +- documentation/docusaurus.config.ts | 91 ++++++- documentation/package-lock.json | 41 +++ documentation/package.json | 1 + documentation/sidebars.js | 251 +++++++++++------- 53 files changed, 649 insertions(+), 449 deletions(-) rename documentation/docs/{learn_more => }/agents.mdx (98%) rename documentation/docs/{learn_more => }/call/from_mcp_client.mdx (96%) rename documentation/docs/{learn_more => }/call/from_outside_your_app.mdx (89%) rename documentation/docs/{learn_more => }/call/from_react.mdx (84%) rename documentation/docs/{learn_more => }/call/from_within_your_app.mdx (86%) rename documentation/docs/{learn_more => }/call/overview.mdx (84%) rename documentation/docs/{learn_more => }/call/via_http.mdx (92%) rename documentation/docs/{learn_more => }/define/methods.mdx (94%) rename documentation/docs/{learn_more => }/define/overview.mdx (57%) rename documentation/docs/{learn_more => }/define/protobuf.mdx (81%) rename documentation/docs/{learn_more => }/define/pydantic.mdx (95%) rename documentation/docs/{learn_more => }/define/zod.mdx (91%) rename documentation/docs/{learn_more => }/errors.mdx (78%) rename documentation/docs/{full_stack_apps/get_started_claude_code.mdx => get_started/claude_code.mdx} (91%) rename documentation/docs/{full_stack_apps/get_started_codex.mdx => get_started/codex.mdx} (92%) rename documentation/docs/{full_stack_apps => get_started}/examples.md (86%) rename documentation/docs/{full_stack_apps => get_started}/python.mdx (93%) rename documentation/docs/{ai_chat_apps/get_started.mdx => get_started/quickstart.mdx} (99%) rename documentation/docs/{full_stack_apps => get_started}/react.mdx (92%) rename documentation/docs/{full_stack_apps => get_started}/typescript.mdx (93%) rename documentation/docs/{learn_more => }/idempotency.mdx (81%) rename documentation/docs/{learn_more/applications.mdx => implement/application.mdx} (95%) rename documentation/docs/{learn_more => }/implement/readers.mdx (100%) rename documentation/docs/{learn_more => }/implement/servicers.mdx (80%) rename documentation/docs/{learn_more => }/implement/transactions.mdx (83%) rename documentation/docs/{learn_more => }/implement/workflows.mdx (91%) rename documentation/docs/{learn_more => }/implement/writers.mdx (97%) rename documentation/docs/{learn_more => }/nonlocal.md (100%) rename documentation/docs/{tools/cli.md => rbt_cli.md} (100%) rename documentation/docs/{learn_more => }/secrets.mdx (98%) rename documentation/docs/{learn_more => }/side_effects.md (94%) rename documentation/docs/{ai_chat_apps/what_is.mdx => surfaces/ai_chat.mdx} (95%) rename documentation/docs/{learn_more/implement => surfaces}/ui_methods.mdx (95%) rename documentation/docs/{learn_more => }/tasks.mdx (79%) rename documentation/docs/{learn_more => }/testing.md (86%) rename documentation/docs/{learn_more/auth.mdx => users/authorization.mdx} (94%) rename documentation/docs/{learn_more/identity_and_external_apis.mdx => users/external_apis.mdx} (97%) diff --git a/documentation/docs/learn_more/agents.mdx b/documentation/docs/agents.mdx similarity index 98% rename from documentation/docs/learn_more/agents.mdx rename to documentation/docs/agents.mdx index 09a0339bd..26aa17768 100644 --- a/documentation/docs/learn_more/agents.mdx +++ b/documentation/docs/agents.mdx @@ -1,7 +1,7 @@ # Agents Reboot's `reboot.agents` package lets you run [Pydantic AI](https://ai.pydantic.dev/) -agents inside Reboot [workflows](/learn_more/implement/workflows) with +agents inside Reboot [workflows](/implement/workflows) with **durable, replay-safe execution of model calls and tool calls** -- you get back the same answer on re-runs without re-hitting the LLM provider, and tool side-effects @@ -22,7 +22,7 @@ When you wrap a Pydantic AI agent with Reboot's `Agent`: - **Every tool call** -- whether registered via `@agent.tool`, passed as `tools=`, or contributed via `toolsets=` -- is memoized too, so a tool's side effects aren't repeated on replay. During [effect - validation](/learn_more/side_effects) tools DO re-run, which helps + validation](/side_effects) tools DO re-run, which helps surface non-determinism early. - **Streaming runs are drained inside a memoized block.** Even on the first run, events arrive in a single batch once the model call @@ -235,7 +235,7 @@ Reboot wraps every tool call in `at_least_once`. On replay, the stored return value is reused without invoking your tool function. You therefore can't rely on your tool function being invoked again on replay. Note that during [effect -validation](/learn_more/side_effects), Reboot re-runs your tool to +validation](/side_effects), Reboot re-runs your tool to verify your code is deterministic, so if you are performing external side-effects inside your tool body you need to ensure they are done idempotently (or consider using an `at_most_once` block instead, but diff --git a/documentation/docs/ai_chat_apps/examples.mdx b/documentation/docs/ai_chat_apps/examples.mdx index c24bdad2f..64e142ff5 100644 --- a/documentation/docs/ai_chat_apps/examples.mdx +++ b/documentation/docs/ai_chat_apps/examples.mdx @@ -17,7 +17,7 @@ It demonstrates: * `UI` methods that render React views (wiki, page, transcript) inside the AI chat interface. -* A long-running [`workflow`](/learn_more/implement/workflows) +* A long-running [`workflow`](/implement/workflows) method (`Wiki.ingest`) that acts as a per-wiki background agent, reacting to new transcripts as they arrive. @@ -72,5 +72,5 @@ It demonstrates: and browser contexts. * An `App.tsx` React component that implements the UI. -Follow the [AI Chat App quickstart](/ai_chat_apps/get_started) +Follow the [AI Chat App quickstart](/get_started/quickstart) to build this example from scratch. diff --git a/documentation/docs/ai_chat_apps/get_started_claude_code.mdx b/documentation/docs/ai_chat_apps/get_started_claude_code.mdx index f6cea39cc..25cdcf38c 100644 --- a/documentation/docs/ai_chat_apps/get_started_claude_code.mdx +++ b/documentation/docs/ai_chat_apps/get_started_claude_code.mdx @@ -138,7 +138,7 @@ my-app/ The API definition uses the same `User` + application type pattern described in the -[hand-written guide](/ai_chat_apps/get_started): `User` is the +[hand-written guide](/get_started/quickstart): `User` is the auto-constructed entry point whose methods create other state types, and those types' methods use `mcp=Tool()` to be callable by the AI. UI methods use `UI()` and render React inside the MCP @@ -191,11 +191,11 @@ wizard. * **[Get Started (with Codex)](/ai_chat_apps/get_started_codex)** — the equivalent agent-driven workflow for Codex. -* **[Get Started (hand-written)](/ai_chat_apps/get_started)** — +* **[Get Started (hand-written)](/get_started/quickstart)** — build the same app step by step to understand every file. -* **[UIs for AI Chat Apps](/learn_more/implement/ui_methods)** — +* **[UIs for AI Chat Apps](/surfaces/ui_methods)** — the full reference for `UI` methods. -* **[Creating tools](/learn_more/define/pydantic#creating-tools-for-the-ai)** — +* **[Creating tools](/define/pydantic#creating-tools-for-the-ai)** — control which methods are callable by the AI. * **[AI Chat App Examples](/ai_chat_apps/examples)** — more examples to explore. diff --git a/documentation/docs/ai_chat_apps/get_started_codex.mdx b/documentation/docs/ai_chat_apps/get_started_codex.mdx index 12d717773..fc3a3ecc3 100644 --- a/documentation/docs/ai_chat_apps/get_started_codex.mdx +++ b/documentation/docs/ai_chat_apps/get_started_codex.mdx @@ -159,7 +159,7 @@ my-app/ ``` The API definition uses the same `User` + application type pattern -described in the [hand-written guide](/ai_chat_apps/get_started): +described in the [hand-written guide](/get_started/quickstart): `User` is the auto-constructed entry point whose methods create other state types, and those types' methods use `mcp=Tool()` to be callable by the AI. UI methods use `UI()` and render React inside the MCP host. @@ -211,11 +211,11 @@ wizard. * **[Get Started (with Claude Code)](/ai_chat_apps/get_started_claude_code)** — the equivalent agent-driven workflow for Claude Code. -* **[Get Started (hand-written)](/ai_chat_apps/get_started)** — +* **[Get Started (hand-written)](/get_started/quickstart)** — build the same app step by step to understand every file. -* **[UIs for AI Chat Apps](/learn_more/implement/ui_methods)** — +* **[UIs for AI Chat Apps](/surfaces/ui_methods)** — the full reference for `UI` methods. -* **[Creating tools](/learn_more/define/pydantic#creating-tools-for-the-ai)** — +* **[Creating tools](/define/pydantic#creating-tools-for-the-ai)** — control which methods are callable by the AI. * **[AI Chat App Examples](/ai_chat_apps/examples)** — more examples to explore. diff --git a/documentation/docs/learn_more/call/from_mcp_client.mdx b/documentation/docs/call/from_mcp_client.mdx similarity index 96% rename from documentation/docs/learn_more/call/from_mcp_client.mdx rename to documentation/docs/call/from_mcp_client.mdx index 8b842670b..0104c5c4d 100644 --- a/documentation/docs/learn_more/call/from_mcp_client.mdx +++ b/documentation/docs/call/from_mcp_client.mdx @@ -43,7 +43,7 @@ This opens MCPJam in your browser (if no tab opens, visit [http://localhost:6274](http://localhost:6274)), pointed straight at your app's MCP endpoint with OAuth enabled. From there you can browse your app's tools, call them, and see -[`UI`](/learn_more/implement/ui_methods) method React apps rendered +[`UI`](/surfaces/ui_methods) method React apps rendered inline. ## Set up a secure tunnel @@ -95,7 +95,7 @@ its URL for you. :::warning Free ngrok tunnels don't work with Claude -Claude renders [`UI`](/learn_more/implement/ui_methods) methods as +Claude renders [`UI`](/surfaces/ui_methods) methods as HTML with ` - - -``` - - - - -\` UI _and_ the standalone \`web/\` SPA you'll add below. -- \`RBT_BUILD_TARGET=mcp: vite build\` bundles one \`UI\` method's - React app into a single, self-contained - \`dist/mcp//index.html\` using - [\`vite-plugin-singlefile\`](https://www.npmjs.com/package/vite-plugin-singlefile). - The Reboot server serves it at \`/__/frontend/mcp//index.html\`. -- \`RBT_BUILD_TARGET=web vite build\` bundles the \`web/\` SPA into - \`dist/web/\`, served at \`/__/frontend/web/\`. - -The config auto-discovers MCP UIs from the \`mcp/\` directory, so -adding a new \`mcp//index.html\` is all it takes to build it. - -`}> - - - -```sh -touch frontend/vite.config.ts -``` - - - - -```ts -// Vite configuration for Reboot UIs. -// -// One config drives three jobs. The dev server is selected by -// `command === "serve"`; the two build shapes are selected by the -// `RBT_BUILD_TARGET` env var, set per UI by `build.mjs`. `mode` keeps -// its conventional Vite values — `development` (serve) / `production` -// (build) — so the matching `.env.` files load as usual: -// -// * `vite` (serve): a single dev server that delivers HMR for every -// `mcp/` UI AND the standalone `web/` SPA, all under -// `base: "/__/frontend/"`. Envoy proxies that prefix to this dev -// server (`run --config=hmr`). -// * `RBT_BUILD_TARGET=mcp: vite build`: builds one MCP UI into -// a single, self-contained `dist/mcp//index.html` (assets -// inlined via `vite-plugin-singlefile`). The framework serves it -// at `/__/frontend/mcp//index.html` in dist mode. -// * `RBT_BUILD_TARGET=web vite build`: builds the `web/` SPA into -// `dist/web/` with normal (non-inlined) assets. Its `base` is -// `/__/frontend/web/` so asset URLs resolve when served at that -// prefix. In Vite's `production` mode, it reads -// `web/.env.production`. -import fs from "fs"; -import path from "path"; -import react from "@vitejs/plugin-react"; -import { defineConfig, type Plugin } from "vite"; -import { viteSingleFile } from "vite-plugin-singlefile"; - -// A served directory under `/__/frontend/` requested without its -// trailing slash (e.g. `/__/frontend/web`) doesn't match Vite's static -// index.html serving, which expects `/__/frontend/web/`, so it 404s. -// Redirect the slash-less form to the canonical trailing-slash form so -// the `web/` SPA and each `mcp/` UI load with or without the -// trailing slash — matching how the framework's dist-mode server -// behaves. Only a path that resolves to a real directory with an -// `index.html` is redirected, so Vite's own internal module URLs -// (`@vite/client`, `@react-refresh`) and source or asset files fall -// through untouched. -function redirectFrontendDirTrailingSlash(root: string): Plugin { - const prefix = "/__/frontend/"; - return { - name: "reboot-frontend-dir-trailing-slash", - configureServer(server) { - server.middlewares.use((req, res, next) => { - const url = req.url ?? ""; - const queryAt = url.indexOf("?"); - const pathname = queryAt === -1 ? url : url.slice(0, queryAt); - if (pathname.startsWith(prefix) && !pathname.endsWith("/")) { - const subpath = pathname.slice(prefix.length); - if (fs.existsSync(path.join(root, subpath, "index.html"))) { - const query = queryAt === -1 ? "" : url.slice(queryAt); - // 302 (not 301): a permanent redirect would be cached by the - // browser, which is wrong for a dev server whose routes can - // change between runs. - res.statusCode = 302; - res.setHeader("Location", `${pathname}/${query}`); - res.end(); - return; - } - } - next(); - }); - }, - }; -} - -// When a `web/` SPA exists, treat it as the dev server's home: the bare -// `/`, `/__/frontend`, and `/__/frontend/` otherwise 404 (there's no -// index there), so silently redirect them to `/__/frontend/web/`. Also -// print the friendlier `http:///` at startup instead of the -// `/__/frontend/` base URL Vite would otherwise show (which is a dead -// link). -function serveWebAppAtRoot(root: string): Plugin { - const webIndex = path.resolve(root, "web", "index.html"); - const target = "/__/frontend/web/"; - const homes = new Set(["/", "/__/frontend", "/__/frontend/"]); - return { - name: "reboot-serve-web-app-at-root", - configureServer(server) { - if (!fs.existsSync(webIndex)) return; - server.middlewares.use((req, res, next) => { - const url = req.url ?? ""; - const queryAt = url.indexOf("?"); - const pathname = queryAt === -1 ? url : url.slice(0, queryAt); - if (homes.has(pathname)) { - const query = queryAt === -1 ? "" : url.slice(queryAt); - res.statusCode = 302; - res.setHeader("Location", target + query); - res.end(); - return; - } - next(); - }); - // Print `http:///` instead of the `/__/frontend/` base. - const printUrls = server.printUrls.bind(server); - server.printUrls = () => { - const urls = server.resolvedUrls; - if (urls) { - const toRoot = (u: string) => u.replace(/\/__\/frontend\/$/, "/"); - urls.local = urls.local.map(toRoot); - urls.network = urls.network.map(toRoot); - } - printUrls(); - }; - }, - }; -} - -// Auto-discover MCP UIs: every `mcp//` with an `index.html`. -// There may be no `mcp/` directory at all (a web-only frontend, or -// one whose last MCP UI was removed), so guard the read. -const mcpDir = path.resolve(__dirname, "mcp"); -const mcpNames: string[] = fs.existsSync(mcpDir) - ? fs - .readdirSync(mcpDir) - .filter((name) => fs.existsSync(path.resolve(mcpDir, name, "index.html"))) - : []; - -// Path alias for API imports (`@api/...` -> `./api/...`). -const resolve = { - alias: { - "@api": path.resolve(__dirname, "./api"), - }, - dedupe: ["react", "react-dom", "zod"], -}; - -export default defineConfig(({ command }) => { - // Dev server: serves both the MCP UIs and the `web/` SPA. - // - // UIs use a double iframe architecture: - // MCP Host -> srcdoc (origin=null) -> iframe (origin=localhost:9991) - // - // The inner iframe loads from Envoy ("/__/frontend/**"), which - // proxies to Vite. Because the inner iframe has a real origin, - // Vite's URLs work normally. `base: "/__/frontend/"` ensures all - // paths route through Envoy. - // - // The standalone `web/` SPA is served at `/__/frontend/web/` by this - // same server. It reaches the backend via `VITE_REBOOT_URL` (see - // `web/.env.development`) rather than its own origin, so opening it - // straight from this dev server exercises the real cross-origin - // frontend/backend path — the same shape as a production deploy. - // - // Hot Module Replacement works automatically: Vite's client connects - // to the page's origin, and Envoy proxies WebSocket upgrades to - // Vite. This also works with tunnels (ngrok) since the tunnel - // points to Envoy. - if (command === "serve") { - const port = parseInt(process.env.RBT_VITE_PORT || "4444", 10); - - return { - plugins: [ - react(), - redirectFrontendDirTrailingSlash(__dirname), - serveWebAppAtRoot(__dirname), - ], - root: ".", - // Read `.env*` from `web/` (alongside the SPA), so the `web/` - // SPA's `VITE_REBOOT_URL` is picked up by both this serve and the - // `web/` production build, which is also rooted there. - envDir: path.resolve(__dirname, "web"), - resolve, - base: "/__/frontend/", - server: { - port, - strictPort: true, - // Listen on all interfaces since requests come through - // Envoy (and tunnels). - host: true, - allowedHosts: true, - }, - }; - } - - // Which UI a build targets is read from `RBT_BUILD_TARGET` (set by - // `build.mjs`), not Vite's `mode` — so `mode` stays `production` - // and the `web/` build below reads `web/.env.production`. - const target = process.env.RBT_BUILD_TARGET ?? ""; - - // Build the standalone `web/` SPA into `dist/web/`. We root the - // build at `web/` so `index.html` and its `assets/` land directly - // under `dist/web/` (rather than `dist/web/web/`). Keep assets as - // separate files (a normal multi-file build) and set `base` so - // their URLs resolve when the SPA is served at `/__/frontend/web/`. - if (target === "web") { - return { - plugins: [react()], - root: path.resolve(__dirname, "web"), - base: "/__/frontend/web/", - build: { - outDir: path.resolve(__dirname, "dist/web"), - emptyOutDir: true, - }, - resolve, - }; - } - - // Build one MCP UI (`RBT_BUILD_TARGET=mcp:`). We root the - // build at `mcp//` so the output lands directly at - // `dist/mcp//index.html`, a single self-contained file - // (assets inlined by `vite-plugin-singlefile`). The framework - // serves it at `/__/frontend/mcp//index.html` in dist mode. - const name = target.startsWith("mcp:") ? target.slice("mcp:".length) : ""; - if (!mcpNames.includes(name)) { - const valid = mcpNames.map((n) => `mcp:${n}`).join(", "); - throw new Error( - `Unknown build target: ${target || "(unset)"}. Set ` + - `RBT_BUILD_TARGET=web or one of: ${valid}.` - ); - } - - return { - plugins: [react(), viteSingleFile()], - root: path.resolve(__dirname, "mcp", name), - base: "/__/frontend/", - // Read `.env*` from `web/` (the shared frontend env), so an MCP UI - // can pick up e.g. `VITE_WEB_APP_URL` for a pop-out link. - envDir: path.resolve(__dirname, "web"), - build: { - outDir: path.resolve(__dirname, "dist/mcp", name), - emptyOutDir: true, - assetsInlineLimit: 100000000, - cssCodeSplit: false, - rollupOptions: { - output: { - inlineDynamicImports: true, - }, - }, - }, - resolve, - }; -}); -``` - - - - -\` — the -same counter, opened full-page outside the AI chat. Create that SPA -under \`frontend/web/\`: an \`index.html\`, a \`main.tsx\` entry point -that requires sign-in via \`useUser()\`, and an \`App.tsx\` that lists -the signed-in user's counters, reads \`?counter=\`, and -watches/increments a counter with the same generated -\`useCounter()\` hook. - -`}> - -```sh -mkdir -p frontend/web/src -touch frontend/web/index.html -touch frontend/web/src/main.tsx frontend/web/src/App.tsx -``` - - - - -```html - - - - - - - Chat Counter - - -
- - - -``` - - - - - - -```tsx -// frontend/web/src/main.tsx -import { useUser } from "@api/ai_chat_counter/v1/counter_rbt_react"; -import { - RebootClientProvider, - useSignIn, - useSignOut, -} from "@reboot-dev/reboot-react"; -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { App } from "./App"; - -const Root = () => { - const { user, isLoading } = useUser(); - const signIn = useSignIn(); - const signOut = useSignOut(); - // The provider renders us immediately and resolves the signed-in - // user in the background, so we see `isLoading` until `/whoami` - // lands. - if (isLoading) { - return
Checking session…
; - } - if (user === undefined) { - return ( -
-

Chat Counter

-

Sign in to create and browse your counters.

- -
- ); - } - return void signOut()} />; -}; - -// Point the Reboot client at the backend via `VITE_REBOOT_URL`. A web -// app is served from its own origin — a CDN or static host — almost -// never same-origin with the Reboot backend, so a real deploy MUST set -// `VITE_REBOOT_URL` to the backend's origin. `web/.env.development` -// sets it for local dev (to the Envoy endpoint), so this SPA runs on -// the Vite dev server cross-origin to the backend — the same shape as -// production. -createRoot(document.getElementById("root")!).render( - - - - - -); -``` - - - - - - -```tsx -// frontend/web/src/App.tsx -import { - type UseCounterApi, - type UseUserApi, - useCounter, -} from "@api/ai_chat_counter/v1/counter_rbt_react"; -import { useState, type FC } from "react"; - -interface AppProps { - user: UseUserApi; - onSignOut: () => void; -} - -// Read an initial counter ID from the `?counter=` query param so a -// counter can be shared via URL — the MCP clicker's "Open in web app" -// button deep-links here; picking a counter from the list below also -// opens it. -const initialCounterId = (): string => - new URLSearchParams(window.location.search).get("counter") ?? ""; - -export const App: FC = ({ user, onSignOut }) => { - const [counterId, setCounterId] = useState(initialCounterId); - - return ( -
-
-

Chat Counter

-
- {user.state_id} - -
-
- - {counterId.length === 0 ? ( - - ) : ( - <> - - - - )} -
- ); -}; - -// Landing view: create a counter, and open one of the signed-in -// user's own counters (read reactively via `User.list_counters`). -const CounterList: FC<{ user: UseUserApi; onOpen: (id: string) => void }> = ({ - user, - onOpen, -}) => { - const { response: listResponse, isLoading } = user.useListCounters(); - const [description, setDescription] = useState(""); - const [pending, setPending] = useState(false); - - const handleCreate = async () => { - if (description.trim().length === 0) return; - setPending(true); - try { - await user.createCounter({ description }); - setDescription(""); - } finally { - setPending(false); - } - }; - - const counters = listResponse?.counters ?? []; - const showLoading = isLoading && listResponse === undefined; - - return ( - <> -
-

Create counter

-
{ - event.preventDefault(); - void handleCreate(); - }} - > - setDescription(event.target.value)} - placeholder="What does this counter count?" - /> - -
-
- -
-

Your counters

- {showLoading ? ( -

Loading…

- ) : counters.length === 0 ? ( -

No counters yet — create one above.

- ) : ( -
    - {counters.map((counter) => ( -
  • - -
  • - ))} -
- )} -
- - ); -}; - -const CounterView: FC<{ counterId: string }> = ({ counterId }) => { - // The generated zod hook returns the `UseCounterApi` directly; reads - // are exposed as nested `use*` hooks and writes as plain methods. - const counter: UseCounterApi = useCounter({ id: counterId }); - const { response: valueResponse, isLoading } = counter.useGet(); - const { response: descriptionResponse } = counter.useDescription(); - const [pending, setPending] = useState(false); - - // Show a muted placeholder until the description loads rather than - // flashing the "(no description)" fallback first. - const descriptionLoading = descriptionResponse === undefined; - - const handleIncrement = async () => { - setPending(true); - try { - await counter.increment({ amount: 1 }); - } finally { - setPending(false); - } - }; - - return ( -
-

- {descriptionLoading - ? "Loading…" - : descriptionResponse?.description || "(no description)"} -

-
- {isLoading && valueResponse === undefined - ? "…" - : valueResponse?.value ?? 0} -
- -
- ); -}; -``` - - - -
-Configure your .rbtrc} - description={` - -An [\`.rbtrc\`](/develop_locally#rbtrc-and-flags) file contains flags -for the \`rbt\` CLI. Use it to tell Reboot where your API files live, -how to generate code, and how to run your app. - -The key addition for AI Chat Apps is -\`generate --react=frontend/api\`, which generates the React hooks -the UI uses. - -`}> - -```sh -touch .rbtrc -``` - - - - -```sh -# .rbtrc -# Find API definitions in 'api/'. -generate api/ - -# Generate Python code. -generate --python=backend/api/ - -# Generate React hooks. -generate --react=frontend/api - -# Watch for source changes. -dev run --watch=backend/**/*.py -dev run --watch=frontend/dist/**/*.html - -# Python application. -dev run --python - -# Save state between restarts. -dev run --application-name=ai-chat-counter - -# Entrypoint. -dev run --application=backend/src/main.py - -# Default to HMR mode when no --config is specified. -dev run --default-config=hmr - -# Hot Module Replacement (HMR): Vite dev server proxied through Envoy. -# Run Vite in a separate terminal: `cd frontend && npm run dev` -dev run --frontend-root-path=frontend - -dev run:hmr --frontend-host=http://localhost:4444 - -# Dist mode: serve pre-built artifacts from "frontend/dist/" (no Vite HMR). -# Usage: `uv run rbt dev run --config=dist` -# Requires: `cd frontend && npm run build` -dev run:dist --frontend-dist-path=frontend/dist -``` - - - - - - -```sh -uv run rbt dev run & -cd frontend -RBT_BUILD_TARGET=mcp:clicker npx vite build -RBT_BUILD_TARGET=web npx vite build -``` - - - - -```sh -uv run rbt dev run --config=dist -``` - - - - -```sh -npx @mcpjam/inspector@2.23.3 --url http://localhost:9991/mcp --oauth -``` - - - - - - -

How to try out your app

- -MCPJam opens a browser-based interface where you can test your app's -tools and UIs. - -Click **App Builder** in the left sidebar. You should see tools -including `create_counter`, `counter_get`, -`counter_increment`, and `counter_show_clicker`. - -1. Select **`counter_show_clicker`** and click **Run** to show your UI. You - should be able to increment the counter by clicking a button. -2. If you are signed in to MCPJam you can additionally ask the LLM to - take actions for you in conversation, like your users will. Try - prompts like... - * "show me my clicker UI" - * "increment my counter" -3. As you interact with the UI, notice how actions taken outside the UI - (e.g. asking the LLM to increment the counter) update the UI - reactively. - -Under the hood, your app communicates via MCP (Model Context Protocol), -the standard that AI clients like ChatGPT, Claude, and VS Code use to -discover and interact with apps. - -

Next steps

- -Well done! You have a working AI Chat App. Here are some great -next steps: - -* **[UIs for AI Chat Apps](/surfaces/ui_methods)** — the - full reference for `UI` methods, including parameterized UIs where - the AI passes props to the React component. -* **[Creating tools](/define/pydantic#creating-tools-for-the-ai)** — - how to control which methods are callable by the AI, including - exposing methods on non-`User` state types, or hiding some - `User` methods from the AI. -* **[How Reboot uses MCP](/surfaces/ai_chat)** — learn how - AI Chat Apps work under the hood. -* **[AI Chat App Examples](/get_started/examples)** - — more examples to explore. -* **[Deploy to Reboot Cloud](/deploy_on_reboot_cloud)** — deploy - your app with `rbt cloud up`. -* **[Join us on Discord](https://discord.gg/cRbdcS94Nr)** — to - ask questions and let us know what you think! diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index 71219ea94..dff7c80a3 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -55,8 +55,9 @@ const config: Config = { }, { from: "/ai_chat_apps/get_started", - to: "/get_started/quickstart", + to: "/get_started/claude_code", }, + { from: "/get_started/quickstart", to: "/get_started/claude_code" }, { from: [ "/ai_chat_apps/get_started_claude_code", diff --git a/documentation/sidebars.js b/documentation/sidebars.js index b1a70fa5b..885e6bc00 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -56,11 +56,6 @@ const sidebars = { }, ], }, - { - type: "doc", - id: "get_started/quickstart", - label: "Quickstart (hand-written AI chat app)", - }, { type: "doc", id: "get_started/examples", From b6c84b5158e4fc342eedcf12ddb0353daea74212 Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 08/16] Docs: drop protobuf as a way to define an API Before this change, protobuf was offered as a third way to define an API, which made the story "Pydantic, or Zod, or protobuf, depending" instead of "Python uses Pydantic, TypeScript uses Zod". Protobuf is an implementation detail of Reboot, not part of the developer's job, so the docs should not present it as a choice. The one place it stays is the standard library's value encoding (`reboot.protobuf`, `@bufbuild/protobuf`), which has no alternative API. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- documentation/docs/call/from_react.mdx | 49 +----------- documentation/docs/call/overview.mdx | 5 +- documentation/docs/define/overview.mdx | 1 - documentation/docs/define/protobuf.mdx | 76 ------------------- documentation/docs/develop_locally.md | 22 +++--- documentation/docs/errors.mdx | 37 +-------- documentation/docs/get_started/python.mdx | 11 +-- documentation/docs/get_started/typescript.mdx | 9 +-- documentation/docs/implement/servicers.mdx | 7 +- documentation/docs/library_services/item.mdx | 11 ++- .../docs/library_services/mailgun.md | 4 +- .../docs/library_services/ordered_map.mdx | 8 +- .../docs/library_services/sorted_map.mdx | 5 +- documentation/docusaurus.config.ts | 3 +- documentation/sidebars.js | 1 - 15 files changed, 44 insertions(+), 205 deletions(-) delete mode 100644 documentation/docs/define/protobuf.mdx diff --git a/documentation/docs/call/from_react.mdx b/documentation/docs/call/from_react.mdx index 0098ef540..c6baf0cca 100644 --- a/documentation/docs/call/from_react.mdx +++ b/documentation/docs/call/from_react.mdx @@ -98,47 +98,6 @@ api = API( ) ``` - - - - - - -```protobuf -message ChatRoom { - option (rbt.v1alpha1.state) = { - }; - repeated string messages = 1; -} - -service ChatRoomMethods { - // Returns the current list of recorded messages. - rpc Messages(MessagesRequest) returns (MessagesResponse) { - option (rbt.v1alpha1.method).reader = { - }; - } - - // Adds a new message to the list of recorded messages. - rpc Send(SendRequest) returns (SendResponse) { - option (rbt.v1alpha1.method).writer = { - }; - } -} - -message MessagesRequest {} - -message MessagesResponse { - repeated string messages = 1; -} - -message SendRequest { - string message = 1; // E.g. "Hello, World". -} - -message SendResponse {} -``` - @@ -383,8 +342,8 @@ Reboot methods are accessed by their name in lower camel case, e.g., `deleteAllMessages`. This line calls the -[`writer`](/define/methods#kinds) method, declared -in the `.proto` as `Send`, using the lower camel case name `send`: +[`writer`](/define/methods#kinds) method declared as `send` in the API +definition, using its lower camel case name: @@ -453,8 +412,8 @@ if (aborted !== undefined) { -In this case, because the `Send` method in `chat_room.proto` does not define any -specific error types, the only error that can be returned in the `aborted` +In this case, because the `send` method does not define any specific +error types, the only error that can be returned in the `aborted` object will be Reboot system errors such as `StateAlreadyConstructed`. :::info diff --git a/documentation/docs/call/overview.mdx b/documentation/docs/call/overview.mdx index f09d47688..71b5fd4b2 100644 --- a/documentation/docs/call/overview.mdx +++ b/documentation/docs/call/overview.mdx @@ -91,9 +91,8 @@ that you pass to [`Application`](/implement/application). ## Constructing instances You construct an instance of your data types either _implicitly_ -or _explicitly_, depending on whether or not you have explicitly -designated certain methods as _constructors_ in your `.proto`, or as -a _factory_ in your Pydantic or Zod schema. +or _explicitly_, depending on whether or not you have designated a +method as a _factory_ in your Pydantic or Zod schema. :::tip Prefer an explicit constructor for the types your application creates diff --git a/documentation/docs/define/overview.mdx b/documentation/docs/define/overview.mdx index 5d34bb3de..c56e5b80f 100644 --- a/documentation/docs/define/overview.mdx +++ b/documentation/docs/define/overview.mdx @@ -23,7 +23,6 @@ front: - [Define your API using Pydantic](/define/pydantic) — Python - [Define your API using Zod](/define/zod) — TypeScript -- [Define your API using protobuf](/define/protobuf) — TypeScript or Python ### The `User` type diff --git a/documentation/docs/define/protobuf.mdx b/documentation/docs/define/protobuf.mdx deleted file mode 100644 index 0fb86139c..000000000 --- a/documentation/docs/define/protobuf.mdx +++ /dev/null @@ -1,76 +0,0 @@ -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Protobuf - -You can use [protocol buffers](https://protobuf.dev) (aka protobuf) to -define your API for either TypeScript or Python backends. - -To declare a data type called `ChatRoom`, put the following in a `.proto` -file: - - - - -```protobuf -message ChatRoom { - option (rbt.v1alpha1.state) = { - }; - repeated string messages = 1; -} -``` - - - -Reboot uses standard [Protobuf definitions](https://protobuf.dev/programming-guides/proto3/), but take note of the `rbt` [options (aka, annotations)](https://protobuf.dev/programming-guides/proto3/#options), which require adding `import "rbt/v1alpha1/options.proto";` to your `.proto` file. - -In the above example, `rbt.v1alpha1.state` declares that `message -ChatRoom` is a Reboot data type. - -In addition to defining the data type, you'll also need to define -operations for that type, for example: - - - - -```protobuf -service ChatRoomMethods { - // Returns the current list of recorded messages. - rpc Messages(MessagesRequest) returns (MessagesResponse) { - option (rbt.v1alpha1.method).reader = { - }; - } - - // Adds a new message to the list of recorded messages. - rpc Send(SendRequest) returns (SendResponse) { - option (rbt.v1alpha1.method).writer = { - }; - } -} -``` - - - -:::info -A Reboot service name must end with the suffix `Methods`. By default, -Reboot associates the service with the state whose name matches the -part before the suffix, so `ChatRoomMethods` provides the methods for -`ChatRoom`. -::: - -For each of the `rpc` methods that you declare, you use the -`rbt.v1alpha1.method` annotation to specify the method -[kind](/define/methods). - -Depending on its [kind](/define/methods), a method -might be able to only read (e.g., `reader`) or both read and write -(e.g., `writer`) the state. - -In the example above, `ChatRoom` has just one `reader` and one `writer` -method, but it can have any number of `reader`, `writer`, -`transaction`, and `workflow` methods. - -To learn more about how you implement each data type's methods see -[Implement your API](/implement/servicers). diff --git a/documentation/docs/develop_locally.md b/documentation/docs/develop_locally.md index a6653fbff..1ab552b1a 100644 --- a/documentation/docs/develop_locally.md +++ b/documentation/docs/develop_locally.md @@ -182,19 +182,19 @@ rbt generate --python=backend/api/ api/ -- --mypy_out=backend/api/ In `.rbtrc`: ```shell -# Find '.proto' files in 'api/'. +# Find API definitions in 'api/'. generate api/ -# Generate 'python' code from our '.proto' files in 'backend/api/'. +# Generate Python code into 'backend/api/'. generate --python=backend/api/ -# Generate 'mypy' code from our '.proto' files in 'backend/api/mypy/'. +# Generate 'mypy' stubs into 'backend/api/mypy/'. generate -- --mypy_out=backend/api/mypy/ ``` -In these examples, the order of the flags is crucial. All flags that appear -after `--` are passed directly to the underlying tool (in this case, -`protoc`). +In these examples, the order of the flags is crucial. All flags that +appear after `--` are passed directly to the underlying code +generator. #### Frontend flags @@ -226,8 +226,8 @@ dev run --default-config=hmr - - - - - -```protobuf -rpc Withdraw(WithdrawRequest) returns (WithdrawResponse) { - option (rbt.v1alpha1.method) = { - writer: {}, - errors: [ "OverdraftError" ], - }; -} -``` - - - -These errors are themselves defined by proto messages. - - - - -```protobuf -// Error returned when a withdrawal would overdraft the account. -message OverdraftError { - // Amount that we would have overdraft by. - uint32 amount = 1; -} -``` - diff --git a/documentation/docs/get_started/python.mdx b/documentation/docs/get_started/python.mdx index ab2bacafd..dccc792fe 100644 --- a/documentation/docs/get_started/python.mdx +++ b/documentation/docs/get_started/python.mdx @@ -55,15 +55,12 @@ docker ps title={
Create a Pydantic .py file
} description={` -Reboot uses an interface definition language (IDL) to define -your APIs. Currently we support [Pydantic](https://docs.pydantic.dev/) -and [protocol -buffers](https://protobuf.dev) (if you're interested in -[OpenAPI](https://www.openapis.org/) support, please [reach out to us -on Discord](https://discord.gg/cRbdcS94Nr)). +You define your API up front. In Python that means +[Pydantic](https://docs.pydantic.dev/) (in TypeScript it is +[Zod](https://zod.dev)). This is where you will define and annotate your durable data -types and their operations. This example uses Pydantic. +types and their operations. `}> diff --git a/documentation/docs/get_started/typescript.mdx b/documentation/docs/get_started/typescript.mdx index 66abdcf32..bcc7cc92c 100644 --- a/documentation/docs/get_started/typescript.mdx +++ b/documentation/docs/get_started/typescript.mdx @@ -62,12 +62,11 @@ docker ps title={
Define your API using Zod
} description={` -With Reboot you define your API first, either using -[Zod](https://zod.dev) or an interface definition language (IDL) like -[protocol buffers](https://protobuf.dev). +You define your API up front. In TypeScript that means +[Zod](https://zod.dev) (in Python it is +[Pydantic](https://docs.pydantic.dev/)). -In this quickstart we'll use Zod, so we'll need to create a new -TypeScript file to define the API. +Create a new TypeScript file to define the API. This is where you will define and annotate your durable state data types and their operations. diff --git a/documentation/docs/implement/servicers.mdx b/documentation/docs/implement/servicers.mdx index f40bb5eea..36330b929 100644 --- a/documentation/docs/implement/servicers.mdx +++ b/documentation/docs/implement/servicers.mdx @@ -5,10 +5,9 @@ import TabItem from "@theme/TabItem"; For each of your data types you need to implement its methods. You do that by implementing server-side (backend) **servicers** based on the -data types and methods you defined (e.g., using -[Pydantic](/define/pydantic), -[Zod](/define/zod), or -[protobuf](/define/protobuf)). +data types and methods you defined (using +[Pydantic](/define/pydantic) in Python or +[Zod](/define/zod) in TypeScript). :::note [`UI`](/surfaces/ui_methods) methods have no backend diff --git a/documentation/docs/library_services/item.mdx b/documentation/docs/library_services/item.mdx index be3f89cd0..2ad72fd5d 100644 --- a/documentation/docs/library_services/item.mdx +++ b/documentation/docs/library_services/item.mdx @@ -13,8 +13,7 @@ There are three ways of formatting data for an `Item`. A producer of value is expected to set one of that variants, absence of any variant indicates an error. This type is meant to represent all possible JSON values. * `bytes` is used to store a string of `bytes`. - * `Any` refers to the `Any` protobuf and is the best choice if you are using protobufs - to structure your data. + * `Any` wraps a protobuf message, for code that already has one. ## Imports @@ -46,8 +45,8 @@ Since this is a data type that is not stored individually, you do not need a ser ## Value -`Value` is the recommended way to use `Item` if you have structured data that is -not already a string of `bytes` or a protobuf. +`Value` is the recommended way to use `Item` for structured data: +anything you could express as JSON. To marshal your data * in Python, you can use helper methods from `reboot.protobuf` @@ -179,8 +178,8 @@ console.log(item.bytes); ## Any -If you use protobufs to format your data, you can directly store that -into an `Item`. +If you already have a protobuf message, you can store it directly in +an `Item`. To pack and unpack from an `Any` protobuf, * in Python, you can use helper methods from `reboot.protobuf` diff --git a/documentation/docs/library_services/mailgun.md b/documentation/docs/library_services/mailgun.md index 145dd2a57..cdb94a284 100644 --- a/documentation/docs/library_services/mailgun.md +++ b/documentation/docs/library_services/mailgun.md @@ -70,8 +70,8 @@ See `Send`. | sender | string | The email address of the sender of the message. | | subject | string | The subject of the message. | | domain | string | The domain to send from. | -| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) body.text | string | The body content of the message, as text. | -| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) body.html | string | The body content of the message, as HTML. | +| body.text | string | The body content of the message, as text. Set exactly one of `body.text` and `body.html`. | +| body.html | string | The body content of the message, as HTML. | ### SendResponse {#sendresponse} diff --git a/documentation/docs/library_services/ordered_map.mdx b/documentation/docs/library_services/ordered_map.mdx index 1d7cd2117..1a1140b58 100644 --- a/documentation/docs/library_services/ordered_map.mdx +++ b/documentation/docs/library_services/ordered_map.mdx @@ -13,8 +13,7 @@ when inserted into an `OrderedMap`. A producer of a `Value` is expected to set one of these variants; the absence of any variant indicates an error. This type is meant to represent all possible JSON values. * `bytes` is used to store a string of `bytes`. - * `Any` refers to the `Any` protobuf and is the best choice if you are using protobufs - to structure your data. + * `Any` wraps a protobuf message, for code that already has one. `OrderedMap`s are frequently used to create indexes of other state types, but can also be used to efficiently store large collections of primitive values. @@ -26,9 +25,8 @@ For example: * To index a data type by creation time, you can use time-ordered UUIDs (such as UUIDv1 or UUIDv7) as keys and the data type's state-ID as values. - * To index a large collection of Protobuf message types, you can use a - key of your choice and the serialized value of each Protobuf message - as a value. + * To store a large collection of records, you can use a key of your + choice and each record, serialized, as a value. To use an `OrderedMap` in your application, you typically want to choose an ID for the map and store it somewhere (e.g., as a field in the appropriate state data diff --git a/documentation/docs/library_services/sorted_map.mdx b/documentation/docs/library_services/sorted_map.mdx index f5ead399b..1e40f2f2a 100644 --- a/documentation/docs/library_services/sorted_map.mdx +++ b/documentation/docs/library_services/sorted_map.mdx @@ -20,9 +20,8 @@ For example: * To index a data type by creation time, you can use time-ordered UUIDs (such as UUIDv1 or UUIDv7) as keys and the data type's state-ID as values. - * To index a large collection of Protobuf message types, you can use a - key of your choice and the serialized value of each Protobuf message - as a value. + * To store a large collection of records, you can use a key of your + choice and each record, serialized, as a value. To use a `SortedMap` in your application, you typically want to choose an ID for the map and store it somewhere (e.g., as a field in the appropriate state data diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index dff7c80a3..a96ea2546 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -86,7 +86,8 @@ const config: Config = { { from: "/learn_more/define/methods", to: "/define/methods" }, { from: "/learn_more/define/pydantic", to: "/define/pydantic" }, { from: "/learn_more/define/zod", to: "/define/zod" }, - { from: "/learn_more/define/protobuf", to: "/define/protobuf" }, + { from: "/learn_more/define/protobuf", to: "/define/overview" }, + { from: "/define/protobuf", to: "/define/overview" }, { from: "/learn_more/implement/servicers", to: "/implement/servicers", diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 885e6bc00..8cc1a85b4 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -156,7 +156,6 @@ const sidebars = { "define/methods", "define/pydantic", "define/zod", - "define/protobuf", ], }, { From 687ec5296a2605cfd009dee563e7d36d079c308e Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 09/16] README: match reboot.dev and the reorganized docs Before this change, the README introduced Reboot as a framework for AI chat apps that also builds web apps, named a `Session` type that no longer exists as the entry point, and linked to docs URLs that now only redirect. As the front page on GitHub it should make the same argument as reboot.dev and then send readers into the reorganized docs. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- README.md | 264 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 157 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index fca5c820c..ed190f406 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ # Reboot -**Build AI Chat Apps — and full-stack web apps — with reactive, durable backends.** +**Trust the code your agent writes.** + +A full-stack framework for the AI era. [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![PyPI](https://img.shields.io/pypi/v/reboot)](https://pypi.org/project/reboot/) @@ -17,88 +19,103 @@ --- -Reboot is a framework for building **reactive, stateful, multiplayer AI chat -apps** — visual apps that run inside ChatGPT, Claude, VS Code, Goose, and more. -It also builds full-stack web apps with reactive backends and React frontends. - -With Reboot, you just write business logic — no wiring up databases, caches, -queues, or retry loops. State survives failures by default. ACID transactions -span multiple states. The React frontend stays in sync in real time. And your -backend is automatically an MCP server. +Modern app development is quickly becoming AI-assisted, or entirely +vibe-coded. Can you trust these apps in production? Yes, but only if +you build on a framework that handles the hard stuff and forces AI +agents to ship clean, modular software. -## AI Chat Apps +Reboot solves the hard problems once, at the framework level, so +neither you nor your coding agent has to. What Rust's borrow checker +did for memory management, and React did for component-based +frontends, Reboot does for your backend: **backend safety and data +encapsulation**, enforced by the framework rather than by review. -Build visual, interactive apps that run inside AI chat interfaces. Define a -`Session` type as an entry point and your methods automatically become tools -the AI can call: - -```python -from reboot.api import ( - API, Field, Methods, Model, Reader, Tool, - Transaction, Type, UI, Writer, -) +## Try Reboot with Claude Code or Codex +Install the Reboot plugin: -class CreateCounterResponse(Model): - counter_id: str = Field(tag=1) +```sh +curl -fsSL https://reboot.dev/install.sh | bash +``` +Then describe what you want: + +> Build me a todo-list app I can use from a browser and from Claude and ChatGPT + +The agent proposes a design, scaffolds the project — API, backend, +frontend, sign-in, tests — and runs it. See +[Build with Claude Code](https://docs.reboot.dev/get_started/claude_code) +or [Build with Codex](https://docs.reboot.dev/get_started/codex), or +[build one by hand](https://docs.reboot.dev/get_started/python) to +see every file. + +## Why a new framework? + +Agents are blazingly fast, junior engineers. You cannot trust them to +build your application correctly without a rock-solid foundation to +stand on. A harness isn't enough. + +- **Correct concurrency and retry safety.** Coding agents reliably + ship bugs in these two areas. The only way to fix this is to give + them constraints that make those bugs impossible by construction: + every method declares its kind — `reader`, `writer`, + `transaction`, or `workflow` — and Reboot enforces what each one may + do. +- **Lose nothing on reboot.** Agents don't expect their code to + crash. Until now, the fix was to build on a durable execution + engine. Reboot goes further with **durable applications**: the + moment a function returns, its `state` is saved. Workflows resume + where they failed — the steps that already finished are memoized + rather than run again — and transactions keep everything atomic. No + database, no cache, no queue. +- **Agents are lazy.** You asked for a frontend, but what you really + wanted was a reactive one. You wanted it to retry on an + intermittent network failure, but instead it threw an error and + never cleaned up its local React state. With Reboot you get these + features, and many more, without ever having to ask. +- **Agents make code hard to review.** Even if some harness could get + an agent to handle all of the concerns above, would you want to + review that diff? Could you be sure it didn't introduce bugs? + Reboot's semantics are simple enough, for humans and agents alike, + that you can. + +## What it looks like + +Define your API with [Pydantic](https://docs.reboot.dev/define/pydantic) +in Python (or [Zod](https://docs.reboot.dev/define/zod) in +TypeScript). Every method declares its kind, and whether an AI may +call it: -class UserState(Model): - pass +```python +from reboot.api import API, Field, Methods, Model, Reader, Tool, Type, Writer -class CounterState(Model): - value: int = Field(tag=1, default=0) +class AccountState(Model): + balance: int = Field(tag=1, default=0) -class GetResponse(Model): - value: int = Field(tag=1) +class DepositRequest(Model): + amount: int = Field(tag=1) -class IncrementRequest(Model): - """Request with an amount parameter.""" - amount: int | None = Field(tag=1, default=None) +class BalanceResponse(Model): + balance: int = Field(tag=1) api = API( - User=Type( - state=UserState, - methods=Methods( - create_counter=Transaction( - request=None, - response=CreateCounterResponse, - description="Create a new Counter.", - mcp=Tool(), - ), - ), - ), - Counter=Type( - state=CounterState, + Account=Type( + state=AccountState, methods=Methods( - show_clicker=UI( - request=None, - path="frontend/mcp/clicker", - title="Counter Clicker", - description="Interactive clicker UI.", - ), - create=Writer( - request=None, + deposit=Writer( + request=DepositRequest, response=None, - factory=True, - description="Create the counter at zero.", - mcp=None, - ), - get=Reader( - request=None, - response=GetResponse, - description="Get the current counter " - "value.", + description="Add funds to the account.", mcp=Tool(), ), - increment=Writer( - request=IncrementRequest, - response=None, - description="Increment the counter.", + balance=Reader( + request=None, + response=BalanceResponse, + description="The account's current balance.", mcp=Tool(), ), ), @@ -106,54 +123,87 @@ api = API( ) ``` -### Dive in! - -- [What is an AI Chat App?](https://docs.reboot.dev/ai_chat_apps/what_is) -- [Get Started (Python)](https://docs.reboot.dev/ai_chat_apps/get_started) -- [AI Chat App Examples](https://docs.reboot.dev/ai_chat_apps/examples) - -## Full-stack apps - -Build reactive backends with React frontends — great as a full-page extension -of your AI chat app, or as a standalone web app. - -- [Python Quickstart](https://docs.reboot.dev/full_stack_apps/python) -- [TypeScript Quickstart](https://docs.reboot.dev/full_stack_apps/typescript) -- [Full-stack Examples](https://docs.reboot.dev/full_stack_apps/examples) +Implement it. `self.state` is durable: when the method returns, the +new state is saved, all of it or none of it. -TypeScript backend support is in alpha: the core of Reboot works in -both languages, but some features — including MCP apps, `UI` methods, -the built-in OAuth sign-in flow, and durable agents — are currently -Python-only. If you don't have a strong preference, start with Python. - -## Key features - -**Automatic MCP server.** `Session` methods are automatically exposed as -MCP tools. Other types can opt in with `mcp=Tool()`. `UI` methods open -React apps in the AI's chat. No glue code. - -**Durable state by default.** States survive process crashes, deployments, and -chaos. No external database required. - -**ACID transactions across states.** `transaction` methods compose atomically -across many state instances running on different machines. - -**Reactive React frontend.** Generated hooks keep your UI in sync -without manual management of WebSockets, caches, or polling. +```python +class AccountServicer(Account.Servicer): + + async def deposit( + self, + context: WriterContext, + request: Account.DepositRequest, + ) -> None: + self.state.balance += request.amount + + async def balance( + self, + context: ReaderContext, + ) -> Account.BalanceResponse: + return Account.BalanceResponse(balance=self.state.balance) +``` -**Method system.** Code is safer to write (and _read_) with a clear API -and methods with enforced constraints: `reader` (concurrent, read-only), -`writer` (serialized, mutating), `transaction` (ACID, cross-state), -`workflow` (long-running, durable, cancellable), `ui` (React app in AI -chat). The runtime enforces these guarantees. +Call it from React through generated, typed hooks. `useBalance` +re-renders whenever the balance changes, whether this user, another +user, a workflow, or an AI changed it: -**API-first, code-generated.** Define APIs using Pydantic (Python) or -Zod (TypeScript). Reboot generates type-safe client, server, and React -stubs. +```tsx +const account = useAccount({ id }); +const { response } = account.useBalance(); -## Documentation +await account.deposit({ amount: 50 }); +``` -Full documentation at [docs.reboot.dev](https://docs.reboot.dev/). +The same two methods are tools for Claude, ChatGPT, or any other MCP +client, because they were declared with `mcp=Tool()`. + +## One backend, many frontends + +One app to serve every user — human or machine. You and your agents +can build any kind of app with Reboot: + +- **Humans** reach it through a + [web app](https://docs.reboot.dev/surfaces/web), a + [React Native app](https://docs.reboot.dev/surfaces/react_native) + (alpha), or an + [AI chat app](https://docs.reboot.dev/surfaces/ai_chat) inside + ChatGPT, Claude, or VS Code, where `UI` methods render React + components in the conversation. +- **Agents** reach it over MCP: every method marked `mcp=Tool()` is a + tool. An agent can also run + [inside your app](https://docs.reboot.dev/agents), with durable, + replay-safe model and tool calls. +- **Services** reach it from your own backend code, a script, or a + plain HTTP request. + +Signed-in users come built in. Plug in your favorite auth provider — +Google, GitHub, Auth0, Ory — and Reboot runs the OAuth server in front +of it, so a person signing in auto-constructs their `User`: the +per-user entry point the rest of your app hangs off, and the same +`User` on every surface. See +[Users and sign-in](https://docs.reboot.dev/users/overview). + +## Status + +Python backends are supported today. TypeScript backends and React +Native frontends are in alpha: the core of Reboot works in both +languages, but AI chat apps, `UI` methods, the built-in OAuth sign-in +flow, and durable agents are Python-only for now. More languages are +coming. + +## Get started today + +Reboot is open source. Run a Reboot app on your own infrastructure or +on Reboot Cloud. + +- [Reboot Cloud](https://cloud.reboot.dev/) — deploy with + `rbt cloud up`. +- [Deploy on your own](https://docs.reboot.dev/deploy_on_your_own) — + `rbt serve` on your own machines or Kubernetes. +- [Documentation](https://docs.reboot.dev/) — start with + [How Reboot works](https://docs.reboot.dev/concepts). +- [Examples](https://docs.reboot.dev/get_started/examples) — complete + applications to run and take apart. ## Community From 08370b06f8b4c101d286554ddc809efd46d48bff Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 10/16] Docs: link the `ai-chat-counter` example Every other example on the page links to its source, so a reader could open the code; `ai-chat-counter` had no link because it has no repository of its own. It lives in this repository under `reboot/examples/`, so link it there. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- documentation/docs/get_started/examples.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/docs/get_started/examples.md b/documentation/docs/get_started/examples.md index e3c3e7179..3961e8e0e 100644 --- a/documentation/docs/get_started/examples.md +++ b/documentation/docs/get_started/examples.md @@ -124,7 +124,8 @@ Start with `hello`, `counter`, or `bank` before this one. ### `ai-chat-counter` -_Python backend, React AI chat UIs and a web app_ +[`reboot-dev/reboot/…/ai-chat-counter`](https://github.com/reboot-dev/reboot/tree/main/reboot/examples/ai-chat-counter) +— _Python backend, React AI chat UIs and a web app_ A counter with visual UIs that run inside ChatGPT, Claude, VS Code, or Goose — plus a standalone browser SPA that the chat UI deep-links to. From 71b104857d6e2f8a8d9f97f7d188b727e7229631 Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 11/16] Docs: say what the TLS advice is actually about Three pages told readers to use TLS because of a browser connection limit, with three different numbers and no explanation of what was being limited, so a reader could neither judge whether the advice applied to them nor trust the numbers. The React client is the source of truth: the limit is on WebSockets, one per reactive reader over plain HTTP, and HTTP/2 over TLS removes it. All three pages now say that, with the client's numbers. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- documentation/docs/call/from_react.mdx | 7 +++++-- documentation/docs/rbt_cli.md | 15 ++++++++++----- documentation/docs/surfaces/web.mdx | 7 ++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/documentation/docs/call/from_react.mdx b/documentation/docs/call/from_react.mdx index c6baf0cca..739bcec37 100644 --- a/documentation/docs/call/from_react.mdx +++ b/documentation/docs/call/from_react.mdx @@ -163,8 +163,11 @@ All generated Reboot React Custom Hooks can now be used inside `` or any of `App`'s children. :::tip -Use a TLS endpoint to take advantage of HTTP/2 -multiplexing, a requirement for any Reboot React app with more than ~100 outstanding RPC calls, e.g., reactive readers or mutations. +Use a TLS endpoint so the browser can use HTTP/2. Over plain HTTP, +every reactive reader holds its own WebSocket, and browsers allow +only around 200 of those per page (255 in Chrome). See +[the `rbt` CLI](/rbt_cli#bring-your-own-certificate-with-rbt-dev-run) +for local certificates. ::: In practice, it is best to use an environment variable for your API endpoint. diff --git a/documentation/docs/rbt_cli.md b/documentation/docs/rbt_cli.md index f62a7ea45..b18fb1da4 100644 --- a/documentation/docs/rbt_cli.md +++ b/documentation/docs/rbt_cli.md @@ -38,11 +38,16 @@ view at ::: ## Bring your own certificate with `rbt dev run` -By default `rbt dev run` backends use plain HTTP, not HTTPS. This makes for -easier development, but due to web browser limitations the number of connections -to the backend over HTTP will be limited - beyond 200 connections it is required -to use HTTPS. To enable HTTPS you must provide your own TLS certificate when -running `rbt dev run`. +By default `rbt dev run` backends use plain HTTP, not HTTPS. That is +convenient, but browsers only use HTTP/2 over TLS, and without HTTP/2 +two limits apply. Every reactive reader holds its own WebSocket, and +browsers allow only around 200 open WebSockets per page (255 in +Chrome); beyond that, new ones fail and the Reboot client logs a +warning. Other calls share a handful of HTTP/1.1 connections per +host, so many outstanding calls queue behind each other, which the +client also warns about. Over HTTPS the browser multiplexes all of +them over one HTTP/2 connection. To enable HTTPS you must provide +your own TLS certificate when running `rbt dev run`. ### Generating a Certificate with [mkcert](https://github.com/FiloSottile/mkcert) diff --git a/documentation/docs/surfaces/web.mdx b/documentation/docs/surfaces/web.mdx index 37a40e9c7..c16168c66 100644 --- a/documentation/docs/surfaces/web.mdx +++ b/documentation/docs/surfaces/web.mdx @@ -216,9 +216,10 @@ flag does. public URL plus `/__/oauth/callback` to your identity provider. :::tip Use TLS -HTTP/2 multiplexing needs a TLS endpoint, and browsers cap the number -of plain-HTTP connections to one host. An app with many outstanding -calls — reactive readers count — will hit that cap and needs HTTPS. +Browsers only use HTTP/2 over TLS. Over plain HTTP, every reactive +reader holds its own WebSocket, and browsers allow only around 200 +of those per page (255 in Chrome), so an app with many concurrent +readers needs HTTPS. See [the `rbt` CLI](/rbt_cli#bring-your-own-certificate-with-rbt-dev-run) for local certificates. ::: From 6d9a7043a021e82ae0517c3b3e28ed188d43e8ea Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 12/16] Docs: add the required `mcp=` to the by-hand examples The `ChatRoom` example in the Python guide and on the Pydantic page omitted `mcp=`, which is a required argument, so anyone typing the guide in would have hit a validation error on their first `rbt dev run`. The guide should also say why the argument is there, since it is the one decision the API forces per method. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- documentation/docs/define/pydantic.mdx | 8 ++++++++ documentation/docs/get_started/python.mdx | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/documentation/docs/define/pydantic.mdx b/documentation/docs/define/pydantic.mdx index d3c66e52b..79f9fc830 100644 --- a/documentation/docs/define/pydantic.mdx +++ b/documentation/docs/define/pydantic.mdx @@ -37,11 +37,13 @@ ChatRoomMethods = Methods( request=None, response=MessagesResponse, description="Every message posted to the room so far.", + mcp=None, ), send=Writer( request=SendRequest, response=None, description="Post one message to the room.", + mcp=None, ), ) @@ -63,6 +65,12 @@ All Pydantic fields must include a `tag` parameter using `Field(tag=N)`. This is required for safe backwards compatibility. ::: +:::important +Every method must say whether an AI may call it: `mcp=Tool()` or +`mcp=None`. There is no default, so leaving it off is an error. See +[Creating tools for the AI](#creating-tools-for-the-ai). +::: + :::info Naming conventions Your state class should end in `State`, e.g., `UserState`, `CounterState`. diff --git a/documentation/docs/get_started/python.mdx b/documentation/docs/get_started/python.mdx index dccc792fe..4f9626531 100644 --- a/documentation/docs/get_started/python.mdx +++ b/documentation/docs/get_started/python.mdx @@ -85,8 +85,9 @@ your \`messages\` will be stored. Next, define the methods on your data type by calling \`Methods\` and passing it each method's name, its kind (\`Reader\`, \`Writer\`, -\`Transaction\`, or \`Workflow\`), and its \`request\` and \`response\` -classes. +\`Transaction\`, or \`Workflow\`), its \`request\` and \`response\` +classes, and whether an AI may call it: \`mcp=Tool()\` exposes it as +a tool, \`mcp=None\` keeps it out of reach. There is no default. Lastly, define your API by passing the API \`Type\`, \`ChatRoom\`, to the \`API\` function. @@ -143,11 +144,13 @@ ChatRoomMethods = Methods( request=None, response=MessagesResponse, description="Every message posted to the room so far.", + mcp=None, ), send=Writer( request=SendRequest, response=None, description="Post one message to the room.", + mcp=None, ), ) From f74b3300f6eb6ad234bbd249bdfe7165a7342289 Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 13/16] Web: point the connection-limit warnings at the moved docs page The client's connection-limit warning linked to the old CLI page, and the redirect drops the anchor, so the link landed at the top of the page instead of on the certificate instructions. The sibling WebSocket-limit warning told readers to "see the documentation" without saying where. A warning that names the fix should take the reader straight to it. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- reboot/web/index.ts | 6 ++++-- tests/reboot/react/test_long_running_fetches/test.py | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/reboot/web/index.ts b/reboot/web/index.ts index cac6e9a99..680b135b5 100644 --- a/reboot/web/index.ts +++ b/reboot/web/index.ts @@ -571,7 +571,9 @@ export class WebSockets { "number of concurrent streams. Reboot uses HTTP/2 by default " + "when you use TLS. You should definitely use TLS when you deploy " + "your application in the cloud, but you can also use TLS when " + - "running `rbt dev`, please see the documentation for how to do so." + "running `rbt dev`. See " + + "https://docs.reboot.dev/rbt_cli#bring-your-own-certificate-with-rbt-dev-run " + + "for how to do so." ); } }); @@ -927,7 +929,7 @@ function trackFetch(url: URL) { )} are either long-running or ` + `queued because your browser limits the total number of simultaneous connections ` + `to the same host (${host}). You can solve this by ensuring you use HTTP/2 which ` + - `requires TLS. See https://docs.reboot.dev/tools/cli/#bring-your-own-certificate-with-rbt-dev-run ` + + `requires TLS. See https://docs.reboot.dev/rbt_cli#bring-your-own-certificate-with-rbt-dev-run ` + `for details on how to use TLS with 'rbt dev' (which works similarly for 'rbt serve').` ); diff --git a/tests/reboot/react/test_long_running_fetches/test.py b/tests/reboot/react/test_long_running_fetches/test.py index 81e0bc615..19c198cfa 100644 --- a/tests/reboot/react/test_long_running_fetches/test.py +++ b/tests/reboot/react/test_long_running_fetches/test.py @@ -64,8 +64,8 @@ def look_for_warning( f":{parsed_uri.port}). You can " "solve this by ensuring you use " "HTTP/2 which requires TLS. See " - "https://docs.reboot.dev/tools" - "/cli/#bring-your-own-certificate" + "https://docs.reboot.dev/rbt_cli" + "#bring-your-own-certificate" "-with-rbt-dev-run for details on " "how to use TLS with 'rbt dev' " "(which works similarly for " @@ -87,8 +87,8 @@ def look_for_warning( f":{parsed_uri.port}). You can " "solve this by ensuring you use " "HTTP/2 which requires TLS. See " - "https://docs.reboot.dev/tools" - "/cli/#bring-your-own-certificate" + "https://docs.reboot.dev/rbt_cli" + "#bring-your-own-certificate" "-with-rbt-dev-run for details on " "how to use TLS with 'rbt dev' " "(which works similarly for " From 094af330a9a34f1cf60737bab772b0c0c0704c52 Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 14/16] Auth: point the missing-authorization warning at the moved docs section The warning logged under `rbt dev run` when a call reaches a type with no `authorizer()` linked to a docs section that no longer exists, so the old URL only redirected to the top of the authorization page. The warning is most people's first contact with authorization, so it should land on the paragraph that explains it. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- reboot/aio/auth/authorizers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reboot/aio/auth/authorizers.py b/reboot/aio/auth/authorizers.py index 9c4c6895a..9250b22fb 100644 --- a/reboot/aio/auth/authorizers.py +++ b/reboot/aio/auth/authorizers.py @@ -390,9 +390,9 @@ async def authorize( f"*** {self._state_name}.{method_name.split('.')[-1]} IS " "MISSING AUTHORIZATION *** Calls to this method are ONLY " "ALLOWED during development and will be DENIED in " - f"production. See {DOCS_BASE_URL}/learn_more/auth#default-authorizer " - "for more details. Will silence this message for the next 1 " - "minute." + f"production. See {DOCS_BASE_URL}/users/authorization" + "#relaxed-during-development for more details. Will " + "silence this message for the next 1 minute." ) ) From 924bbb3851cecc39d1bf7a87d047657c58db8dae Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 15/16] Docs: name the `OAuthTokenManager` methods as code symbols The method headings on the OAuth token manager page read "Fetch" and "Store", capitalized like prose, while the methods are called `fetch` and `store`. A heading that carries the name as it is typed is what a reader searches for and what the surrounding pages link to. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- documentation/docs/library_services/oauth_token_manager.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/library_services/oauth_token_manager.mdx b/documentation/docs/library_services/oauth_token_manager.mdx index 8bcbb16ec..11182e4a8 100644 --- a/documentation/docs/library_services/oauth_token_manager.mdx +++ b/documentation/docs/library_services/oauth_token_manager.mdx @@ -98,7 +98,7 @@ The stored value is an `OAuthTokens` message: ## Methods -### Fetch +### `fetch` A `reader` that returns the stored tokens for a user. `found` is false when nothing is stored for that user — or once their tokens have been @@ -140,7 +140,7 @@ exist yet, so the very first `fetch` can abort with a "state not constructed" error rather than report `found: false`. Treat both the same way — "this user hasn't connected the service yet". -### Store +### `store` A `transaction` that encrypts and persists tokens for a user, **replacing** any previously stored ones wholesale — including the From f50b9ae2424668b4ccafcd6b3727dbc75730043b Mon Sep 17 00:00:00 2001 From: Robert-Jan Huijsman <22160949+rjhuijsman@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:51:47 +0000 Subject: [PATCH 16/16] Docs: file `UI` methods under AI chat apps, and agents under Going further Neither `UI` methods nor "Agents inside your app" is a frontend, yet both sat as siblings of the web, React Native, and AI chat pages. `UI` methods are a detail of building an AI chat app, and an agent running inside your app is a backend feature that builds on tasks and side effects. Filing them where they belong keeps the frontends section a list of frontends. No page moved; only the sidebar changed. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Votcp4rQnqt25q6XJGNavW --- documentation/sidebars.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 8cc1a85b4..6739aa9a3 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -131,19 +131,16 @@ const sidebars = { label: "React Native apps", }, { - type: "doc", - id: "surfaces/ai_chat", + type: "category", label: "AI chat apps", - }, - { - type: "doc", - id: "surfaces/ui_methods", - label: "UI methods", - }, - { - type: "doc", - id: "agents", - label: "Agents inside your app", + link: { type: "doc", id: "surfaces/ai_chat" }, + items: [ + { + type: "doc", + id: "surfaces/ui_methods", + label: "UI methods", + }, + ], }, ], }, @@ -195,6 +192,11 @@ const sidebars = { items: [ "tasks", "side_effects", + { + type: "doc", + id: "agents", + label: "Agents inside your app", + }, "idempotency", "errors", "secrets",