- name
- build-an-app
- description
- Fast end-to-end workflow for turning this starter into a working domain app from a short or vague request. Use when asked to build, make, create, or prototype an app, tool, tracker, dashboard, booking, inventory, or CRM system with Agent-Native. Read before the first edit.
- scope
- both
- metadata
- {"internal":true}
# Build an App
Done means one useful domain workflow with a designed route, shared UI/agent
actions, correct access, agent context, and a smoke run that proves its key
rule. Finish a narrow slice before expanding it.
This skill edits source files. If this session cannot write files (a deployed
app's runtime agent), say the change needs a code session and stop.
The four-area parity rule from `adding-a-feature` (UI, action, instructions,
application state) applies to everything you add. This skill supplies the
order and decisions; use the edit-point reference for the scaffold you have.
## 1. Orient
Read `AGENTS.md`, this skill, and only the files you will edit. Skip broad repo
surveys, `README.md`, and `DESIGN.md`. Use the bundled docs/source commands in
`AGENTS.md` only to answer a concrete API question; do not web-research
framework APIs.
## 2. Decide
Choose these six things before writing code:
1. **Slice**: the one workflow that proves the app. For a booking app: create a
booking, reject an overlap, cancel, weekly grid per room. Name what you are
deferring (recurrence, notifications, roles, integrations) unless requested.
2. **Who sees each table**: pick one row of the access table in
`references/data-and-access.md`: private, team-visible, shared on invite, or a
child that inherits its parent's access. Making a team resource private per
user is the usual wrong turn. Everyone then sees an empty list, and no
conflict check can see anyone else's rows. Give each table `owner_email`
and/or `org_id`, directly or via `ownableColumns()`; `org_id` alone passes
`[db-tool-scoping]`. Doctor checks the schema marker, not query authorization;
never denylist or opt out `rooms` or `bookings`.
3. **Invariants**: rules that must hold under concurrent requests (no overlap,
stock never negative, unique slug). Enforce them in a write action inside a
transaction or with a database constraint; the UI alone is insufficient.
4. **Actions**: `list-<things>`, `create-<thing>`, a single `update-<thing>`
patch per resource, and the domain verbs (`cancel-booking`). Fewer,
orthogonal tools make the agent choose better.
5. **Route**: give the domain its own route (for example, `/rooms`). Preserve
existing routes and set `app.homePath` so sign-in lands on the domain when
that is the requested home screen.
6. **Agent context**: URL ids and filters (for example `roomId`, `week`) that
belong in `navigation`.
## 3. Build: write everything, then verify once
In a generated app, read `package.json` at
`["agent-native"].scaffold.template` and choose the matching reference:
- `chat`: `references/edit-points-chat.md`
- `default`: `references/edit-points-default.md`
- workspace root: use `adding-workspace-apps` to create an app, then follow the
new app's own `AGENTS.md` and scaffold reference.
- another domain template: use its `AGENTS.md` and `adding-a-feature`; do not
apply Chat or default shell instructions to it.
If metadata is absent, inspect the actual route, layout, plugin, and navigation
files before choosing an edit point. Do not infer a shell from the app name.
Write the UI, actions, instructions, and state before running the verification
steps below. Do not leave placeholder or comment-only screens.
`references/rooms-example.md` gives a booking slice to adapt. It was verified
against the Chat starter; use the selected edit-point reference for the shell.
Use `frontend-design` for a deliberate visual direction and finish the
requested screens and interactions.
### Data
- Use PostgreSQL only, and keep migrations additive: new columns are nullable
or have a default, and nothing is dropped or renamed. Never edit an applied
entry; append a new named one.
- Set `ownerEmail` from `ctx.userEmail` and `orgId` from `ctx.orgId` on inserts
where those columns exist. Never rely on a development identity fallback.
- Ids are text from `crypto.randomUUID()`.
- Bound every list (a time range or `.limit()`), select only the columns it
renders, and index the columns it filters and sorts on.
- Store normalized UTC ISO timestamps; collect/display local times in the
user's timezone. Validate offsets at the action boundary before parsing.
The access table, the concurrency pattern, and the security rules are in
`references/data-and-access.md`.
### Actions
- `schema` is zod, with `.describe()` on every field stating its units and
format (for example, "ISO 8601 date-time with offset").
- Reads use `http: { method: "GET" }` and `readOnly: true`. Leave writes on the
default POST so `useActionMutation` needs no `method`.
- Reject with `fail(message, { errorCode, statusCode })` from
`@agent-native/core/action`: 409 for a conflict, 404 for not found, 403 for
forbidden. A returned `{ error }` is counted as success everywhere, and a
bare `throw` reaches the browser as a generic 500.
- Return plain objects that include the `id`. Keep LLM calls out of actions;
AI-shaped work belongs in the agent chat.
- When the request is batch-shaped ("add these 20"), write one action that
takes an array and writes it in one transaction.
### UI
- Load and write data with `useActionQuery` and `useActionMutation` from
`@agent-native/core/client/hooks`. No `fetch`, and no `/api/*` routes for app
data. Those routes are only for uploads, webhooks, OAuth callbacks, and streaming.
- While data loads, show a `Skeleton` in the shape of the final layout, not a
spinner or "Loading…".
- Writes should feel instant. In `onMutate`, update `["action", "<name>", params]`
with `setQueryData`; in `onError`, roll back and show
`toast.error(actionErrorMessage(error) ?? "<fallback>")`.
- Import controls from `@/components/ui/*`. When one is missing (dialog,
select, skeleton, alert-dialog), add a one-line re-export like `button.tsx`:
`export * from "@agent-native/toolkit/ui/dialog";`.
- Use Tabler icons only. Confirm with `AlertDialog`; never use `window.confirm`,
`alert`, or `prompt`.
- Don't add a page title that repeats the nav item, a subtitle under a title,
or a stats strip. The data is the page. For visual direction, see `frontend-design`.
- Follow the app's existing localization pattern for user-facing copy.
- Add a pure `link` builder with `buildDeepLink()` to actions that create or
list navigable resources; see `adding-a-feature` for the shape.
### Agent instructions
Append one section of about 1,500 characters at most to `AGENTS.md`. The
runtime prompt cuts the file off at 6,000 characters. Cover:
- a one-line purpose with the routes
- the invariants, and how to report a rejection
- time and format conventions
- the new `navigation` fields
- an action table with unpadded cells
Keep the action table aligned with the scaffold's primary tool list when one
exists; other actions should remain discoverable through `tool-search`. Leave
unrelated AGENTS.md sections, README, and DESIGN.md as they are.
### Real-time
Agent writes need no wiring, because the chat run refreshes action queries
after each tool. Add `useDbSync({ realtime: { reason } })` to a page only when
other people change its data while it is open and the user must see the change
before refreshing. A booking grid doesn't qualify: the server rejects bookings
made from a stale view. See `real-time-sync`.
## 4. Verify, once, in this order
1. Start `pnpm exec agent-native dev` in the background, without `--open`, and
wait for the ready URL. Startup regenerates `.generated/action-types.d.ts`
and the actions registry, so a typecheck run before this is meaningless.
On loopback, automatic dev sign-in works when the local database has no real
users. If the sign-in page appears, use an existing local account or switch
to a separate disposable local database; do not create accounts or set
`AUTH_DISABLED`.
2. Run `pnpm typecheck`, then `pnpm agent-native:doctor`. Plain `pnpm doctor`
runs pnpm's own command and exits 0 without running Agent-Native Doctor. Fix
every finding, then rerun only what failed.
3. Check the agent path. Call a guarded write twice with the same input via
`pnpm action <name> --key value`. The second call must fail with the expected
message; read back the saved state through the matching list/get action.
The CLI prints the failure message, not its structured `errorCode`, so also
verify any code-specific behavior in the action contract or browser.
4. Run one browser smoke on the domain route:
- create a record
- trigger the rejection: a toast appears and nothing is written
- cancel or undo, repeat the previously rejected write, and confirm it
succeeds
Inspect console and network failures. Fix errors from the new route or actions
and identify existing scaffold messages separately. The only expected 4xx/5xx
is the rejection being tested. Capture the finished workflow.
5. Stop the dev server and close the tab.
Skip the production build, unrelated test suites, and README or DESIGN rewrites.
To triage failures, see `references/verification.md`.
## 5. Report
State what works and the proof, what you deferred, and anything the user still
has to do (for example, set `DATABASE_URL` before deploying). Never report a
check you didn't run.
## Related
- `adding-a-feature`: the parity checklist for each later feature in this app.
- `frontend-design`, `actions`, `sharing`, `real-time-sync`, and `security`:
go deeper on one area when the references aren't enough.
Ver en GitHub