# Client SDK

`cloudbed/client` is the browser half: Preact hooks wired to your server's queries and mutations over a WebSocket, plus built-in auth and a small router. The client entry (`client/index.tsx`) renders into `#app`:

```tsx
import { render } from "preact";
import { useAuth, useMutation, useQuery } from "cloudbed/client";

function App() {
  const auth = useAuth();
  const todos = useQuery("todos") ?? [];
  const addTodo = useMutation("addTodo");

  return (
    <div>
      <p>Welcome, {auth.displayName}</p>
      <button onClick={() => addTodo("New task")}>Add</button>
      <ul>{todos.map((t) => <li key={t.id}>{t.text}</li>)}</ul>
    </div>
  );
}

render(<App />, document.getElementById("app")!);
```

## Data hooks

### `useQuery(name)`

Subscribe to a server query by name. Returns `undefined` until the first result arrives, then the query's value — and re-renders automatically whenever a mutation changes the underlying data. Live updates are pushed by the server; there is nothing to invalidate or refetch.

### `useMutation(name)`

Returns a stable async function that runs the named server mutation with whatever arguments you pass. On failure it rejects with a `TransportError` carrying `{ code, message }` (the `message` of any `Error` your mutation threw).

```tsx
const addTodo = useMutation("addTodo");
try {
  await addTodo(text);
} catch (err) {
  setError(err.message);
}
```

## Auth

Every capsule gets "Sign in with Google" with zero registration. Tokens are scoped to your app's origin and user ids are **pairwise per app**, so capsules can't correlate a user across apps. Before sign-in, users are guests (`guest:local`, or `guest:<name>` via a `?guest=<name>` URL parameter) — `ctx.auth.userId` works either way on the server.

- `useAuth()` — current auth state: `userId`, `displayName`, `provider` (`"guest"` | `"google"`), `isGuest`, `isAuthenticated`, `isLoading`, plus `email` / `emailVerified` / `picture` when signed in.
- `<SignInWithGoogle />` — a ready-made button; accepts `className` and custom `children`, disables itself while auth is loading, and renders nothing if sign-in isn't configured for the deploy.
- `signInWithGoogle()` — start the sign-in flow imperatively (redirects to the auth broker and back).
- `signOut()` — drop the identity and reconnect as a guest.
- `isSignInConfigured()` — whether the deploy has sign-in available.

The redirect round-trip is handled for you: the SDK completes the flow on `/auth/callback` and returns the user to the page they started from.

## Router

A minimal client-side router, included so a capsule needs no other dependencies:

```tsx
import { Link, Route, Router, Routes, useParams } from "cloudbed/client";

function App() {
  return (
    <Router>
      <nav><Link to="/">Home</Link></nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/todo/:id" element={<Todo />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </Router>
  );
}

function Todo() {
  const { id } = useParams();
  // ...
}
```

- `<Router>` wraps the app; `<Routes>` renders the first matching non-wildcard `<Route>`, falling back to `path="*"` when nothing else matches.
- `<Link to>` navigates without a reload (and correctly defers to modifier-clicks and middle-clicks).
- `useParams()` — captured `:param` values; `useLocation()` — current `{ pathname, search, hash }`.
- `useNavigate()` in components — or the module-level `navigate(to, { replace? })` outside them.
