# apphaven — guide for AI agents

apphaven hosts web apps for people who aren't developers. You write the app's files through these
tools; apphaven gives it a real backend (database, sign-in, access rules) and hosts it.

- **Frontend:** your code. The usual shape is a Vite project (React, Vue, Svelte, Tailwind…)
  made installable, so the user has their app on their phone's home screen like any other app
  (see "Make it installable" below). Plain HTML/CSS/JS pages suit a website: content that
  loads fast and that search engines read. The two combine: pages next to the app. No
  server-side rendering, no Node server code.
- **Backend:** no server code. You describe data, access rules and operations in YAML files in
  `apphaven/`. apphaven turns them into a database and a REST API.
- **Accounts:** every app gets email + password sign-in, address confirmation and password
  reset for its users, built in. Email sign-in links are optional (`auth.magic_link`). No external
  auth or database provider setup is needed.

## Workflow

1. `app_status` (no app given lists them) / `create_app` — one app per project. The user's apps are private to them.
   An account holds at most five apps that aren't live yet (never published, or dark since their
   plan ended); `create_app` says so when it's full. Before starting a new app, check `app_status`:
   if the user's idea fits an app they already have, build there, and if they're at the limit,
   ask them to delete one they no longer need from the console, or publish one. Rarely, when
   Apphaven is near its capacity, `create_app` asks to try again tomorrow; nothing else is affected.
2. `get_docs` — read `config` before writing YAML, and `sdk` before writing frontend data code
   (and `files` when the app takes photos or documents from its users, `realtime` when several
   people look at the same thing at once: a game, a chat, a shared board).
3. `write_files` — write the frontend and the `apphaven/*.yaml` config. Send several files per call.
   For small changes to an existing file, `edit_file` (exact find-and-replace) instead of resending it.
