Appearance
Tile Creator
The canonical (always up-to-date) version of this skill and the rest of the App Platform docs live at https://poe-tiles-docs.pages.dev/guide.html. Check there for the latest CLI install snippet (used to upgrade poe-tiles) and to refresh this skill in an existing project — the docs page links to the snippet, and poe-tiles skills install --dir .claude/skills overwrites the on-disk skill files with the version bundled in the currently-installed CLI.
Build a Poe app from a natural-language prompt. Scaffold → schema → UI → tests → deploy.
User Argument:
If empty, ask "What app should I build?" and stop. Otherwise derive a kebab-case app name (e.g. "voting app" → voting-app).
Important things you should do
- Prefer using synced-store for game state / data that should be persisted and/or synced across multiple users.
- When building new features, write the ui/logic first then add tests later
await store.waitForBootstrap()before rendering UI. Resolves as soon as authoritative data is ready from either source — local cache or first server pull — so offline-capable cases (already-loaded instance, or new instance opened withis-newset) unblock immediately without a server round-trip. AvoidwaitForServerData()in the render path: it always waits for a server pull, which breaks offline launches and stalls freshis-newinstances that have no server state to fetch. ReservewaitForServerData()for tests / Node-side scripts where a server round-trip is actually required.- If
onAddUserseeds first-render state, mirror the deterministic browser-safe handler intodefineClientConfig({ hooks });Poe.setupStore(config)runs it optimistically only for new creator launches (is-new="true"), then server data replaces the overlay. - Any time the user can try out a new state — publish by running
bun run publish-to-poe-tilesfrom the app's directory. ReportappUrlas a clickable markdown link. - Before the FIRST publish, fill in the listing-page fields the scaffold ships as
TODOplaceholders yourself — do NOT ask the user. Generate based on what the app actually does (derive fromsynced-store/schema.ts,ui/App.*, the user's original prompt): set.poe-app.json → displayNameto a human-friendly title (e.g."Texas Hold'em"rather than"texas-holdem"), rewriteREADME.md(long description), replace.poe-app.json → shortDescriptionwith a ≤140-char sentence specific to the app, and runbunx playwright test screenshot.test.playwright.tsafterbun run buildto regenerateassets/screenshot.png. Treat any remaining literalTODOin those files as a bug — fix it. See references/scaffolding-a-new-app.md Step 5.5. - Apps scaffolded via
poe-tiles apps initupload source code by default (visible to anyone who fetches it). ThesourceBundleblock in the app's.poe-app.jsoncontrols this — set"visibility": "owner_only"to keep source private to the creator, or delete the block entirely to skip source upload (the app will then be non-editable / non-remixable on the platform). The built-in default ignore list keeps.env*,node_modules/,dist/, etc. out of the bundle automatically. - Scaffolded apps publish flag-free:
bun run publish-to-poe-tilesis justbun run build && poe-tiles apps publish. The CLI reads handle, appvisibility,runtimeBundle.dir, andsourceBundlefrom.poe-app.jsonin the current directory. Override any field with the corresponding flag (--handle,--visibility,--dir, etc.) for one-off publishes; explicit flags always win over config. - Before starting any task run
./scripts/doctor.shto verify all dependencies are installed. Stop on non-zero exit. - Mobile-first. Apps must work on both desktop and mobile, but prioritize mobile — most players are on phones. Design touch targets, viewport sizing, and on-screen keyboard behavior for mobile first; desktop layouts come second.
- Apps must remain polished and legible in both light and dark system modes. Use theme-aware colors (
prefers-color-scheme, CSS variables, Tailwinddark:variants, or PDL semantic tokens where available) instead of a light-only or dark-only palette, and verify the app in both modes before declaring it done. - Apps must render nicely on the For You feed. Apps do not own the full viewport — they render inside a host iframe whose size is set by the parent. On mobile (e.g. iPhone SE, 375×667 viewport) the For You feed iframe is roughly 350px × 509px; on desktop the manager's For You feed iframe is roughly 780px × 414px (phone-shaped, host takes the rest of the screen for chrome). Apps must render well at both sizes. Layout must not have unintentionally overlapping or clipped elements. If the app is not intended to scroll, it must not scroll at either size — size content to fit, don't rely on the host clipping overflow. Test with the iframe at both sizes before declaring the app done.
- Use
Poe.hapticsfor haptic feedback on user actions —Poe.haptics.impact("light"|"soft"|"medium"|"rigid"|"heavy"),Poe.haptics.notification("success"|"warning"|"error"),Poe.haptics.selection(). Fire-and-forget, safe from any context (iframe or top frame), routed cross-platform: iOS native bridge →UIFeedbackGenerator, Android/mobile web →navigator.vibrate, iOS Safari 17.4+ → switch-label tap fallback, desktop / older iOS Safari → silent no-op. Don't callnavigator.vibratedirectly — it skips the iOS native path entirely. See @references/client-api.md. - Use
var(--poe-safe-area-inset-{top|bottom|left|right}, env(safe-area-inset-*))for any padding that protects content from device chrome or host-app overlays. The platform stylesheet maps each var to the matchingenv(...)value by default, but a parent app (e.g. the manager when it draws a top bar above the iframe) overrides them with explicit px values so the child does not double-pad. Never use rawenv(safe-area-inset-*)directly — it ignores parent-app overlays and the child renders behind the parent's chrome. See references/safe-area-insets.md. - In multi-player apps, render each participating user's display name and profile picture next to their in-app representation (e.g. their snake in a multi-player snake game, their cursor, their score row, their move). Read from
$userInfo— see @../synced-store/references/getting-user-info-of-members.md. - Write at least one Playwright test at
tests/e2e.test.playwright.tsthat drives the app's primary flow from start to finish — for a game, that means starting a new instance, taking the moves needed to reach a terminal state (win / loss / round end), and asserting the terminal state renders. Single-player apps can use one client; multi-player apps drive each player from a separateTestServerclient so sync is exercised end-to-end. See references/e2e-tests.md. - In multi-player apps, call
notifyActivitywhenever something happens that another player might care about — so the space bumps to the top of the recents list, lights up the per-space numeric badge in the sidebar, and (when warranted) rings the device. Apps that want standard badge behavior should opt intosimpleUnread({ clearOn: "active" }), then useunread: "increment"withoutpushfor passive updates ("spymaster gave a clue", "Aaron reacted 🎉"); omit unread for cosmetic refreshes. Pick push for required actions ("your turn") and high-signal opt-in events ("friend beat your high score"). UsepostToChatwhen the event should also appear as one app-owned announcement in the containing chat; do not add app-local room guards just for this becausenotifyActivityskips the chat append when no containing chat room exists. For games like checkers, chess, darts, and Poe Jump, do not post to chat on every move; reservepostToChatfor terminal/high-signal milestones such as a player winning, a match ending, or a new high score being reported. Target withtargetUserIds(the next player, the previous record-holder, etc.) — default sender-suppression handles "don't push me for my own action" automatically. See @references/client-api.md "When to notify, and at what level".
Always-loaded context
- ../synced-store/SKILL.md — synced-store skill (schema, mutators, actions, testing).
- references/client-api.md — Client API:
Poe.setupStore,Poe.stream,Poe.call,<poe-app>,notifyActivity, etc. - references/backend-api.md — Backend API:
defineSchema,defineBackendConfig, mutators, actions, system tables. - references/sandbox-limitations.md — Sandboxed iframe limits: storage APIs, URL navigation, cross-frame DOM,
window.location.origin. - ../synced-store/references/getting-user-info-of-members.md —
$userInfo/$userssystem tables; how to render display names and profile pictures.
References
- Read references/safe-area-insets.md before padding any element against the viewport edge — top bars, bottom bars, FABs, toasts.
- Read references/scaffolding-a-new-app.md when scaffolding a new app.
- Read references/composing-apps.md when the prompt might require multi-app composition (e.g. a March-Madness tournament where each match is a sub-app game, a Discord-like server with channels as sub-apps).
- Read references/running-the-client-api-on-a-server-owned-by-the-user.md when running a client on a user-owned NodeJS server (bots, scripts).
- Read references/vite-plugin.md when touching
vite.config.ts, externals, or backend bundling. - Read references/assets.md when storing or retrieving static files:
import "...?url"(Vite) vsPoe.getBundleAssetUrl()(runtime). - Read references/synced-store-client-reference.md for the
SyncedStoreClientAPI surface (subscribe,query,mutate). - Read references/unit-tests.md before writing
*.test.ts/*.test.happydom.tsx(createPoeAppTestHarness, fixtures,waitFor*). - Read references/e2e-tests.md before writing
tests/e2e.test.playwright.ts(TestServer,waitForBlobFrame, multi-client patterns). - Read synced-store/references/schema-migrations.md before bumping
schemaVersionor writing amigrateDatastep (harness.seedInstance,EntryKeyvs string pitfalls, push-driven upgrade). - Read references/cli.md and references/cli-limitations.md before scaffolding or publishing (
poe-tiles apps init/publish/list). - Read references/game-ux-best-practices.md when building a game — conventions for help affordance, turn indicator, feedback, end states.
- Read REST API for server-side endpoints managing apps from outside the platform (auto-generated OpenAPI; lives only in the docs site).