---
name: ai-driven-apps
user-invocable: true
description: >-
  How the AI builds, shows, drives, and tests an Adom app. In Adom the AI writes the app, shows it to
  the user (in their own already-signed-in native browser via Adom Desktop, in pup for the
  logged-out experience, or in a Hydrogen webview), drives the front-end over a live SSE or WebSocket
  channel to exercise every path (auto-click buttons, show and hide floaty toolbars, load a
  different symbol or footprint, click send-to-kicad), reads back state, console, and screenshots,
  and ralph-loops until the UI is right. Covers state-on-the-server, GET /state, GET /console, the
  live channel, the CLI driving commands, the three display surfaces and when to use each, and the
  ralph-test loop. Read BEFORE building or testing any Adom web app. Trigger words: ai-drivable, ai
  driven app, drive the front-end, drive the ui, live channel, SSE, server-sent events, websocket,
  GET /state, GET /console, ralph test, ralph loop, auto-click buttons, test the ui, show app in
  native browser, show app in pup, webview vs pup, native browser auth, logged out testing, ui click.
---

# AI-driven apps

This is the loop the whole Adom UI system is designed around. The AI is both the builder and the
tester; the user watches and steers. An app is only as good as the AI's ability to drive and observe
it, so build for that from the first commit.

## The loop

1. **The AI builds the app:** a small server plus a webview UI, made drivable from day one (state on
   the server, a live channel, a CLI).
2. **The AI shows it to the user** in one of three surfaces (below), chosen for what is being tested.
3. **The AI drives the front-end** over the live channel to exercise every path: click each button,
   open and close the floating toolbar, load a different symbol or footprint, hit send-to-kicad, and
   read back state, the console buffer, and screenshots.
4. **The AI ralph-loops:** screenshot, analyze what looks wrong, fix the code, reload, repeat, until
   the UI is correct and a pleasure to use.
5. **The AI narrates** through toasts and chat so the user can follow along.

## State on the server, observed two ways

- **The server owns the truth.** The UI never keeps its own copy of "am I recording?" or "which
  symbol is loaded?". Buttons fire commands to the server; the server holds state.
- **`GET /state`** returns the current state as JSON. The UI reflects it; the AI reads it to check
  progress without scraping HTML.
- **`GET /console`** exposes the app's log: override `console.log/warn/error` to also post to the
  server, keep a ring buffer, and catch `window.onerror` and `unhandledrejection`. Now the AI reads
  what the UI is doing without DevTools.

## The live channel

- Expose **Server-Sent Events** for one-way push, or a **WebSocket** for two-way. The UI subscribes
  and updates the instant anything changes; the CLI subscribes too. A 300 to 500ms poll of
  `GET /state` is the minimum fallback, but the live channel is what makes driving feel instant.
- **Because the channel is two-way, the AI drives the front-end from the CLI.** Every user-visible
  action has a command that does exactly what the click does:
  ```
  mytool ui click "send-to-kicad"      # press a button
  mytool ui toolbar show               # reveal the floating toolbar, then `hide`
  mytool ui load-symbol LM358          # swap the loaded symbol or footprint
  mytool ui toast "file downloaded"    # raise a toast (also the AI's narration channel)
  ```
  Each command drives the real UI through the same path a user click would, so the AI can walk every
  interaction, assert the resulting state, screenshot it, and move on. This is the test harness and
  the demo driver in one.
- **Acceptance test:** an AI driving the app over the channel and a person clicking the UI produce
  identical visible results.

## The three display surfaces

Pick the surface by what you need to test, and use more than one at once when it helps.

| Surface | What it is | Use it for |
|---|---|---|
| **Native browser** (**nb**) | The user's own Chrome or Edge, driven through Adom Desktop | The real, signed-in experience. The browser is already the user's, so the app opens **logged in with zero friction**. Use it for anything behind a login. |
| **pup** | A clean, scriptable Chrome the AI fully owns | The **fresh, logged-out, first-time-user** experience, and automated screenshot loops that should not fight the user's own tabs. |
| **Hydrogen webview** | An in-workspace browser tab | Showing a result inside the workbench, next to the code and the chat. |
| **hdbw** (HD Browser Window) | Under Hydrogen Desktop: a standalone Tauri browser window HD manages directly (window label `hd-browse-*`, destination id `webview-window`) | A dedicated app window beside the HD shell — bigger than a pane tab, still HD-managed. Note: the in-HD *pane tab* is the wv surface (the `webview` skill routes there inside HD). |


### VS Code's "Simple Browser" is NOT a surface

**NEVER show an Adom app in VS Code's built-in "Simple Browser".** It is none of the Adom
surfaces: no live channel, no console read-back, no AI drivability, wrong pane semantics.
If you are about to paste an app URL into Simple Browser, stop and load the `webview` skill
instead.

### Every app's SKILL.md must carry a display section (MANDATE)

When you write or update an app's SKILL.md, include a section titled
**"Displaying this app (REQUIRED)"** that (a) names the app's own show/open verb if it has
one, (b) instructs loading the `webview` skill for manual display (pup / native browser / hdbw per
the table above), and (c) states the Simple Browser ban verbatim. Skill discovery from
trigger words alone is probabilistic; the app skill IS reliably loaded when the app is
named, so it must carry the display rules itself. (Decision John 2026-07-17, after a fresh
workspace showed adom-parts-search in Simple Browser.)

The native-browser and pup split is the point: open the **logged-in path in the native browser** and
the **logged-out path in pup** at the same time, and verify both. Tell the user which surface and
why.

## Ralph-testing the UI

The visual feedback loop that turns "it runs" into "it is right": edit, reload, drive a step over
the channel, screenshot, read the console buffer, analyze, fix, repeat. Drive it in pup (or the
native browser), capture each frame to the screenshot log, and loop until the UI looks and behaves
correctly. This is how the AI debugs UI with no human in the loop, and it is the proof the app is
genuinely drivable.

## Reaching a container app from outside

- A webview and a browser window both render **outside** the container, so they cannot reach
  `http://localhost:<port>` or `http://127.0.0.1:<port>`. Always use the public proxy URL:
  `https://<slug>.adom.cloud/proxy/<port>/`.
- **Inside a webview, always use relative URLs.** A leading slash bypasses the proxy and hits the
  wrong origin. Every `fetch`, `href`, `src`, and CSS `url()` must be relative.

## Boundary with app-creator (do not blur it)

`adom/app-creator` owns CREATING the app: scaffolding, project layout, server + webview skeleton,
wiring the first commit. THIS skill owns everything after: SHOWING the app, DRIVING its front-end,
and RALPH-TESTING the UI until it is right. If you are adding "how to build/scaffold" content, it
goes in app-creator; if you are adding "how to drive/verify/test the UI", it goes here. Keeping the
line crisp is what stops the two skills drifting into contradictory duplicates.

## Related skills

`app-creator` (scaffolding the app), `adom-screenshot` (the ralph loop and shotlog),
`adom-cli-design` and `ai-hints-in-clis` (the CLI the driving commands live in), `toasts` (the
narration channel), `app-shell` (the layout being driven), and `adom-ui-design` (the hub).