4. `validate` — checks config and shows the database changes a deploy would make. Fix every error.
5. `deploy_preview` — builds, migrates the preview database, and publishes to the preview URL.
   Read the summary: it lists warnings and database changes.

   Both also **scan the files** and refuse to deploy (and going live refuses too) when they
   find a secret: API keys, tokens, connection strings, private keys, `.env` files. Everything in
   the app's files is served to every visitor, so there is nowhere safe for a secret in a
   frontend; remove it and tell the user to revoke it. (Server-side connectors for things like
   payments are coming; until then, don't call third-party APIs that need a secret.) The scan also
   stops `http://` scripts and public access to fields like `password` or `ssn`, and warns about
   `eval`, HTML built from data (XSS), public personal fields, and `update: true` / `delete: true`.
   A documented placeholder can be accepted with `scan-ignore` on its line.
6. Test it yourself before handing it over:
   - `test_api` (reads) and `test_api_write` (POST, PATCH, PUT, DELETE) — call the API as test
     users (`as: "alice"`, `as: "bob"`) or signed out, to check each access rule from both sides
     (alice can't read bob's data, bob can't change alice's, a visitor can submit the form, etc.).
   - `view_page` — screenshot a page (optionally signed in as a test user) with console errors and
     failed requests. Check the main screens on a phone (`device`: phone, tablet, desktop;
     `full_page: true` for the whole page). Add `steps` to use the page like a person first
     (`{"click_text": "Add task"}`, `{"fill": "#title", "value": "Milk"}`, `{"click": "form
     button"}`, `{"wait_for": ".task"}`, `{"hover": ".card"}`, `{"press": "Tab"}`,
     `{"scroll_to": "#footer"}`), so forms and buttons get tested, not just first screens.
     "Are you sure?" boxes are answered OK (`dialogs: "dismiss"` for Cancel). One page at a
     time: a second screenshot asked for while one is still opening can be refused; wait for the
     first, then ask again.
   - `query_data` / `change_data` (insert, update, delete), `get_logs` (recent API errors).
   - Test ordinary sign-up and address confirmation separately, without `as`: test users are
     already confirmed. Follow "Testing sign-in in preview" in `get_docs` topic `sdk`.
   Fix and redeploy as often as needed, then give the user the preview URL (the `url` in the
   deploy_preview result) to open and try; they come back to the chat with what should change.
7. Going live is the user's step. The first time, they **publish from the dashboard**
   (`publish_url` in `app_status`; give them that link), where they choose whether the live app
   starts with the test version's accounts and data. After that, `deploy_live` pushes later
   changes to the live app when the user asks. Changes that would delete live data need the
   user's approval in the dashboard; tell them so. Publishing needs an active plan or a
   publishing allowance: `app_status` explains when the app lacks that access. Explain the
   current availability factually; building and testing remain available without publishing.
   Billing belongs to the owner in Apphaven. Do not recommend a plan, promote an upgrade or
   start checkout through the connector.

8. When a piece of work is done, `send_feedback` is a courtesy, never a requirement: a short note
   to the Apphaven team on what blocked you, broke, confused, would help, or worked well. It
   shapes what you can do for your users here. Nothing is answered in the conversation.

### What tool permissions mean

Hosts may ask for permission according to everything a tool can do, even when this call only
takes a screenshot or requests a dry run. Permission to work in preview does not authorize
publishing, changing live data, or sending information to someone else.

- `write_files`, `edit_file` and `restore_version` can overwrite or remove workspace files.
- `deploy_preview` replaces the test version and can remove preview data. npm builds run the
  app's build script and can contact external services; the test version remains private.
- `test_api` uses GET, but `as` can create/confirm an account and sign it in, and auth links
  can confirm accounts or consume a sign-in token. It is not a read-only tool. `test_api_write`
  can also overwrite/delete data, upload sample files and trigger preview mail. Only the
  owner's own address is eligible for delivery from preview.
- `view_page` runs the app and its scripts in a browser. Its actions can change/delete data,
  upload files, send mail and follow links or contact services outside Apphaven.
- `deploy_live` changes a publicly available app. `change_data` and `manage_users` can remove
  data or accounts in the selected environment; their existing production safeguards apply.
- `send_feedback` shares a note with the Apphaven team outside the owner's workspace. These
  tools cannot recall it. Never include personal details or the app's data.

The retrieval tools (`get_docs`, `app_status`, `read_files`, `query_data`, `list_users`,
`get_logs`, `read_emails`) and `validate` are read-only. `create_app` adds an app without
replacing an existing one. Tool permission labels do not replace the owner's authorization.

## Your own domain

The owner can put the live app on an address they own, like `www.theirshop.com`. They enter it in
the console's Settings (not you: it's their domain); from then on `app_status` shows the domain's
state, the one record to add, and the reason when it's stuck, and the owner may paste you a request
to help. Walk them through it:

1. **Ask where the domain is registered** if you don't know (GoDaddy, Namecheap, Squarespace,
   Cloudflare, Wix, IONOS…). The record goes in that domain's DNS settings there, nowhere else.
2. **Give the record exactly as `app_status` shows it**: a CNAME, name `www.theirshop.com` (some
   providers want only `www`), pointing to `<slug>.customers.onapphaven.com`. Say which screen
   it's on at their provider ("DNS", "DNS records", "Manage DNS") and that any existing record
   for `www` must go. Nothing else changes; no A record, no nameserver change. If their DNS is
   at Cloudflare, the record must be **DNS only** (grey cloud): a proxied record hides the CNAME
   and `app_status` then reports an address record instead.
3. **The bare domain** (`theirshop.com`) can't point here directly, so people who type it need a
   forwarding rule at the same provider, from `theirshop.com` to `https://www.theirshop.com`
   (GoDaddy and Squarespace call it Forwarding, Namecheap "Redirect Domain", Cloudflare a Redirect
   Rule). Suggest it once the www address is live.
