# How the app appears in COHO

Atrium app developer spec, section 6: How the app appears in COHO.

**Default: web app with a quick view.** If a manager must click, fill a form, approve something, pay, or view a report inside COHO, set `quickViewPath` (and usually `openPresentation`). Native Store config is only for a handful of scalar prefs. Background-only agents are a rare exception (roughly under 5% of apps).

Decision rule:

| Manager needs to… | Surface |
|---|---|
| Open a screen, run a workflow, see a report, collect payments, review applicants, or do one-off actions | **Quick view** (`quickViewPath`) and/or **external URL** |
| Flip a few prefs (send time, dry-run, subject line) and leave the app running | Optional **light config** (§5.3) *in addition to* the above when needed |
| Never open the app — pure background automation only | **Agent** (omit `quickViewPath`) — justify this explicitly |

## Quick view (iframe) — default

Set `config.quickViewPath`. Self-hosted apps resolve it against the registered
base URL. Hosted apps with browser UI set `config.browserBaseUrl`, and COHO
resolves quick view and external config paths against that public HTTPS origin
while runtime calls stay private. Absolute URLs are allowed only on the selected
browser origin. COHO loads the page in an iframe after minting a short-lived
signed launch URL (`GET`, empty body) with:

| Query | Role |
|---|---|
| `connectionReference` | Which org connection opened |
| `atriumUsageToken` | Short-lived JWT: signed-in **person + organisation + connection** (≈15 minutes). Use as `Authorization: Bearer` for interactive Public API calls. Remint while open via the postMessage bridge — do not treat expiry as “close the app”. |
| `theme` | Opening manager's COHO UI theme: `light` or `dark` (from persisted `User.DarkMode`, not OS preference). Covered by `atriumSignature` — use for first paint. |
| `atriumSignature` | HMAC over the launch GET (same scheme as other signed GETs) |

Verify `atriumSignature` the same way as other signed GETs. Read person/org from the usage JWT claims (`personReference`, `organisationReference`, `connectionReference`) after verifying it with COHO — do not trust unsigned query params for identity. `theme` is a UI hint only (safe after signature verify); it is not an auth claim.

You must:

- Send `Content-Security-Policy: frame-ancestors …` using **`ATRIUM_FRAME_ANCESTORS`** when set (do not use a bare `*`). On Atrium Hosting, COHO injects **one** Store origin for that environment into the container — treat it as the source of truth. The same Docker image works everywhere because each env gets its own value; a domain move is a Hosting redeploy + re-provision, not an app rebuild.
- Use **`ATRIUM_PARENT_ORIGIN`** as the exact target for `atrium.quickView.close` postMessage (Hosting sets it to the same origin as `ATRIUM_FRAME_ANCESTORS`).
- Treat the page as **third-party chrome** — no requirement to match COHO CSS.
- Do not render the app's name as a page title. COHO's header above the iframe already shows it from the Store listing, so an `h1` with the name shows it twice. Start the page with its first panel.
- Never put the API key or signing secret in the query string. The launch signature is not a substitute for checking method, path, and connection.
- For interactive UI calls, prefer `Authorization: Bearer <atriumUsageToken>`. Effective scopes are **connection grant ∩ opening person's role**. Keep the connection API key for background/webhooks only.
- Keep the Bearer fresh for as long as the quick view stays open: listen for COHO remints and update your client. You cannot mint a usage token yourself.

### Self-host Store origins

`lifecycle.setup` does **not** carry iframe parents (connect is the wrong place to pin CSP — a domain move should not require reconnect). Hosting injects `PORT`, `ATRIUM_DATA_DIR`, `ATRIUM_SETUP_SECRET`, `ATRIUM_FRAME_ANCESTORS`, and `ATRIUM_PARENT_ORIGIN` into the container at provision time.

When **self-hosting**, you set both env vars yourself. They are always the **same** value: the Store / manager SPA origin for the COHO environment you are embedded from. Do **not** hardcode a multi-environment list into the image, and do not use `*`.

Use **one image, different process env per deployment** (same pattern as Hosting). Playground and production are not different apps or codebases — flip the two vars (and the usual secrets / API base) when you cut over.

| COHO environment | Set both vars to |
|---|---|
| **Playground** | `https://playground.coho.life` |
| **Production** | `https://coho.life` |

