skill
App Creator
Public Made by Adomby adom
Use when the user wants to build a new Adom "app", a mini web server that renders its UI in a Hydrogen webview tab.
{
"schema_version": 1,
"type": "skill",
"slug": "app-creator",
"title": "App Creator",
"brief": "Use when the user wants to build a new Adom \"app\", a mini web server that renders its UI in a Hydrogen webview tab.",
"version": "1.1.1",
"tags": [],
"license": "MIT",
"sample_prompts": [
{
"prompt": "Create a new Adom app for visualizing my BOM by vendor"
},
{
"prompt": "Scaffold a Hydrogen webview app with a status HUD and a server-sent-events feed"
},
{
"prompt": "Build me an Adom mini app that wraps the parts-search CLI in a UI"
}
],
"source_path": "SKILL.md",
"readme": "---\nname: app-creator\ndescription: >\n Use when the user wants to build a new Adom \"app\" \u2014 a mini web server that\n renders its UI in a Hydrogen webview tab. Covers architecture (server in\n any language: Rust+tiny_http, Node, Python, Go), required conventions\n (Adom brand, custom SVG favicon, proxy URL, webview safety), and publish\n flow (GitHub repo + wiki app page for discovery and install). Trigger\n words: make an app, create an app, build an app, new app, adom app, build\n an adom app, mini web app, mini web server, webserver app, build a\n webview app, webview app, hydrogen app, build a widget, create mini app,\n tiny app, tool with a UI, cli with a UI, app needs an icon, favicon for\n an app, app branding, app icon, app standards, how to make an adom app,\n app conventions, app template, webview-hosted app, first-class app, app\n page on wiki.\n---\n\n# Adom App Creator\n\n> ## \ud83d\uded1 STEP 0 \u2014 MANDATORY before writing one line of UI code\n>\n> Before authoring any HTML, CSS, JS, SVG, or webview content:\n>\n> 1. **Read [`gallia/skills/brand/SKILL.md`](../brand/SKILL.md) end-to-end.**\n> Palette tokens (`#0d1117` page bg, `#161b22` panels, `#00b8b1` accent),\n> Familjen Grotesk + Satoshi + JetBrains Mono fonts, **monochrome icons\n> only** (`#e6edf3` or `currentColor` \u2014 *never* emoji, *never* coloured\n> favicons, *never* coloured tab-strip icons), 0.15 s transitions, frosted-\n> glass card pattern, dark theme always.\n> 2. **Read [`gallia/skills/human-ui-patterns/SKILL.md`](../human-ui-patterns/SKILL.md) end-to-end.**\n> Tooltips (rule 1d \u2014 body-appended `position:fixed` div, z 99999, NEVER\n> `title=\"\"` or CSS `::after`), HUDs (draggable + collapsible + dismissible),\n> NEVER ALL CAPS, click previews, hero images, provenance captions,\n> AI-drivability, the full pre-publish checklist.\n>\n> Both skills are <500 lines each. Reading them takes ~30 s and prevents the\n> #1 user complaint on every UI build: *\"have you even bothered to look at\n> the brand guide skill and ui design skill?\"* (verbatim, 2026-04-26 on the\n> aci library dashboard).\n>\n> **Rule of thumb:** if you've just typed `<div>` or `:root {` without having\n> the open contents of those two files in your context window in the last\n> two minutes, stop and Read them. The rules are easy to half-remember and\n> half-violate at the same time \u2014 emoji icons, the wrong shade of background\n> grey, a teal favicon, `title=\"\"` tooltips. Every one of those has shipped\n> from a Claude Code session that \"knew the rules\" but didn't re-read.\n>\n> **No exceptions for \"quick prototypes\" or \"internal tools.\"** This skill,\n> aci library, every chip-fit HUD, every wiki page hero \u2014 they all started as\n> \"I'll just throw something together and polish later.\" The polish phase\n> has never come; the violations ship.\n\nAdom \"apps\" are mini web servers that render their UI inside a Hydrogen\n**webview panel tab** (not the Adom Viewer). They look and feel like native\nHydrogen panels to the user, but under the hood they're just HTTP servers\nthe AI drives. Any future Adom user can discover them via wiki triggers and\ninstall them via the standard app install prompt.\n\nThis skill covers:\n1. What counts as an \"app\" (vs a CLI-only tool or an AV widget)\n2. The required conventions every app must follow (brand, favicon, proxy URL)\n3. The mini web server pattern (any language / any HTTP library)\n4. How to ship: GitHub repo, wiki app page, install prompt\n5. Where apps fit in the broader skill ecosystem\n\n## What is an Adom app?\n\nAn Adom app is:\n\n- A **mini web server** that runs on the Adom Docker container (or the user's\n own machine, for desktop integrations). The server listens on a TCP port.\n- Its UI is **HTML + CSS + JavaScript** served by that server, rendered in a\n **Hydrogen webview panel tab**. Not the Adom Viewer. Not a Tauri window.\n Not a browser tab the user opens manually, a first-class Hydrogen panel.\n- The server can be a **single page** or a **multi-page site**, the app's\n author decides based on what fits the feature.\n- The implementation language is the AI's choice. Rust with embedded\n `tiny_http` works great for tools that also expose a CLI (e.g.,\n video-post). Node/Python/Go/anything else is fine if it better matches\n the workflow.\n- The app is **discoverable via wiki triggers**. A user says something like\n \"speed up my demo recording\" and Claude Code suggests the matching app\n from the wiki, with a paste-into-Claude install prompt.\n\nAn app is NOT:\n\n- A CLI-only tool (those belong in `adom-cli-design` / `tool-publisher`\n without the web UI)\n- An Adom Viewer widget (those use `av-creator` and push HTML to the AV\n server, which is a different render surface)\n- A Hydrogen built-in panel like 3D Editor or Schematic Editor (those are\n Tauri native + ship with Hydrogen itself)\n\n**Read `adom-app-model` first** for the bigger picture (repo layout, wiki\npublishing, Tier A vs Tier B distribution, service containers, version\ndiscipline). This skill only covers the client-side UI conventions; the\nmodel skill covers \"where does this app live and how does it ship?\"\n\n**Also read `adom-cli-design`.** Every app ships a CLI surface (at minimum\n`<app> install`, `<app> serve`, and whatever subcommands the AI drives the\nserver with). That CLI must follow the Adom CLI conventions \u2014 Rust +\n`clap`, `OK:` / `ERROR:` / `Hint:` output lines, isatty-aware colors, the\nskill-file pattern. Apps with a half-finished CLI surface annoy everyone\ndownstream; don't skip this read.\n\n## Does your app need a service container?\n\nIf the app has a backend that holds API keys, caches third-party responses,\nor otherwise wants to be shared across users, it needs a **private service\ncontainer** on the `default-light` image. See `standalone-service` skill for\nthe full setup, but the short version:\n\n- Add `service/{deploy.sh, watchdog.sh, service.json}` to your repo root\n- Deploy once with `adom-cli carbon containers create --image-id <default-light> --repo-id <REPO_ID> --ssh`\n followed by `ssh ... bash service/deploy.sh` (the CLI requires `--repo-id`)\n- The watchdog cron auto-pulls `origin/main` every 2 min and rebuilds +\n restarts \u2014 you don't redeploy after releases, just `git push`\n- The user's container points `<APP>_API` at the service container's public URL\n for shared-cache benefits\n\nIf the app is a pure client (no backend state worth sharing), skip the\n`service/` directory and let it run locally per-user.\n\n## Each app ships a BUILD-SKILL.md\n\nEvery app's release process is the same shape (bump VERSION \u2192 cargo build \u2192\ngit tag \u2192 gh release \u2192 `adom-wiki asset upload` \u2192 `adom-wiki page update`\n\u2192 verify). Bundle this as `<repo>/BUILD-SKILL.md` and deploy it from the\nbinary's `install` subcommand to `~/.claude/skills/<slug>-build/SKILL.md`.\nSee `adom-app-model` for the template. Trigger words like \"release X\",\n\"bump X\", \"publish X\" route Claude to it.\n\n## Required conventions\n\nEvery Adom app MUST follow these rules. They exist so apps feel like\nfirst-class Hydrogen panels, not random HTML pages someone served on a port.\n\n### 1. Use the Adom brand guide\n\nSource: `~/project/gallia/skills/brand/SKILL.md`, read it first.\n\nMinimum requirements for every app's UI:\n\n- **Fonts:** Familjen Grotesk (headlines) + Satoshi (body) + JetBrains Mono\n (code). Load from `https://adom.inc/fonts/` via `@import url(...)` in\n your CSS. **Never** fall back to Inter, Arial, Helvetica, or system fonts\n as primary.\n- **Color palette:** full `:root` CSS token block at [`tab-wiring-reference.md`](tab-wiring-reference.md) \u00a7 Brand CSS tokens. Use `--accent` (#00b8b0 teal) for primary actions, `--green` for success, `--red` for destructive.\n- **Dark theme by default.** The Hydrogen workspace is dark, so any white\n panel looks jarring. Start from `--bg: #0d1117` and only go lighter when\n you need contrast.\n- **Adom teal accent** (`#00b8b0`) for primary actions, focus rings, and\n highlights. Green (`#3fb950`) only for success states, red (`#f85149`)\n only for destructive actions.\n- **8px / 12px / 16px spacing grid.** No random 13px margins.\n- **Border radius `8px`** for cards and buttons, `12px` for larger\n containers. No sharp corners unless you have a specific reason.\n\nSee the video-post voiceover UI (`src/voiceover/ui.html` in the\n`adom-inc/video-post` repo) for a working example that follows every\nrule above.\n\n### 1b. \ud83d\uded1 EVERY icon is monochrome white \u2014 no exceptions, no favicon carve-out\n\nPer `brand/SKILL.md`: **every icon in an Adom app is monochrome white\n(`#e6edf3`) or `currentColor` inheriting from a white-text context**.\nThis includes:\n\n1. **In-app icons** \u2014 header logo, button glyphs, tooltip icons,\n sidebar icons, empty-state art.\n2. **The Hydrogen tab-strip icon** \u2014 `docs/icon.svg`, both as the HTML\n `<link rel=\"icon\">` favicon AND as the base64-encoded data URL\n passed to `adom-cli hydrogen workspace add-tab --display-icon`.\n The tab strip is Adom UI chrome; every app dropping a colored icon\n in there turns the strip into a clown car.\n3. **Every other rendering of the app icon inside the Adom UI** \u2014\n panel catalog, discovery list, start-menu equivalents, etc.\n\nThe *only* place brand color is allowed is **wiki marketing art** \u2014\nthe hero image and thumbnail on the app's wiki page. Those are\nscreenshots-of-branded-products, not Adom UI elements.\n\n**Past Claudes (including the author of this app) have missed this\nrule three times.** If you are auditing an app and your instinct is\n*\"the skill says favicons CAN be teal, so my teal `docs/icon.svg` is\nfine\"* \u2014 you are reading a stale cache of this skill. There is no\nfavicon carve-out. Delete that belief and re-read this section.\n\n**Pre-publish audit \u2014 gate every release on this returning empty:** see [`tab-wiring-reference.md`](tab-wiring-reference.md) \u00a7 Icon audit grep commands for the two `grep` commands and the wrong/right SVG HTML examples.\n\nBoth 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` \u2014 a teal parent silently turns the icon teal.\n\n**No favicon carve-out.** (Earlier revisions of this skill said `docs/icon.svg` could be colored. That was wrong \u2014 the favicon renders in the Hydrogen tab strip, which is UI chrome, not a marketing surface.)\n\n### 2. Ship a custom SVG favicon\n\nCode 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) \u00a7 SVG favicon implementation.\n\nRequirements:\n\n- **SVG format**, square viewBox (`0 0 64 64` or `0 0 24 24`), transparent background.\n- **Monochrome white only** \u2014 single `fill=\"#e6edf3\"` (or `currentColor`). NO teal, no gradients. See \u00a71b.\n- **Saved at `docs/icon.svg`** in the repo; serve it at `/favicon.svg` with `Content-Type: image/svg+xml`.\n- Reference with a **relative** `href=\"favicon.svg\"` (no leading slash \u2014 see \u00a77a).\n\n### 3. Use the Adom proxy URL, not 127.0.0.1\n\nThe Hydrogen webview iframe cannot load `http://127.0.0.1:PORT/` directly \u2014 the browser blocks it as cross-origin. Always point the tab at the Coder proxy URL: `https://<slug>.adom.cloud/proxy/<PORT>/`.\n\nRead `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) \u00a7 Proxy URL substitution.\n\n### 4. Open the webview tab via adom-cli\n\nFull `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) \u00a7 Opening and closing the webview tab.\n\nKey points:\n- Panel type UUID for Web View: `adom/a1b2c3d4-0031-4000-a000-000000000031`.\n- Get `leaf-id` with `adom-cli hydrogen workspace get | jq -r '.focusedPanelId'`.\n- 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).\n- Always call `adom-cli hydrogen workspace remove-tab` on exit.\n\n### 5. Handle browser requirements\n\nThe webview is a real Chromium-based iframe. Everything browser-native\nworks (MediaRecorder, WebAudio, Canvas, WebGL, fetch, etc.) but remember:\n\n- **Permissions** (mic, camera, geolocation) go through Hydrogen, not the\n iframe. If your app needs the mic, use `adom-cli hydrogen audio enable`\n from the CLI and let Hydrogen capture, don't try `getUserMedia` inside\n the iframe, it'll fail on permissions.\n- **File downloads** trigger Hydrogen's download handler, which saves to\n `~/project/` paths on the container. Use those paths in subsequent CLI\n commands, not browser `Downloads/`.\n- **Range requests**, support them on any endpoint that streams large\n files (video, audio, big JSON blobs). Hydrogen's video player uses Range\n by default; if your server 404s on Range it breaks seeking.\n- **No cookies / no localStorage expectations**, webviews are fresh every\n session. Persist state to disk via your CLI, not the browser.\n\n### 6. Graceful shutdown\n\nApps should have a `/shutdown` endpoint (or equivalent) that the UI can\ncall when the user is done. On receiving it, the server:\n1. Finishes any pending work (file writes, ffmpeg mux, etc.)\n2. Removes the Hydrogen webview tab\n3. Exits the CLI process cleanly\n\nNever leave the server running after the user is done, it holds a port,\nshows a zombie tab, and confuses future invocations.\n\n### 7a. Always use relative URLs inside the webview (CRITICAL)\n\nApps hosted through the Coder `/proxy/<port>/` prefix MUST use relative\nURLs for every fetch, href, src, and link. A leading slash resolves to\nthe origin (`hydrogen.adom.inc`) and bypasses the proxy entirely, so your\nfetches hit the wrong server and fail silently.\n\nWrong/right HTML examples: see [`tab-wiring-reference.md`](tab-wiring-reference.md) \u00a7 Relative URL wrong vs right examples.\n\nEvery `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.\n\nSymptom when you get this wrong: the UI loads (since the top-level HTML\ncomes from the proxy), but every button click silently does nothing, the\nvideo player stays blank, and your `/console` log stays empty because the\nJS console forwarding is also hitting the wrong URL. You can spend hours\nchasing \"why isn't this working\" before realizing every network request\nis going to the wrong host. This is the single most common bug when\nwiring up a new Adom app.\n\n### 7. 2-way HTTP communication (CRITICAL)\n\n**Every action the UI can trigger MUST be independently triggerable via\nplain HTTP from outside the UI.** This is the single most important rule\nfor Adom apps and is what makes them AI-drivable.\n\nWhy: the AI building/testing/operating the app has to be able to drive\nit without a human clicking buttons. If the Record button is wired to a\nJS function that only the UI calls, the AI is stuck. If the Record button\nPOSTs to `/start-recording` (the same endpoint the AI can hit with curl),\nthe AI can test, debug, and automate the app by itself.\n\nRules:\n\n- **Every user-visible action has a server endpoint.** Not just a\n JS function. The JS onClick handler is a thin wrapper that POSTs to the\n endpoint; it does NOT contain the logic.\n- **State changes go through the server**, not through in-memory JS state.\n If the UI needs to know something, it reads it from the server via\n `GET /state` (or SSE / websocket for live updates).\n- **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) \u00a7 7 for the curl-driven test sequence.\n\n- **Always expose `GET /state`** (or similar) returning the app's current\n state as JSON so the AI can check progress without scraping HTML.\n- **Return structured JSON on all POST endpoints**, not just HTML\n redirects. The AI parses the response to decide what to do next.\n\nThis is NOT a nice-to-have, it's the defining property of an Adom app.\nA web server that can only be driven by clicking a human's mouse is just\na website. An Adom app is an AI-drivable surface with a human-friendly UI\nlayered on top.\n\n**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 \u2014 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) \u00a7 7. 2-way comms \u2014 state poller pattern.\n\nThe 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.\n\n### 7c. Every button needs a hover-delayed tooltip\n\nFull 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).\n\n**Rule: every button (and any label whose meaning is not obvious to a\nfirst-time user) must have a tooltip. The tooltip must be gentle: it\nappears only after the pointer has been still on the target for about\n500 ms, and it fades in smoothly, not instantly. A moved pointer that\npasses over many buttons must not cause any tooltip to show.**\n\nWhy: buttons in an AI-built app have to be self-explaining, because\nthe user may not have read any docs and the AI cannot be standing next\nto them. At the same time, tooltips that pop in on every movement are\naggressively noisy and make the UI feel twitchy. The 500 ms hover\ndelay is the standard tradeoff: intentional hovers get the explanation,\ncasual passes do not.\n\n**Required content of a button tooltip:**\n\n1. What the button does, in plain English.\n2. What will change in the app after you click it.\n3. For destructive or irreversible-seeming actions, what is and is\n not preserved (files on disk, state in memory, etc.).\n4. For actions that use technical jargon in the button label (for\n example, \"EBU R128\", \"Auto-level\", \"Mux\"), an explanation of the\n jargon, written for a non-expert. Never assume the user has the\n vocabulary; always define the terms.\n\n**Required content of a non-button label tooltip** (for example, a\nwaveform meta line that says `\"EBU R128 -16 LUFS / -1.5 dBTP\"`):\n\n1. What the label means, in human terms, no jargon.\n2. A breakdown of any technical parameters, one by one, each with\n a plain-English explanation and what it means for the user's\n outcome (\"quieter moments got slightly louder\", \"no audio will\n clip on playback\", etc.).\n3. Whatever the practical effect is on the thing the user was\n looking at. Do not just define terms; explain the consequence.\n\nImplementation pattern, clipping rules, and edge-anchored positioning CSS: see [`webview-conventions-detail.md`](webview-conventions-detail.md) \u00a7 7c.\n\nKey: 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.\n\n### 7e. Diagnostic views NEVER auto-correct \u2014 show the truth\n\n**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) \u00a7 7e.\n\n### 7d. Provide a live \"now playing\" / \"current state\" indicator for any media or document being viewed\n\n**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) \u00a7 7d.\n\n### 7b. Always expose a frontend eval channel so the AI can hot-patch the UI\n\nFull implementation (server ring-buffer, UI `setInterval` poller, AI `curl` usage, and caveats): see [`webview-conventions-detail.md`](webview-conventions-detail.md) \u00a7 7b.\n\nKey points:\n- Ship `POST /eval` + `GET /eval/:id` endpoints so the AI can push JS snippets into the running UI.\n- Gate behind `--dev` / `DEV_MODE=1` \u2014 this is a full remote-code-execution primitive.\n- Results route through `/console` or a dedicated `/eval-result` endpoint; never just `\"ok\": true`.\n- Catch and return exceptions as `{error, stack}` so the AI can diagnose snippet failures.\n\n### 7a. AI drives the frontend via state mutations\n\nThe flip side of 2-way comms is that the AI is an equal citizen to the\nhuman user. When the AI wants to drive the UI, it:\n\n1. POSTs to a command endpoint (`/start-recording`, `/play`, `/seek`)\n2. The server updates its state\n3. The UI's state poller sees the change and reflects it\n\nThere is no separate \"AI remote control\" channel. The same `POST\n/start-recording` that your UI's Record button fires is what the AI\nfires. The UI's state poller is what turns the server state change into\nvisual feedback the human sees.\n\nThis means your server's command endpoints double as AI automation\nhooks. Test that during development: before shipping, `curl -X POST\n.../start-recording` from the terminal and watch the UI animate without\ntouching the browser. If it doesn't animate, the state poller is broken\nor the UI is keeping local state.\n\n### 8. JavaScript console forwarding\n\nFull `<script>` snippet, server ring-buffer implementation, and AI debug workflow: see [`webview-conventions-detail.md`](webview-conventions-detail.md) \u00a7 8.\n\nKey points:\n- Override `console.log/warn/error` to `POST /console` as JSON; keep a bounded ring buffer server-side (500 entries).\n- Catch `window.onerror` and `unhandledrejection` and forward them too.\n- Expose `GET /console` so the AI can read all UI-side log output without DevTools access.\n- Never ship an app without this \u2014 it is the only way to debug UI-side bugs in the webview.\n\n### 9. Use Hydrogen's built-in recording countdown\n\nHydrogen's `recording start --countdown 3` shows a native 3-2-1 countdown\noverlay before starting capture. **Use this instead of writing your own\nJS countdown in the app's UI.** The native overlay is:\n\n- Visually consistent with the rest of Hydrogen\n- Reliable (runs in the parent window, can't be broken by iframe issues)\n- Works for mic-only sessions too (disable screen, enable audio, still get countdown)\n\nIf your app needs a countdown before capturing audio, trigger\n`adom-cli hydrogen recording start --countdown 3 --audio-only` (or whatever\nthe current flag spelling is, check `adom-cli hydrogen recording start --help`)\nfrom your server's `/start-recording` endpoint. Don't roll your own JS\ntimer; it'll drift, miss the first click, and look different from every\nother countdown in the system.\n\n## Mini web server patterns\n\nFull code templates for Rust (`tiny_http`), Node.js, and Python: see [`webview-conventions-detail.md`](webview-conventions-detail.md) \u00a7 Mini web server patterns.\n\nPick 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.\n\n## Before publishing: show the user the working app\n\n**Never publish an app to GitHub or the wiki before the user has seen it\nworking.** The user has to click through the UI (or watch the AI click\nthrough the 2-way HTTP endpoints) and confirm it behaves correctly before\nany `git push`, `gh release create`, or `adom-wiki page create` runs.\n\nThis is a hard rule because:\n- The AI cannot judge UI correctness by reading code. Fonts load wrong,\n layouts break, animations stutter, and the code still compiles.\n- Publishing means other users will find it via wiki discovery. Shipping a\n broken app wastes every future user's time.\n- Git history and wiki publish dates are public. A bad v0.1.0 is embarrassing.\n\nCorrect flow:\n\n1. Build the app locally\n2. Start it (`my-app run` or equivalent)\n3. Show the user a screenshot of the UI or ask them to click through it\n4. Drive the 2-way HTTP endpoints yourself to verify the happy path works\n5. Fix whatever the user or the verification surfaces\n6. Only after the user gives explicit approval, publish to GitHub + wiki\n\nIf the user says \"build an app that does X\", the default end state is\n**a running app with a screenshot shown to the user**, NOT a published\nGitHub repo. The publish step comes after a separate \"ship it\" from the user.\n\n## CLI surface\n\nFollow **`adom-cli-design`** for the app's CLI \u2014 output conventions\n(`OK:` / `ERROR:` / `Hint:`), isatty-aware colors, the skill-file pattern,\nand the Rust + `clap` defaults. All of those apply in full to apps; this\nskill doesn't restate them.\n\n## Publishing an app to the wiki\n\nFollow the `tool-publisher` skill for the full lifecycle. The short version:\n\n1. **Create the GitHub repo** (`adom-inc/<app-name>`, private by default).\n2. **Push v0.1.0** with Cargo.toml / package.json / setup.py + SKILL.md +\n README.md + docs/icon.svg.\n3. **Create a GitHub release** with the binary (or install script) attached.\n4. **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) \u00a7 Wiki page metadata template.\n5. **Verify** the wiki page renders the install prompt correctly (`adom-wiki page get apps/my-app-name`).\n\nThe wiki's `/discover` aggregator picks up `discovery_triggers` on next\nregeneration and adds them to `adom-wiki-discover`, so any Claude Code\nsession that hears a matching trigger will suggest your app automatically.\n\n## Relationship to other skills\n\n- **`tool-publisher`**, how to publish any CLI tool (including apps) to\n the wiki. Apps are a specific kind of tool: CLI + embedded HTTP server +\n webview UI.\n- **`adom-cli-design`**, the output conventions (`OK:` / `ERROR:` /\n `Hint:`) that apply to the app's CLI surface, before and after the\n server runs.\n- **`brand`**, the visual identity (colors, fonts, spacing) that every\n app's HTML must follow.\n- **`adom-panels`** / **`adom-panels-webview`**, the Hydrogen webview\n panel API, including `navigate`, `refresh`, and proxy mode.\n- **`adom-workspace-control`**, adding/removing tabs, finding panel leaf\n IDs, activating tabs.\n- **`adom-wiki`**, the CLI for publishing wiki pages and uploading\n assets (including the app's wiki page + icon + install prompt).\n- **`av-creator`** \u2014 **DEPRECATED.** The Adom Viewer is no longer the\n canonical display surface. Build new visual content (3D models,\n diagrams, widgets, charts) as apps via this skill, not as AV widgets.\n Only consult `av-creator` if you're maintaining an existing\n un-ported AV view.\n\n## Reference apps\n\n- **video-post** (`adom-inc/video-post`), Rust CLI + tiny_http\n server, voiceover recording UI, ffmpeg mux pipeline. Demonstrates the\n full pattern: brand-compliant UI, SVG favicon, proxy URL, Hydrogen\n audio integration, graceful shutdown.\n- **adom-desktop demo server** (`adom-desktop/skills/demo/server.cjs`) -\n Node.js HTTP server that drives the step-by-step demo webview. Uses the\n same webview tab + proxy URL pattern.\n\n## Checklist before publishing\n\nFull checklist (UI + brand, server wiring, 2-way comms, CLI output, verification, repo + wiki): see [`publish-checklist.md`](publish-checklist.md).\n\nThe 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).\n",
"author": {
"name": "Kyle Bergstedt",
"email": "[email protected]"
},
"visibility": {
"public": true
},
"hero": null,
"discovery_triggers": [],
"discovery_pitch": null,
"metadata": {},
"created_at": "2026-05-28T05:29:45.880Z",
"updated_at": "2026-05-28T05:29:45.880Z",
"sub_skills": [],
"parent_app": null,
"org": "adom"
}