4. **Check, don't promise.** `app_status` re-checks the record each time you call it (at most once
   a minute): `waiting_dns` means the record isn't visible yet (minutes usually, up to a day at
   some providers; the `detail` says if it points somewhere else), `issuing` means it's in place
   and the certificate is coming, `active` means done. Then the live address is the domain, and
   the onapphaven.com address sends people there.

## Test version and sign-in

There are three identities to keep separate:

| Identity | What it gives access to |
| --- | --- |
| The owner's **Apphaven account** | The dashboard and the private preview. Signing in to Apphaven opens the preview; it does not sign them in inside their app. |
| An **account inside the app** | The app's features, data and roles. The owner signs up here separately, even if they use the same email address as their Apphaven account. Passwords and sessions are separate. |
| A **test user** (`as: "alice"`) | An app account the tools create and sign in automatically in preview. Its address is already confirmed, so it checks authenticated screens and access rules, but does not exercise ordinary sign-up or confirmation. |

Preview and production have **separate databases and separate user accounts**. Preview starts as
a copy of production (`change_data` with `action: reset_preview` refreshes it).
Reset prepares a separate copy, removes copied passwords, provider tokens, sessions and verification
tokens, then applies the test version's schema before switching it over. If preparation fails, the
existing test version stays usable with its data unchanged. A message saying the reset was
interrupted means you can retry the same action to finish it safely. Sign in afterward with fresh
test users or new sign-ups; copied production accounts cannot sign in with their live passwords.

**Handing the test version to the owner.** Give them the preview URL and explain the steps:
open it while signed in to Apphaven, sign up inside the app using their **Apphaven account's
email address**, open its confirmation email, then return to the app and sign in. Only that
address is eligible for real email delivery from preview; other addresses' mail is captured
for you to inspect with `read_emails`. Check the mail status if it does not arrive. The owner
chooses their own app password; you do not need it.

The owner is not automatically an app admin. When the app has an admin/organiser role, use
`manage_users` (`action: set_role`) on their app account after they sign up, so they can try
their own controls. Do the same on production after publishing (unless you launch with
preview data, below).

**Cleaning up before going live.** `manage_users` with `action: delete` removes leftover test accounts (records that
need them go too: check what it reports). Delete made-up sample records too. When the owner
publishes from the dashboard, they can bring the test version's accounts and data along (same
passwords and roles; `@apphaven.test` users are left out), so make sure what's there is real.
Once the live app has its own data, the two databases stay separate.

### The preview is private

Only the app's owner can open the preview, signed in to apphaven: the preview URL sends anyone
else to a sign-in page, and its API answers 401 `preview_locked`. So a preview link is for the
owner to look at their own app, not something to hand to testers or members; when other people
should use the app, the owner publishes it. Your `test_api` and `view_page` calls get in automatically, and
the test users you create sign in inside the preview as before.

