---
name: app-creator
description: >
  Use when the user wants to build a new Adom "app" — a mini web server that
  renders its UI in a Hydrogen webview tab. Covers architecture (server in
  any language: Rust+tiny_http, Node, Python, Go), required conventions
  (Adom brand, custom SVG favicon, proxy URL, webview safety), and publish
  flow (GitHub repo + wiki app page for discovery and install). Trigger
  words: make an app, create an app, build an app, new app, adom app, build
  an adom app, mini web app, mini web server, webserver app, build a
  webview app, webview app, hydrogen app, build a widget, create mini app,
  tiny app, tool with a UI, cli with a UI, app needs an icon, favicon for
  an app, app branding, app icon, app standards, how to make an adom app,
  app conventions, app template, webview-hosted app, first-class app, app
  page on wiki.
---

# Adom App Creator

> ## 🛑 STEP 0 — MANDATORY before writing one line of UI code
>
> Before authoring any HTML, CSS, JS, SVG, or webview content:
>
> 1. **Read [`gallia/skills/brand/SKILL.md`](../brand/SKILL.md) end-to-end.**
>    Palette tokens (`#0d1117` page bg, `#161b22` panels, `#00b8b1` accent),
>    Familjen Grotesk + Satoshi + JetBrains Mono fonts, **monochrome icons
>    only** (`#e6edf3` or `currentColor` — *never* emoji, *never* coloured
>    favicons, *never* coloured tab-strip icons), 0.15 s transitions, frosted-
>    glass card pattern, dark theme always.
> 2. **Read [`gallia/skills/human-ui-patterns/SKILL.md`](../human-ui-patterns/SKILL.md) end-to-end.**
>    Tooltips (rule 1d — body-appended `position:fixed` div, z 99999, NEVER
>    `title=""` or CSS `::after`), HUDs (draggable + collapsible + dismissible),
>    NEVER ALL CAPS, click previews, hero images, provenance captions,
>    AI-drivability, the full pre-publish checklist.
>
> Both skills are <500 lines each. Reading them takes ~30 s and prevents the
> #1 user complaint on every UI build: *"have you even bothered to look at
> the brand guide skill and ui design skill?"* (verbatim, 2026-04-26 on the
> aci library dashboard).
>
> **Rule of thumb:** if you've just typed `<div>` or `:root {` without having
> the open contents of those two files in your context window in the last
> two minutes, stop and Read them. The rules are easy to half-remember and
> half-violate at the same time — emoji icons, the wrong shade of background
> grey, a teal favicon, `title=""` tooltips. Every one of those has shipped
> from a Claude Code session that "knew the rules" but didn't re-read.
>
> **No exceptions for "quick prototypes" or "internal tools."** This skill,
> aci library, every chip-fit HUD, every wiki page hero — they all started as
> "I'll just throw something together and polish later." The polish phase
> has never come; the violations ship.

Adom "apps" are mini web servers that render their UI inside a Hydrogen
**webview panel tab** (not the Adom Viewer). They look and feel like native
Hydrogen panels to the user, but under the hood they're just HTTP servers
the AI drives. Any future Adom user can discover them via wiki triggers and
install them via the standard app install prompt.

This skill covers:
1. What counts as an "app" (vs a CLI-only tool or an AV widget)
2. The required conventions every app must follow (brand, favicon, proxy URL)
3. The mini web server pattern (any language / any HTTP library)
4. How to ship: GitHub repo, wiki app page, install prompt
5. Where apps fit in the broader skill ecosystem

## What is an Adom app?

An Adom app is:

- A **mini web server** that runs on the Adom Docker container (or the user's
  own machine, for desktop integrations). The server listens on a TCP port.
- Its UI is **HTML + CSS + JavaScript** served by that server, rendered in a
  **Hydrogen webview panel tab**. Not the Adom Viewer. Not a Tauri window.
  Not a browser tab the user opens manually, a first-class Hydrogen panel.
