# {{projectName}} / {{appName}}

Voltro frontend scaffold (template: **frontend-app**).

The **reactive end-to-end loop** — the framework's headline feature, in one
page. A live notes list (`useSubscription`) plus a create form
(`useMutation`) with **zero-boilerplate auto-optimistic** updates. This is the
only template that wires a web frontend to an api; every other `frontend-*`
template is backend-less.

## Scaffold the pair

`frontend-app` consumes a sibling api — it is meant to be scaffolded **together
with [`api-backend`](../api-backend)** (which exposes the `notes.list` query +
`notes.create` mutation this page subscribes to / calls):

```bash
voltro create-project demo --api=api-backend --web=frontend-app
cd demo
pnpm install
pnpm dev            # boots BOTH apps (api on :4000, web on its portRange port)
```

Open the web app, add a note, and watch it appear in the list **instantly** —
no refetch, no polling. Open a second browser tab: a note added in one tab
shows up live in the other.

## How the loop works

```
 Browser                         api (api-backend)
 ───────                         ─────────────────
 useSubscription('app',          notes.list  (defineQuery, source: 'notes')
   'notes.list')  ───WS────►       └─ streams a fresh snapshot/delta on every
        ▲                              write to the tenant's `notes` rows
        │ delta (WebSocket)
        │
 useMutation('app',              notes.create  (defineMutation,
   'notes.create')  ──rpc──►       target: { table:'notes', op:'insert' })
   .mutate({...})                   └─ one atomic DB write → commit → delta
```

1. **`useSubscription('app', 'notes.list')`** opens ONE live WebSocket
   subscription. `data` is the current rows; it re-renders whenever the
   `notes` table changes — from THIS tab, another tab, a workflow, or an
   out-of-band DB write.
2. **`useMutation('app', 'notes.create').mutate({...})`** invokes the
   mutation. Because the descriptor declares `target: { table:'notes',
   op:'insert' }`, the framework **auto-prepends an optimistic row** to the
   subscription the instant you submit — then replaces it with the real row
   (or reverts it) when the rpc resolves. No `.withOptimistic`, no
   `useOptimistic`, no `startTransition`.

Optimistic rows carry `optimistic: true`; the page renders them faintly until
the server delta supersedes them (see `src/pages/index.tsx`).

## The `apis` wiring

```ts
// app.config.ts
apis: {
  app: { package: '@{{projectName}}/api' },   // the sibling api in this project
}
```

The map key (`'app'`) is the **lookup name** every hook takes as its first
argument — `useSubscription('app', …)`, `useMutation('app', …)`. The
`package` resolves the sibling api workspace package; its port is
auto-discovered from its own `app.config.ts`, and codegen pulls its typed rpc
surface. Consume more apis by adding more entries (each gets its own
WebSocket + reconnect cycle).

## Files

```
app.config.ts            type:web + the `apis: { app }` wiring
package.json             depends on @{{projectName}}/api (the sibling) + @voltro/client
src/pages/
  layout.tsx             root shell (header + <main>)
  index.tsx              the reactive notes page — useSubscription + useMutation
```

## Pairing with a different api

This page is hard-wired to api-backend's `notes` domain. To point it at your
own api, change `app.config.ts`'s `apis.app.package`, then swap the rpc tags +
the `Note` type in `src/pages/index.tsx` to match your query/mutation
descriptors. The hook shapes (`useSubscription` / `useMutation`) are identical
for any api.

## Multi-tenancy note

The dev `AuthMiddleware` resolves the tenant from the `x-tenant` header,
defaulting to `'acme'` (the `TENANT` constant in `index.tsx`). The `notes`
table carries the `tenant()` mixin, so reads are auto-scoped and
`notes.create` guards that the submitted `tenantId` matches the caller's —
a typed `TenantMismatch` otherwise. Wire real auth before production (see the
authentication docs).