The confirmation and reset links in the preview's sign-in mail work on any device, because the
owner often opens their own mail on a phone that isn't signed in to Apphaven: each carries a pass
for that one link. A confirmation link confirms the address there, and the phone then says to go
back to the test version and sign in. A reset link opens Apphaven's built-in page
(`/_apphaven/reset-password`, the SDK's default) and the new password is set there; a reset page
of the app's own is an ordinary preview page, so it asks the owner to sign in to Apphaven first.
A sign-in link (magic link), or any other address the phone opens, asks them to sign in to
Apphaven first too.

### When sign-in fails or another method is requested

Read the failing endpoint, response code and message before changing anything: `preview_locked`
is the private preview gate, an unconfirmed address needs its confirmation link, and a model's
`forbidden` response may be an access rule. A 401 or 403 alone does not identify the cause. The
sdk docs have the troubleshooting steps and a complete sign-up test.

Apps currently support email/password and optional email sign-in links. Google or Apple sign-in
for an app is not provided; signing in to the Apphaven platform with Google is separate.
Explain that capability gap and offer the supported flow. Keep the working Apphaven auth and
data in place when diagnosing preview restrictions. Moving to an external provider is a
separate choice for the owner, with setup and data implications to explain first; placeholder
provider settings do not make a sign-in option work. You can report the missing capability
through `send_feedback`.

## Files

```
package.json          ← a Vite project: apphaven builds it and serves dist/
vite.config.js, index.html, src/...
public/               ← files served as-is next to the app (plain pages, icons, robots.txt)
apphaven/app.yaml     ← backend config (all apphaven/*.yaml files are merged)
```

- Vite/npm: if `package.json` has a `build` script, apphaven runs `npm install` and
  `npm run build` and serves `dist/` (change with `frontend.output_dir`). Don't upload
  `node_modules`. `@apphaven/client` is pre-installed.
  Unchanged frontend inputs can reuse the last successful build for up to an hour, including
  when only `apphaven/` backend settings change. Editing frontend files (including a dependency
  lockfile) triggers a new build. An Apphaven platform update also invalidates saved builds.
- Plain files, no build: with no `package.json`, every file except `apphaven/` and dotfiles is
  served as-is (`index.html`, `app.js`, `style.css`…). The sdk is at `/_apphaven/sdk.js`.
- Either way, `/about` serves `about.html` or `about/index.html`. Unknown paths fall back to
  `index.html` (set `frontend.spa: false` to get 404s instead; a `404.html` is used if present).

### Make it installable

An app the user opens every day belongs on their phone's home screen. A Vite app becomes one
with `vite-plugin-pwa`: a manifest (name, colours, icons), a service worker that keeps the app
opening instantly, and nothing else to set up; apphaven serves the manifest and the worker with
the right headers. The recipe is in the examples (`get_docs` topic `examples`, "Installable").
Two rules keep it honest:

- Use `registerType: "autoUpdate"` and keep `/api` and `/_apphaven` out of the worker's
  navigation fallback, exactly as the recipe shows. Then every deploy reaches installed phones
  on their next open, and sign-in and data never go through a cache.
- Icons are square PNGs (192 and 512). Ask the user for their logo through the Add files page
  and make the icons from it, or draw a simple one; a made-up path is a blank icon.

Install from the **live** app, not the preview (the preview is private and signs the owner in
each time). Tell the user how: on an iPhone, open the live address in Safari, Share, "Add to
Home Screen"; on Android, the browser offers "Install app" or it's in the menu.

### A website and an app together

A business usually wants both: pages that load fast and that search engines read (home, about,
prices), and the app behind a sign-in. Put the pages in `public/` as plain HTML (`public/about.html`
is served at `/about`) next to the Vite app, which takes `/` or `/app`; one project, one deploy,
one domain. Keep the pages' links to the app relative (`/app`) and the shared look in one CSS file.

### Files from the user (logo, photos, documents)

These are the owner's own files for the app (its logo, the menu photos). Files the app's users
send it (a profile photo, a receipt, a CV) are image and file fields instead: get_docs "files".

Don't ask the user to paste or attach files in the chat: chat attachments never reach you as bytes,
and typing a picture out as base64 is slow and error-prone. The same goes for photos you find on
the web: fetching a page gives you text, never the image's bytes, so `write_files` with base64
can't carry them (it's for small files you make yourself, like an SVG icon), and a made-up path
is a broken image. Photos come from the user: send them to the app's
**Add files** page (`files_url` in `create_app` / `app_status`, which is
`<dashboard>/apps/<id>/files?add=files`):

> I need your logo. Drop it in here: https://…/apps/app_123/files?add=files — I'll wire it in.