Example for a Playground self-host deploy:

```bash
export ATRIUM_FRAME_ANCESTORS=https://playground.coho.life
export ATRIUM_PARENT_ORIGIN=https://playground.coho.life
```

These match what Atrium Hosting injects for that environment today. If COHO moves a Store host, Hosting apps are re-provisioned automatically; self-hosted apps update the two env vars and restart — no reconnect required.

## Runtime secrets (Hosting)

Third-party API keys and other process env that must not live in git belong in **Hosting runtime secrets**, not in the image or a committed `.env`.

- **Set:** My apps / partner portal (Hosting runtime secrets editor), organisation Store manage panel, Public API `PUT /v1.1/public/atrium/apps/{appReference}/runtime-env`, or MCP `PutAtriumRuntimeEnv` / `PutPublisherAtriumRuntimeEnv`.
- **Delete one key:** Public API `DELETE /v1.1/public/atrium/apps/{appReference}/runtime-env/{keyName}`, MCP `DeleteAtriumRuntimeEnvKey` / `DeletePublisherAtriumRuntimeEnvKey`, or the Delete control next to each key in the editor.
- **Shape:** full-replace map of `NAME=value` keys (`^[A-Z][A-Z0-9_]{0,63}$`, max 32 keys). Empty body clears all keys.
- **Read:** list endpoints return **names only** — values are write-only — plus `missingRequiredKeys` when the manifest declares required entries that are unset.
- **Inject:** on every provision, each key is injected as container env from the per-app Secrets Manager document (alongside `SetupSecret`). Saving or deleting on a Ready hosted app re-provisions so the running task picks up the map.
- **Manifest (optional):** declare expected keys in `runtimeEnv: [{ "name": "TAWK_API_KEY", "secret": true, "description": "…", "required": true }]`. Required keys must be set before a Ready hosted app can re-provision (Update / regenerate setup secret / runtime-env save).
- **Reserved:** `PORT`, `ATRIUM_*`, `AWS_*`, `ECS_*`, and a short list of OS names cannot be set.
- **Self-host:** set the same names on your process; COHO does not inject them.

Thin `postMessage` bridge (parent is COHO):

| Message `type` | Direction | Role |
|---|---|---|
| `atrium.quickView.close` | app → COHO | Close the shell |
| `atrium.quickView.ready` | app → COHO | Optional. Ask the host to (re)send theme after your page boots. |
| `atrium.quickView.theme` | COHO → app | `{ theme: "light" \| "dark" }`. Sent on iframe load, on `ready`, and when the manager toggles dark mode while the shell is open. |
| `atrium.usageToken.refresh` | app → COHO | Ask COHO to remint a usage JWT (optional `requestId`) |
| `atrium.usageToken` | COHO → app | Fresh `{ atriumUsageToken, expiresAt }` (optional `requestId`) — keep iframe state; do not reload |
| `atrium.usageToken.error` | COHO → app | Remint failed; tell the manager to close and reopen |

Apps may ignore theme messages. Matching COHO CSS is not required — this is optional host context for third-party chrome. Check `event.origin` against your allowed COHO parent origins before applying theme. Prefer the signed `theme` query for first paint, then listen for live updates.

COHO also remints the usage JWT proactively about every 12 minutes and when the manager’s tab becomes visible again. The Node kit helpers `subscribeAtriumQuickViewTheme` and `createUsageTokenSession` wire theme and token refresh. Resize, open-full-app, and toast remain reserved on the same bridge; do not invent parallel channels.

## External URL

`externalConfigureUrl` opens in a **new tab** through the same short-lived signed
launch flow as quick view (including `atriumUsageToken`). Verify the `GET`
signature before showing connection-specific UI. Use this for an existing SaaS
dashboard, or alongside quick view — not as a substitute for skipping UI when
managers still need to work in the product.

## Light config in the Store

Optional JSON Schema form, proxied to you (§5.3). Flat scalars only. Never use
this as the main product surface.

## Agent (no UI) — rare exception

Omit `quickViewPath` only when the product is truly background: webhooks + Public
API + optional light prefs, and managers never need to open a screen. Birthday-style
message senders fit; payments, reports, applicant review, multi-step setup, and
one-off actions do **not**. The Store will not offer **Open quick view**. If you
omit `quickViewPath`, write down why the product has no interactive manager surface
before you list.