- The server can be a **single page** or a **multi-page site**, the app's
  author decides based on what fits the feature.
- The implementation language is the AI's choice. Rust with embedded
  `tiny_http` works great for tools that also expose a CLI (e.g.,
  video-post). Node/Python/Go/anything else is fine if it better matches
  the workflow.
- The app is **discoverable via wiki triggers**. A user says something like
  "speed up my demo recording" and Claude Code suggests the matching app
  from the wiki, with a paste-into-Claude install prompt.

An app is NOT:

- A CLI-only tool (those belong in `adom-cli-design` / `tool-publisher`
  without the web UI)
- An Adom Viewer widget (those use `av-creator` and push HTML to the AV
  server, which is a different render surface)
- A Hydrogen built-in panel like 3D Editor or Schematic Editor (those are
  Tauri native + ship with Hydrogen itself)

**Read `adom-app-model` first** for the bigger picture (repo layout, wiki
publishing, Tier A vs Tier B distribution, service containers, version
discipline). This skill only covers the client-side UI conventions; the
model skill covers "where does this app live and how does it ship?"

**Also read `adom-cli-design`.** Every app ships a CLI surface (at minimum
`<app> install`, `<app> serve`, and whatever subcommands the AI drives the
server with). That CLI must follow the Adom CLI conventions — Rust +
`clap`, `OK:` / `ERROR:` / `Hint:` output lines, isatty-aware colors, the
skill-file pattern. Apps with a half-finished CLI surface annoy everyone
downstream; don't skip this read.

## Does your app need a service container?

If the app has a backend that holds API keys, caches third-party responses,
or otherwise wants to be shared across users, it needs a **private service
container** on the `default-light` image. See `standalone-service` skill for
the full setup, but the short version:

- Add `service/{deploy.sh, watchdog.sh, service.json}` to your repo root
- Deploy once with `adom-cli carbon containers create --image-id <default-light> --repo-id <REPO_ID> --ssh`
  followed by `ssh ... bash service/deploy.sh` (the CLI requires `--repo-id`)
- The watchdog cron auto-pulls `origin/main` every 2 min and rebuilds +
  restarts — you don't redeploy after releases, just `git push`
- The user's container points `<APP>_API` at the service container's public URL
  for shared-cache benefits

If the app is a pure client (no backend state worth sharing), skip the
`service/` directory and let it run locally per-user.

## Each app ships a BUILD-SKILL.md

Every app's release process is the same shape (bump VERSION → cargo build →
git tag → gh release → `adom-wiki asset upload` → `adom-wiki page update`
→ verify). Bundle this as `<repo>/BUILD-SKILL.md` and deploy it from the
binary's `install` subcommand to `~/.claude/skills/<slug>-build/SKILL.md`.
See `adom-app-model` for the template. Trigger words like "release X",
"bump X", "publish X" route Claude to it.

## Required conventions

Every Adom app MUST follow these rules. They exist so apps feel like
first-class Hydrogen panels, not random HTML pages someone served on a port.

### 1. Use the Adom brand guide

Source: `~/project/gallia/skills/brand/SKILL.md`, read it first.

Minimum requirements for every app's UI:

- **Fonts:** Familjen Grotesk (headlines) + Satoshi (body) + JetBrains Mono
  (code). Load from `https://adom.inc/fonts/` via `@import url(...)` in
  your CSS. **Never** fall back to Inter, Arial, Helvetica, or system fonts
  as primary.