What they drop lands in the workspace under `uploads/` (they can pick another folder), the test
version is redeployed so the files are served at `/uploads/<name>` (in a Vite app too: Apphaven
adds the owner's files to the built output at their own path, unless the build already serves
that path; hidden paths are excluded). Whole-project imports are build inputs and do not get
this extra serving step. Photos lose their location data on the way in. Your **next tool result**,
whatever tool it is, ends with a note listing the new paths and image sizes. `read_files` shows
them too, marked `[added by the user]`. Then reference them by path (`<img src="/uploads/logo.png">`)
and `deploy_preview`.

For files you make yourself, `write_files` with `encoding: base64` works, and a bad string gets an
error naming the offset. Prefer SVG for icons and artwork: it's text, so nothing can be mistyped.

## Email

Apps send email with no setup, from `"<App name>" <<slug>@viaapphaven.com>` (the test version
adds `+preview`):

- **Sign-in mail** is automatic: password reset (`app.auth.requestPasswordReset`), sign-in
  links (`auth.magic_link: true`, no passwords at all) and address confirmation. Every password
  sign-up confirms its address before it can sign in: `signUp` returns the user but no session,
  so show "check your email". Test users (`@apphaven.test`) are confirmed on creation in the
  preview; the live app refuses them. See the sdk docs.
- **Operations can email**: `email: { to, subject, template | body }` on a create/update
  operation sends once per record, to the owner, the signed-in user, or addresses in the record
  (`customer.email`). Templates are HTML files in `apphaven/emails/` with `{{ placeholders }}`.
  These are fully supported; sign-in messages use fixed Apphaven copy with the app's name.
  The owner's Mail page previews the deployed templates with sample details at phone and
  desktop sizes, without sending anything. See the config docs.
- **Preview delivers only to the owner's Apphaven account email address.** Other recipients'
  mail is `captured`, which is expected: it is kept for inspection, not sent to their inboxes.
  Every message is kept in the app's mail log: `read_emails` to check its status, and with an
  `id` to read one rendered. Owner delivery can also fail or be limited; check the actual status. Use
  them the way you use `view_page`, and open the links they contain (the confirmation link
  included) with `view_page` to test the whole flow. Live apps send for real: up to 500 a day
  once established (100 a day for the first week, 250 until day 30), with a per-address limit
  for addresses nobody confirmed; they skip addresses that bounced or unsubscribed, and sending
  pauses by itself on a spam complaint or too many bounces (`read_emails` says so).
- Not for marketing: one event, one message, to the people in it. Newsletters and campaigns
  need the owner's own email service.

## The backend in one example

```yaml
# apphaven/app.yaml
models:
  Task:
    fields:
      title: { type: string, required: true, max_length: 200 }
      done: { type: boolean, default: false }
      owner: { type: user, default: $user.id, readonly: true, required: true }
    access:
      read: owner == $user.id
      create: $user != null
      update: owner == $user.id
      delete: owner == $user.id
```

```js
import { createClient } from "/_apphaven/sdk.js";      // Vite: from "@apphaven/client"
const app = createClient();
await app.auth.signUp({ email, password, name });   // then "check your email": no session until the link is opened
const { items } = await app.db.Task.list({ filter: "done == false", sort: "-created_at" });
await app.db.Task.create({ title: "Buy milk" });
```

## Rules of thumb

- Access is **denied unless a rule allows it**. Every model needs `access` rules, or the app can't
  touch it. Think about who may read/create/update/delete each model, and write it down.
- For "things a user owns", use the `owner` pattern above (`type: user, default: $user.id,
  readonly: true`), so the creator is recorded and can't be faked.
- Never put secrets or API keys in frontend files — everything there is public.
- Keep the UI working when signed out: show a sign-in form, and handle 401/403 errors.
- Renaming a field or model? Use `renamed_from` so data is kept. Removing one deletes its data.
- Every deploy saves the files as a version: `restore_version` (the versions are in `app_status`) undoes a bad change
  (files only — data isn't rolled back).
- The user is not technical: explain what you built in plain words, give them the preview URL,
  and don't ask them to edit code or YAML.
