# Authoring an App Skill

You're a coding agent working in the repository of an application that is tested by our cloud QA browser agent. Your job: produce a **skill** — a per-property knowledge base — so that when the QA agent runs workflows against this app, it has the institutional knowledge it can't infer from the DOM alone.

You are the primary author of this skill. You have everything you need: the source, your existing memory/notes about the app, and your judgment about what actually matters for end-to-end testing. This guide tells you *what to produce*, not how to investigate — you already know how to do that.

## The critical constraint

**The skill is consumed by a runtime browser agent that has only the DOM, `page.*` actions, and `page.request`. It has no source code, no repo, no file system, no access to anything you can read.**

Every item you write must be useful in that world. Your repo is the *source* you read; the skill content is the *translation* into runtime-actionable knowledge.

✅ "Org switching is reflected in the URL: paths under `/o/<slug>/…` are tenant-scoped, and navigating directly to a path with a different `<slug>` is the canonical way to switch."
❌ "See the org middleware in the auth package for how tenant context is set."

✅ "When a feature is gated off, the most common signal is the action button being absent from the DOM (not disabled). Less commonly, the route renders a 'requires upgrade' empty state, or a mutation returns a typed error like `FEATURE_DISABLED`."
❌ "The feature-flag service exposes a `useFlag()` hook checked by the `<Gate>` component."

**The translation check, for every item:** *if all I had was a browser, would this sentence help me?* If it names a file, internal class, hook, middleware, package, or private symbol — rewrite as observable behavior or drop it.

This applies to *examples too*. Don't write "the `/api/v2/widgets` endpoint" if it's just a plausible-sounding example you invented — the QA agent will look for that exact path and be confused if it doesn't exist. Either it's real and stable, or omit it.

## Prerequisite: the property must be linked to this repo

The skill is attached to a Canary **property** — the abstraction for "the app under test." Before you start authoring, confirm the property you're targeting is linked to the repository you're working in. If it isn't, link it first or ask the user which property to target:

```bash
canary property list                                              # find the property
canary property repo link --property <id> --repo <owner/name>     # link if needed
```

Once linked, every write you do via `canary property skill set/import` lands on the right property.

## Sections

Each section captures a *model* the QA agent can reason from. Sections cover what generalizes across web apps; specific instances (every flag name, every URL) go stale within weeks and aren't worth enumerating. Aim for the runtime agent to be able to *speculate* a correct diagnosis from your model, even for situations you didn't explicitly cover.

For each section, ask: "what does the runtime agent need to understand to make pass/fail judgments correctly?"

**Volume.** Aim for 3–6 items per section where the app has something to say. If a section legitimately doesn't apply (e.g. a single-locale app's `localization`), still ship one short item that says so — a brief "N/A and why" is more useful than an absent section.

### `general` — Overview

What the app is, who uses it, what it's for. One or two short items — enough that the QA agent has a frame for everything else.

### `product` — Domain Model

The entities the user works with and how they relate. Lifecycle states at a high level. Not a schema dump — the relationships and concepts a tester needs to make sense of what they're looking at.

✅ "Projects contain Tasks. Tasks belong to one Project; reassignment moves them between Projects. Completed Tasks are kept (not deleted) and visible via a 'Completed' filter."

### `technology` — Tech Stack

Framework family, component library family, API shape — only the parts that inform locator strategy or interaction patterns. The runtime agent uses this to pick the right way to interact (native form submit vs client-side router, virtualized lists, etc.).

✅ "Single-page app rendered client-side; routes update the URL without full page loads. Wait for hydration completion before interacting on first load."

### `navigation` — Navigation Model

**How to get places**, not a list of every route. The runtime agent should be able to navigate to a concept (a settings page, a list of an entity type, a detail view) without you having pre-enumerated every URL.

Cover: URL grammar (path-segment patterns, ID prefixes, query params with semantic effect), public-vs-authed routing, modal-as-route, how redirects behave, what an unknown route does (404 vs redirect home), how the back button behaves.

✅ "URLs follow `/{entity-type-plural}/{id}` for list and detail. IDs are prefixed (e.g. `xyz_…`), which makes them recognizable in URLs. Deep-linking to a detail page works without going through the list — the page fetches its own data."