- **Color palette:** full `:root` CSS token block at [`tab-wiring-reference.md`](tab-wiring-reference.md) § Brand CSS tokens. Use `--accent` (#00b8b0 teal) for primary actions, `--green` for success, `--red` for destructive.
- **Dark theme by default.** The Hydrogen workspace is dark, so any white
  panel looks jarring. Start from `--bg: #0d1117` and only go lighter when
  you need contrast.
- **Adom teal accent** (`#00b8b0`) for primary actions, focus rings, and
  highlights. Green (`#3fb950`) only for success states, red (`#f85149`)
  only for destructive actions.
- **8px / 12px / 16px spacing grid.** No random 13px margins.
- **Border radius `8px`** for cards and buttons, `12px` for larger
  containers. No sharp corners unless you have a specific reason.

See the video-post voiceover UI (`src/voiceover/ui.html` in the
`adom-inc/video-post` repo) for a working example that follows every
rule above.

### 1b. 🛑 EVERY icon is monochrome white — no exceptions, no favicon carve-out

Per `brand/SKILL.md`: **every icon in an Adom app is monochrome white
(`#e6edf3`) or `currentColor` inheriting from a white-text context**.
This includes:

1. **In-app icons** — header logo, button glyphs, tooltip icons,
   sidebar icons, empty-state art.
2. **The Hydrogen tab-strip icon** — `docs/icon.svg`, both as the HTML
   `<link rel="icon">` favicon AND as the base64-encoded data URL
   passed to `adom-cli hydrogen workspace add-tab --display-icon`.
   The tab strip is Adom UI chrome; every app dropping a colored icon
   in there turns the strip into a clown car.
3. **Every other rendering of the app icon inside the Adom UI** —
   panel catalog, discovery list, start-menu equivalents, etc.

The *only* place brand color is allowed is **wiki marketing art** —
the hero image and thumbnail on the app's wiki page. Those are
screenshots-of-branded-products, not Adom UI elements.

**Past Claudes (including the author of this app) have missed this
rule three times.** If you are auditing an app and your instinct is
*"the skill says favicons CAN be teal, so my teal `docs/icon.svg` is
fine"* — you are reading a stale cache of this skill. There is no
favicon carve-out. Delete that belief and re-read this section.

**Pre-publish audit — gate every release on this returning empty:** see [`tab-wiring-reference.md`](tab-wiring-reference.md) § Icon audit grep commands for the two `grep` commands and the wrong/right SVG HTML examples.

Both must return zero. The `docs/icon.svg` hit is the most common one because authors assume favicons are exempt. They are not. Prefer MDI icons (already monochrome, work with `currentColor`) or custom 24x24 SVGs with `fill="currentColor"`. Never use colored icon sets. If using `currentColor`, verify the parent's `color` is `var(--text)` or `#e6edf3` — a teal parent silently turns the icon teal.

**No favicon carve-out.** (Earlier revisions of this skill said `docs/icon.svg` could be colored. That was wrong — the favicon renders in the Hydrogen tab strip, which is UI chrome, not a marketing surface.)

### 2. Ship a custom SVG favicon

Code samples for serving `/favicon.svg` and referencing it in HTML, icon design tips, and icon generation guidance: see [`tab-wiring-reference.md`](tab-wiring-reference.md) § SVG favicon implementation.

Requirements:

- **SVG format**, square viewBox (`0 0 64 64` or `0 0 24 24`), transparent background.
- **Monochrome white only** — single `fill="#e6edf3"` (or `currentColor`). NO teal, no gradients. See §1b.
- **Saved at `docs/icon.svg`** in the repo; serve it at `/favicon.svg` with `Content-Type: image/svg+xml`.
- Reference with a **relative** `href="favicon.svg"` (no leading slash — see §7a).

### 3. Use the Adom proxy URL, not 127.0.0.1

The Hydrogen webview iframe cannot load `http://127.0.0.1:PORT/` directly — the browser blocks it as cross-origin. Always point the tab at the Coder proxy URL: `https://<slug>.adom.cloud/proxy/<PORT>/`.

Read `VSCODE_PROXY_URI` from the environment and substitute `{{port}}`. Rust code sample and `127.0.0.1` fallback for terminal testing: see [`tab-wiring-reference.md`](tab-wiring-reference.md) § Proxy URL substitution.

### 4. Open the webview tab via adom-cli

Full `adom-cli hydrogen workspace add-tab` command, `--display-icon` forms (MDI string vs base64 data URL), Rust embed pattern, and tab-cleanup command: see [`tab-wiring-reference.md`](tab-wiring-reference.md) § Opening and closing the webview tab.

Key points:
- Panel type UUID for webview: `adom/a1b2c3d4-0031-4000-a000-000000000031`.
- Get `leaf-id` with `adom-cli hydrogen workspace get | jq -r '.focusedPanelId'`.
- Pass `docs/icon.svg` as a `data:image/svg+xml;base64,...` URL via `--display-icon` (Hydrogen does not auto-pick up the `<link rel="icon">` favicon).
- Always call `adom-cli hydrogen workspace remove-tab` on exit.

### 5. Handle browser requirements

The webview is a real Chromium-based iframe. Everything browser-native
works (MediaRecorder, WebAudio, Canvas, WebGL, fetch, etc.) but remember:

- **Permissions** (mic, camera, geolocation) go through Hydrogen, not the
  iframe. If your app needs the mic, use `adom-cli hydrogen audio enable`
  from the CLI and let Hydrogen capture, don't try `getUserMedia` inside
  the iframe, it'll fail on permissions.
- **File downloads** trigger Hydrogen's download handler, which saves to
  `~/project/` paths on the container. Use those paths in subsequent CLI
  commands, not browser `Downloads/`.
- **Range requests**, support them on any endpoint that streams large
  files (video, audio, big JSON blobs). Hydrogen's video player uses Range
  by default; if your server 404s on Range it breaks seeking.
- **No cookies / no localStorage expectations**, webviews are fresh every
  session. Persist state to disk via your CLI, not the browser.

### 6. Graceful shutdown

Apps should have a `/shutdown` endpoint (or equivalent) that the UI can
call when the user is done. On receiving it, the server:
1. Finishes any pending work (file writes, ffmpeg mux, etc.)
2. Removes the Hydrogen webview tab
3. Exits the CLI process cleanly

Never leave the server running after the user is done, it holds a port,
shows a zombie tab, and confuses future invocations.

### 7a. Always use relative URLs inside the webview (CRITICAL)

Apps hosted through the Coder `/proxy/<port>/` prefix MUST use relative
URLs for every fetch, href, src, and link. A leading slash resolves to
the origin (`hydrogen.adom.inc`) and bypasses the proxy entirely, so your
fetches hit the wrong server and fail silently.

Wrong/right HTML examples: see [`tab-wiring-reference.md`](tab-wiring-reference.md) § Relative URL wrong vs right examples.

Every `fetch()`, `<a href>`, `<link rel="icon">`, `<img src>`, `<script src>`, every CSS `url()`. If it's in the HTML or JS your app serves, no leading slash. Ever.

Symptom when you get this wrong: the UI loads (since the top-level HTML
comes from the proxy), but every button click silently does nothing, the
video player stays blank, and your `/console` log stays empty because the
JS console forwarding is also hitting the wrong URL. You can spend hours
chasing "why isn't this working" before realizing every network request
is going to the wrong host. This is the single most common bug when
wiring up a new Adom app.

### 7. 2-way HTTP communication (CRITICAL)

**Every action the UI can trigger MUST be independently triggerable via
plain HTTP from outside the UI.** This is the single most important rule
for Adom apps and is what makes them AI-drivable.

Why: the AI building/testing/operating the app has to be able to drive
it without a human clicking buttons. If the Record button is wired to a
JS function that only the UI calls, the AI is stuck. If the Record button
POSTs to `/start-recording` (the same endpoint the AI can hit with curl),
the AI can test, debug, and automate the app by itself.

Rules:

- **Every user-visible action has a server endpoint.** Not just a
  JS function. The JS onClick handler is a thin wrapper that POSTs to the
  endpoint; it does NOT contain the logic.
- **State changes go through the server**, not through in-memory JS state.
  If the UI needs to know something, it reads it from the server via
  `GET /state` (or SSE / websocket for live updates).
- **The AI can drive the full app lifecycle via curl.** Before shipping, you MUST be able to script a full user session from bash. If any step requires clicking a DOM element with no HTTP equivalent, that's a bug. See [`webview-conventions-detail.md`](webview-conventions-detail.md) § 7 for the curl-driven test sequence.

- **Always expose `GET /state`** (or similar) returning the app's current
  state as JSON so the AI can check progress without scraping HTML.
- **Return structured JSON on all POST endpoints**, not just HTML
  redirects. The AI parses the response to decide what to do next.

This is NOT a nice-to-have, it's the defining property of an Adom app.
A web server that can only be driven by clicking a human's mouse is just
a website. An Adom app is an AI-drivable surface with a human-friendly UI
layered on top.

**Server state is the single source of truth.** The UI must not keep its own copy of state like "am I recording?". Instead: poll `GET /state` at 300-500ms, compare `phase` against the last seen value, and drive UI transitions on every change. Buttons fire `POST` commands to the server only — they do NOT update local state. Full state-poller JS pattern and curl-driven happy-path test sequence: see [`webview-conventions-detail.md`](webview-conventions-detail.md) § 7. 2-way comms — state poller pattern.

The test: if the AI curl-driven flow and the human-click flow look identical in the UI, the wiring is correct. If they diverge, the UI is keeping local state that should live on the server.

### 7c. Every button needs a hover-delayed tooltip

Full tooltip implementation (CSS pattern, clipping rules, edge-anchored positioning, eval channel, console forwarding), plus mini web server patterns (Rust/Node/Python): see [`webview-conventions-detail.md`](webview-conventions-detail.md).

**Rule: every button (and any label whose meaning is not obvious to a
first-time user) must have a tooltip. The tooltip must be gentle: it
appears only after the pointer has been still on the target for about
500 ms, and it fades in smoothly, not instantly. A moved pointer that
passes over many buttons must not cause any tooltip to show.**

Why: buttons in an AI-built app have to be self-explaining, because
the user may not have read any docs and the AI cannot be standing next
to them. At the same time, tooltips that pop in on every movement are
aggressively noisy and make the UI feel twitchy. The 500 ms hover
delay is the standard tradeoff: intentional hovers get the explanation,
casual passes do not.

**Required content of a button tooltip:**

1. What the button does, in plain English.
2. What will change in the app after you click it.
3. For destructive or irreversible-seeming actions, what is and is
   not preserved (files on disk, state in memory, etc.).
4. For actions that use technical jargon in the button label (for
   example, "EBU R128", "Auto-level", "Mux"), an explanation of the
   jargon, written for a non-expert. Never assume the user has the
   vocabulary; always define the terms.

**Required content of a non-button label tooltip** (for example, a
waveform meta line that says `"EBU R128 -16 LUFS / -1.5 dBTP"`):

1. What the label means, in human terms, no jargon.
2. A breakdown of any technical parameters, one by one, each with
   a plain-English explanation and what it means for the user's
   outcome ("quieter moments got slightly louder", "no audio will
   clip on playback", etc.).
3. Whatever the practical effect is on the thing the user was
   looking at. Do not just define terms; explain the consequence.

Implementation pattern, clipping rules, and edge-anchored positioning CSS: see [`webview-conventions-detail.md`](webview-conventions-detail.md) § 7c.

Key: use `data-tooltip` attribute with 500ms CSS transition delay. Do NOT use HTML `title=` attributes. See reference file for full CSS and edge-anchoring pattern.

### 7e. Diagnostic views NEVER auto-correct — show the truth

**Diagnostic views must show source data, never an auto-corrected prettier version.** Compute corrections, display them with a clear signal, but never silently apply them. Full rule, forbidden examples, and the `wrapper.position.z = -minZ` trap: see [`webview-conventions-detail.md`](webview-conventions-detail.md) § 7e.

### 7d. Provide a live "now playing" / "current state" indicator for any media or document being viewed

**When an artifact exists in multiple versions (raw/normalized/preview/committed), the UI must always show which version is currently visible or playing.** Labeled pill overlay pattern and color-keyed dot details: see [`webview-conventions-detail.md`](webview-conventions-detail.md) § 7d.

### 7b. Always expose a frontend eval channel so the AI can hot-patch the UI

Full implementation (server ring-buffer, UI `setInterval` poller, AI `curl` usage, and caveats): see [`webview-conventions-detail.md`](webview-conventions-detail.md) § 7b.

Key points:
- Ship `POST /eval` + `GET /eval/:id` endpoints so the AI can push JS snippets into the running UI.
- Gate behind `--dev` / `DEV_MODE=1` — this is a full remote-code-execution primitive.
- Results route through `/console` or a dedicated `/eval-result` endpoint; never just `"ok": true`.
- Catch and return exceptions as `{error, stack}` so the AI can diagnose snippet failures.

### 7a. AI drives the frontend via state mutations

The flip side of 2-way comms is that the AI is an equal citizen to the
human user. When the AI wants to drive the UI, it:

1. POSTs to a command endpoint (`/start-recording`, `/play`, `/seek`)
2. The server updates its state
3. The UI's state poller sees the change and reflects it

There is no separate "AI remote control" channel. The same `POST
/start-recording` that your UI's Record button fires is what the AI
fires. The UI's state poller is what turns the server state change into
visual feedback the human sees.

This means your server's command endpoints double as AI automation
hooks. Test that during development: before shipping, `curl -X POST
.../start-recording` from the terminal and watch the UI animate without
touching the browser. If it doesn't animate, the state poller is broken
or the UI is keeping local state.

### 8. JavaScript console forwarding

Full `<script>` snippet, server ring-buffer implementation, and AI debug workflow: see [`webview-conventions-detail.md`](webview-conventions-detail.md) § 8.

Key points:
- Override `console.log/warn/error` to `POST /console` as JSON; keep a bounded ring buffer server-side (500 entries).
- Catch `window.onerror` and `unhandledrejection` and forward them too.
- Expose `GET /console` so the AI can read all UI-side log output without DevTools access.
- Never ship an app without this — it is the only way to debug UI-side bugs in the webview.

### 9. Use Hydrogen's built-in recording countdown

Hydrogen's `recording start --countdown 3` shows a native 3-2-1 countdown
overlay before starting capture. **Use this instead of writing your own
JS countdown in the app's UI.** The native overlay is:

- Visually consistent with the rest of Hydrogen
- Reliable (runs in the parent window, can't be broken by iframe issues)
- Works for mic-only sessions too (disable screen, enable audio, still get countdown)

If your app needs a countdown before capturing audio, trigger
`adom-cli hydrogen recording start --countdown 3 --audio-only` (or whatever
the current flag spelling is, check `adom-cli hydrogen recording start --help`)
from your server's `/start-recording` endpoint. Don't roll your own JS
timer; it'll drift, miss the first click, and look different from every
other countdown in the system.

## Mini web server patterns

Full code templates for Rust (`tiny_http`), Node.js, and Python: see [`webview-conventions-detail.md`](webview-conventions-detail.md) § Mini web server patterns.

Pick whichever language the CLI is already using. Embed UI assets at compile time (Rust `include_str!`) or load them from disk (Node.js / Python). Always serve `/favicon.svg` with `Content-Type: image/svg+xml` and implement `/shutdown` for clean exit.

## Before publishing: show the user the working app

**Never publish an app to GitHub or the wiki before the user has seen it
working.** The user has to click through the UI (or watch the AI click
through the 2-way HTTP endpoints) and confirm it behaves correctly before
any `git push`, `gh release create`, or `adom-wiki page create` runs.

This is a hard rule because:
- The AI cannot judge UI correctness by reading code. Fonts load wrong,
  layouts break, animations stutter, and the code still compiles.
- Publishing means other users will find it via wiki discovery. Shipping a
  broken app wastes every future user's time.
- Git history and wiki publish dates are public. A bad v0.1.0 is embarrassing.

Correct flow:

1. Build the app locally
2. Start it (`my-app run` or equivalent)
3. Show the user a screenshot of the UI or ask them to click through it
4. Drive the 2-way HTTP endpoints yourself to verify the happy path works
5. Fix whatever the user or the verification surfaces
6. Only after the user gives explicit approval, publish to GitHub + wiki

If the user says "build an app that does X", the default end state is
**a running app with a screenshot shown to the user**, NOT a published
GitHub repo. The publish step comes after a separate "ship it" from the user.

## CLI surface

Follow **`adom-cli-design`** for the app's CLI — output conventions
(`OK:` / `ERROR:` / `Hint:`), isatty-aware colors, the skill-file pattern,
and the Rust + `clap` defaults. All of those apply in full to apps; this
skill doesn't restate them.

## Publishing an app to the wiki

Follow the `tool-publisher` skill for the full lifecycle. The short version:

1. **Create the GitHub repo** (`adom-inc/<app-name>`, private by default).
2. **Push v0.1.0** with Cargo.toml / package.json / setup.py + SKILL.md +
   README.md + docs/icon.svg.
3. **Create a GitHub release** with the binary (or install script) attached.
4. **Publish a wiki page** of type `app` at `apps/<app-name>` with `type`, `slug`, `title`, `brief`, and `metadata` fields including `repo`, `version`, `releases.adom_docker`, `discovery_triggers`, and `discovery_pitch`. Full JSON template: see [`tab-wiring-reference.md`](tab-wiring-reference.md) § Wiki page metadata template.
5. **Verify** the wiki page renders the install prompt correctly (`adom-wiki page get apps/my-app-name`).

The wiki's `/discover` aggregator picks up `discovery_triggers` on next
regeneration and adds them to `adom-wiki-discover`, so any Claude Code
session that hears a matching trigger will suggest your app automatically.

## Relationship to other skills

- **`tool-publisher`**, how to publish any CLI tool (including apps) to
  the wiki. Apps are a specific kind of tool: CLI + embedded HTTP server +
  webview UI.
- **`adom-cli-design`**, the output conventions (`OK:` / `ERROR:` /
  `Hint:`) that apply to the app's CLI surface, before and after the
  server runs.
- **`brand`**, the visual identity (colors, fonts, spacing) that every
  app's HTML must follow.
- **`adom-panels`** / **`adom-panels-webview`**, the Hydrogen webview
  panel API, including `navigate`, `refresh`, and proxy mode.
- **`adom-workspace-control`**, adding/removing tabs, finding panel leaf
  IDs, activating tabs.
- **`adom-wiki`**, the CLI for publishing wiki pages and uploading
  assets (including the app's wiki page + icon + install prompt).
- **`av-creator`** — **DEPRECATED.** The Adom Viewer is no longer the
  canonical display surface. Build new visual content (3D models,
  diagrams, widgets, charts) as apps via this skill, not as AV widgets.
  Only consult `av-creator` if you're maintaining an existing
  un-ported AV view.

## Reference apps

- **video-post** (`adom-inc/video-post`), Rust CLI + tiny_http
  server, voiceover recording UI, ffmpeg mux pipeline. Demonstrates the
  full pattern: brand-compliant UI, SVG favicon, proxy URL, Hydrogen
  audio integration, graceful shutdown.
- **adom-desktop demo server** (`adom-desktop/skills/demo/server.cjs`) -
  Node.js HTTP server that drives the step-by-step demo webview. Uses the
  same webview tab + proxy URL pattern.

## Checklist before publishing

Full checklist (UI + brand, server wiring, 2-way comms, CLI output, verification, repo + wiki): see [`publish-checklist.md`](publish-checklist.md).

The two blocking categories are **2-way comms** (every action must be curl-scriptable, `GET /state` must exist, `GET /console` must be wired) and **verification** (user must have seen the running UI and given explicit approval before any `git push`, `gh release create`, or wiki publish runs).