Reference sample (exception only): `CompanyForLife/atrium-app-samples` → `samples/hello-agent-node`.
Do **not** start from it unless you have confirmed the product has no manager UI.
The four language hellos (`hello-node`, `hello-python`, `hello-go`, `hello-dotnet`)
are the standard web-app starters.

## Your Store card

Register and edit your listing in the Store (Build an app / Edit listing): icon, screenshot, and copy live on the app row you own. A local `store/` folder (hello-node ships an example) is only a convenience if an AI or teammate prepares files for upload; it is not required.

The Store shows each app as a card. The card picture is the app's **first screenshot**, with the app name over a fade at the bottom. An app with no screenshot gets a coloured card based on its name, so it still looks tidy, but a screenshot shows people what they are getting.

- Make the first screenshot the screen that best shows what the app does. 1440 x 900 works well.
- Keep the key content in the top two thirds. The bottom third fades to a dark colour behind the app name.
- Show the app itself, not a sign-in, error or empty screen.
- The card shows the first two lines of the short description, so lead with the benefit.
- The icon is a square PNG, at least 512 x 512, and the picture fills the whole canvas. Do not add rounded corners, a border, a shadow or padding, and do not put a rounded icon on a coloured square: the corners would show. Transparent corners show the card colour behind. COHO rounds the corners itself.
- **Always supply an icon** when registering or editing a listing, unless one is already on the app. With no icon, the Store shows the app's initials on a coloured tile (e.g. "AL", "CB"). That fallback is temporary scaffolding only — create and upload a real icon before you invite anyone to try the app in COHO (Default builder workflow step 4; design kit §20.1).
- The Build an app form warns when an icon has obvious corners.
- Before a **public** listing can be approved, fill company-level disclosures once on the publisher profile (country, support site and hours, privacy policy URL, sub-processors URL, licence — Atrium standard or your own terms URL), plus app-level fields on Edit listing: data location (COHO-built apps default to AWS London when blank), a plain-English reason for each requested permission, name/icon/description/screenshots. Design kit §20.3 lists the copy limits; the published [listing guidelines](https://coho.life/legal/atrium-app-review-listing-guidelines/) are the product rules.

## Pricing and payments

Set price on **Build an app** / **Edit listing** (not in listing marketing copy). The Store shows the catalogue price on the card and detail page.

| Choice | What the customer sees | Notes |
|---|---|---|
| **Free** | Free | No app charge. The installing organisation still uses its shared API/MCP credit allowance for calls your app makes. |
| **Paid — flat monthly** | Your price per month (+ VAT where shown) | One amount for the organisation. |
| **Paid — per unit / month** | Your price per active unit per month (+ VAT where shown) | Units are stock rooms and properties that are not disabled. Counted when the invoice is raised. |

**Who bills.** COHO is the merchant of record. Customers pay COHO by **card**. Organisations with no card, or with BACS as the only default method, cannot install a paid app until a card is on file. Free apps do not need a card for the app itself.

**When a paid install is charged (UTC calendar months).** Connecting on days **1–14** of the month can raise an install charge for the current month. Connecting on day **15** or later: the rest of that month is free for that connection, and the first charge is on the next 1st. Charges are whole months — no day-level proration. Disconnect ends billing after the month already paid; reconnect in a month already paid for that product does not charge again.

**Your share.** On paid catalogue sales, your developer share of Net Receipts is the percentage set out in your Commercial Schedule. You agree a Commercial Schedule with COHO before you publish your first app. The split is internal bookkeeping on COHO's side (it is not a separate line on the customer's invoice). Remittance timing and tax treatment follow the Atrium Distribution & Billing Addendum.

**Price changes.** New installs pay the new price immediately. For organisations already connected, a price change takes effect on the first 1st that is at least **30 days** after you change it.

**Not at launch.** One-off payments and in-app purchases are not offered yet. Optional free trials exist as a product setting and are **off by default**.

**Private apps (not listed).** A **COHO-hosted** private app carries a **£10 / app / month** hosting seat (card required). Self-hosted private apps have no hosting seat fee; their API/MCP use still draws from the organisation's credits.

**Credits vs app price.** Every connected app (free, paid, private, COHO-built) consumes the installing organisation's shared Public API / MCP credit pool. App subscription price and usage credits are separate lines.