### `tenancy` — Tenancy & Workspaces

How the multi-tenant world works, if it does. Whether tenant context lives in the URL, a cookie, or a header. How to switch. What cross-tenant references look like (or whether they're forbidden). What the agent sees if it lands on a resource belonging to a different tenant than its current session.

If the app is single-tenant, say so — that's the most useful answer.

### `auth` — Authentication & Sessions

Login as a flow: what mechanisms exist (password, magic link, OAuth, SSO), where the login page lives, what success looks like (redirect to where?), what session expiry looks like (silent refresh vs bounce to login), how logout behaves, whether there's MFA. The agent should be able to recover when it gets unexpectedly logged out.

### `roles_permissions` — Roles & Permissions

The roles a user can have, in their *displayed* form (the casing and wording the runtime agent will actually see in the UI). What each role can and can't do, in terms of observable UI affordances (buttons present/absent, routes accessible vs forbidden). What "access denied" looks like — a full-page error, a redirect, a toast, an HTTP response code.

The runtime agent needs to distinguish "this button is missing because I'm the wrong role" (correct gating, not a bug) from "this button should be here but isn't" (real bug).

### `feature_gating` — Feature Gating

How features are turned on or off, *as a mechanism* — not a list of every gate. The runtime agent should be able to recognize a gated-off feature when it sees one, and not confuse it with a bug.

Cover: how gating typically manifests (button absent, route 404, gated empty state, typed error from a mutation, etc.), whether gating is per-tenant / per-user / per-environment / experiment-bucketed, whether there's an admin surface that reveals current gate state, and any specific error codes a mutation might return when it's gate-blocked.

✅ "When a feature is gated off for a tenant, the most common UI signal is the action button being absent (not disabled, not hidden — fully removed from the DOM). Less common: the destination route renders a 'requires upgrade' empty state with a CTA to billing. Mutation endpoints reject gated calls with `403` and an error body like `{ code: 'FEATURE_DISABLED', feature: '<slug>' }`."

Do **not** ship a list of every flag in the system. Flag inventories go stale within weeks. The runtime agent doesn't need to know which flags exist; it needs to recognize the signal when it sees one.

### `environments` — Environments

How environments differ in ways the agent can observe. Different base URLs (and naming conventions for them — prod, staging, sandbox, etc.). Which third-party integrations are stubbed vs real in non-prod (the agent should not expect a real charge to fire in sandbox). Any env-only UI surfaces (debug panels, sandbox banners, "you are in test mode" indicators).

### `data_lifecycle` — Entity Lifecycle

For meaningful entities (the ones a tester would interact with): the states they can be in, how the user moves them between states, what's reversible vs not, what's soft-deleted vs hard-deleted. Whether creation kicks off async processing and how the user sees "processing" vs "ready." Typical timing windows for that processing, so the agent doesn't time out.

### `async_realtime` — Async & Realtime

Which surfaces update in real time vs require a refresh. The wait signal the agent should use (a polling cadence, a websocket/SSE message, a status pill changing) — *something concrete* to watch for. Optimistic UI patterns and what their failure-rollback looks like. Typical end-to-end latency for common flows.

### `localization` — Localization

If the app is single-locale, say so. Otherwise: how locale is determined, how it's switched, what changes between locales (currency formatting, decimal separators, date formats, RTL), and which surfaces are user-locale vs server-locale (e.g. timestamps).

### `ui` — UI Patterns

Inventory of UI primitives the agent will encounter, by *behavior* not implementation. Custom combobox vs native select. Modal vs drawer vs popover. Date picker behavior. Table interactions (clickable rows? action buttons in last column? virtualized?). Whether destructive actions use native `confirm()` or custom dialogs. Keyboard shortcuts the agent might trigger accidentally. Empty-state vs loading-state appearance.

For each pattern: what does the agent click, what happens, how does it dismiss.

### `feedback_errors` — Feedback & Error Signals

**The highest-leverage section.** This is what the QA agent uses to make pass/fail judgments.

Describe the *signal model* the runtime agent will use to detect outcomes:

- **Success signal per surface.** For each common operation (create, update, delete, submit a form), what's the canonical "this worked" signal? A toast? A route change? A status pill flipping? The modal closing? Be specific about *which* signal is canonical so the agent doesn't watch for the wrong one.
- **Error surfaces.** Where do errors appear? Toast, banner, inline field, page-level, modal, native `alert()`? When does each surface fire (form validation vs server rejection vs network failure)?
- **Error response shape.** When the API rejects a request, what does the agent see in the network response — a typed error code, a plain string, an HTTP status code? Knowing the shape lets the agent classify failures even when it didn't anticipate them.
- **Validation timing.** On-change, on-blur, on-submit, async/server? Different forms may differ; if so, describe the pattern.
- **Loading indicators.** What does "in progress" look like — spinner, skeleton, disabled button, progress bar? What's the agent supposed to wait *on*?
- **Destructive action confirmation.** Native `confirm()` or a custom dialog? The agent handles each differently.

The goal: the runtime agent should be able to look at any unexpected state and *speculate* — "no toast appeared and there's red inline text → the form rejected on validation" — without you having pre-enumerated every error case.

### `idiosyncrasies` — Idiosyncrasies

Narrow scope: known runtime-observable gotchas. First-login interrupts, banners that appear conditionally, intentional delays > 500ms that look like hangs, retry/backoff windows, redirects that surprise the agent, flows that look broken but aren't.

Each item should answer: "what will I see, when, and how should I respond?" Reject vague "be careful" entries.

## Procedure

1. **Confirm the target.**
   ```bash
   canary property list
   canary property skill list --property <id>     # see what's already authored, if anything
   ```
   If the property isn't linked to this repo (or to no repo at all), stop and link it first (see prerequisite above).

2. **Capture provenance.** Grab the current commit SHA so the items carry "authored from `<repo>@<sha>`":
   ```bash
   SHA=$(git rev-parse HEAD)
   SOURCE_ID="$(gh repo view --json nameWithOwner -q .nameWithOwner)@$SHA"
   ```

3. **Author in your editor, not in shell.** Assemble a `bundle.json`:
   ```json
   {
     "items": [
       { "section": "navigation", "itemKey": "url_grammar", "itemLabel": "URL grammar",
         "content": "URLs follow /<entity-type>/<id> for list and detail…" },
       …
     ]
   }
   ```
   Keep item content as plain prose — it ships verbatim into the runtime agent's system prompt.

4. **Diff before write.** Compare against what's already there so you don't churn identical content:
   ```bash
   canary property skill list --property <id> --format json
   ```

5. **Import.** One call writes the whole bundle:
   ```bash
   canary property skill import --property <id> --file bundle.json --source-id "$SOURCE_ID"
   ```
   Append-only versioning makes re-runs safe.

6. **Verify.** Skim the result and re-apply the translation check on three random items:
   ```bash
   canary property skill list --property <id>
   canary property skill export --property <id> --format md
   ```

## Anti-patterns

- **Citing implementation.** No file paths, internal symbols, hook/component/middleware names, table names, or design-system component conventions (e.g. "DataTable", "Sheet", "PageHeader"). The runtime agent sees DOM, not your library. Library *families* are OK in `technology` ("React with a react-aria-based UI kit") because they inform interaction patterns; named internal components in any other section are not. If you're tempted to write "see X," ask what X *does* and write that.
- **Enumeration that goes stale.** Every flag. Every URL. Every error code. The runtime agent doesn't need the inventory; it needs the model.
- **Speculation.** If you can't observe it and the code doesn't show it, don't write it. Empty is better than wrong.
- **Vague gotchas.** "Be careful with X" without a concrete cue is useless. Either describe what the agent sees and when, or drop it.
- **One mega-item.** Each item is one focused topic with one stable key. Six small items beat one sprawling one.

## What good looks like

A reviewer reading your output should be able to predict, for a specific QA scenario, exactly which observable signals the runtime agent will use to make pass/fail judgments — and what the agent should do when something looks off — without ever opening the repo. If a section reads like a README, it failed. If it reads like a field guide for someone using the app blind, it's right.
