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:
- Read
gallia/skills/brand/SKILL.mdend-to-end. Palette tokens (#0d1117page bg,#161b22panels,#00b8b1accent), Familjen Grotesk + Satoshi + JetBrains Mono fonts, monochrome icons only (#e6edf3orcurrentColorโ never emoji, never coloured favicons, never coloured tab-strip icons), 0.15 s transitions, frosted- glass card pattern, dark theme always.- Read
gallia/skills/human-ui-patterns/SKILL.mdend-to-end. Tooltips (rule 1d โ body-appendedposition:fixeddiv, z 99999, NEVERtitle=""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:
- What counts as an "app" (vs a CLI-only tool or an AV widget)
- The required conventions every app must follow (brand, favicon, proxy URL)
- The mini web server pattern (any language / any HTTP library)
- How to ship: GitHub repo, wiki app page, install prompt
- 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_httpworks 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-publisherwithout the web UI) - An Adom Viewer widget (those use
av-creatorand 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> --sshfollowed byssh ... bash service/deploy.sh(the CLI requires--repo-id) - The watchdog cron auto-pulls
origin/mainevery 2 min and rebuilds + restarts โ you don't redeploy after releases, justgit push - The user's container points
<APP>_APIat 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
:rootCSS token block attab-wiring-reference.mdยง Brand CSS tokens. Use--accent(#00b8b0 teal) for primary actions,--greenfor success,--redfor destructive. - Dark theme by default. The Hydrogen workspace is dark, so any white
panel looks jarring. Start from
--bg: #0d1117and 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
8pxfor cards and buttons,12pxfor 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:
- In-app icons โ header logo, button glyphs, tooltip icons, sidebar icons, empty-state art.
- The Hydrogen tab-strip icon โ
docs/icon.svg, both as the HTML<link rel="icon">favicon AND as the base64-encoded data URL passed toadom-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. - 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 ยง 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 ยง SVG favicon implementation.
Requirements:
- SVG format, square viewBox (
0 0 64 64or0 0 24 24), transparent background. - Monochrome white only โ single
fill="#e6edf3"(orcurrentColor). NO teal, no gradients. See ยง1b. - Saved at
docs/icon.svgin the repo; serve it at/favicon.svgwithContent-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 ยง 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 ยง Opening and closing the webview tab.
Key points:
- Panel type UUID for Web View:
adom/a1b2c3d4-0031-4000-a000-000000000031. - Get
leaf-idwithadom-cli hydrogen workspace get | jq -r '.focusedPanelId'. - Pass
docs/icon.svgas adata: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-tabon 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 enablefrom the CLI and let Hydrogen capture, don't trygetUserMediainside 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 browserDownloads/. - 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:
- Finishes any pending work (file writes, ffmpeg mux, etc.)
- Removes the Hydrogen webview tab
- 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 ยง 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ยง 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 ยง 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.
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:
- What the button does, in plain English.
- What will change in the app after you click it.
- For destructive or irreversible-seeming actions, what is and is not preserved (files on disk, state in memory, etc.).
- 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"):
- What the label means, in human terms, no jargon.
- 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.).
- 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 ยง 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 ยง 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 ยง 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 ยง 7b.
Key points:
- Ship
POST /eval+GET /eval/:idendpoints 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
/consoleor a dedicated/eval-resultendpoint; 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:
- POSTs to a command endpoint (
/start-recording,/play,/seek) - The server updates its state
- 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 ยง 8.
Key points:
- Override
console.log/warn/errortoPOST /consoleas JSON; keep a bounded ring buffer server-side (500 entries). - Catch
window.onerrorandunhandledrejectionand forward them too. - Expose
GET /consoleso 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 ยง 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:
- Build the app locally
- Start it (
my-app runor equivalent) - Show the user a screenshot of the UI or ask them to click through it
- Drive the 2-way HTTP endpoints yourself to verify the happy path works
- Fix whatever the user or the verification surfaces
- 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:
- Create the GitHub repo (
adom-inc/<app-name>, private by default). - Push v0.1.0 with Cargo.toml / package.json / setup.py + SKILL.md + README.md + docs/icon.svg.
- Create a GitHub release with the binary (or install script) attached.
- Publish a wiki page of type
appatapps/<app-name>withtype,slug,title,brief, andmetadatafields includingrepo,version,releases.adom_docker,discovery_triggers, anddiscovery_pitch. Full JSON template: seetab-wiring-reference.mdยง Wiki page metadata template. - 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, includingnavigate,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 consultav-creatorif 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.
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).