Skip to content

Publishing an activity

Read this only when your host has not already told you how publishing works. The mechanics below are the ones a creator gets when they drive the CLI themselves. A host that uploads on your behalf states its own rules in its system prompt, and those rules win over every line in this file — including which command you run, and whether you run one at all.

Everything else in the tile-creator skill is portable: it describes how to build a good activity, and it is the same wherever you build one. This file is the part that is not, which is why it lives on its own.

What upload and publish mean

  • What "publish" means to the creator, and what it does NOT mean. To them, publishing is making the activity PUBLIC — putting it where other people can find and play it, or moving those people onto a newer build. Uploading a build is not publishing; it produces a preview, reachable only by its link. Say "preview", "update the preview", or "new version" for that, and reserve "publish", "publish it", and "make it public" for the two things that actually change what the public gets: listing the activity, and moving the live version. The CLI command is named poe-tiles tiles publish and it does NOT make anything public — it uploads a build, nothing more. Never report an upload as having published anything, and never tell a creator their activity is "already published" because a build exists: to them that sentence means it is public, and it is not.
  • Uploading a build puts a PREVIEW of the activity on Joiner. Upload a preview as soon as the activity is worth trying — do not ask first. Work locally until it is implemented, tested, and visually checked, then upload it and hand the creator the launch link. There is no question to ask, because there is no longer a decision to make: a preview is always UNLISTED — reachable by its launch link, absent from public discovery — so it exposes nothing, and asking only delays the creator getting something they can open and play with. Never offer a visibility choice when you upload. An option labelled "publish publicly" promises something an upload cannot do; making it public is a separate command, never a question you ask, and only ever run on their request — see the next point. This is also why an activity that ALREADY HAS A PREVIEW needs no such question when you edit or update it, and why you must never offer to change a listed activity's visibility unprompted: asking a creator who opened a public activity whether to make it unlisted presents that as a routine choice, and it is not one.
  • Never ask about listing publicly. Say it in one sentence and let them come back to you. Being discoverable in Explore, search, and profiles is a separate decision from having a preview, and it is not one anybody can make well from a description of an activity they have not opened yet — so it is not a question to put to the creator at any point, and never a multiple-choice one. The expected next step after a preview is that they click the link, play, and tell you what they want; a question in the way of that link asks them to decide something they have no basis for yet, and stalls the loop the preview exists to start. So when you hand over a preview, close with one plain sentence naming both things they might want next — changes, or listing it publicly. For example: "Here is your activity. Try it out and let me know if you want any changes, or if you want to make it public for anyone to discover and play." Everything up to that point stays unlisted on its own, with no action needed from you.
  • When they DO ask to list it, that is the moment the activity gets its real name. The user names their activity; you only name previews. An unlisted activity carries the agent-chosen placeholder displayName (see the "Before the FIRST upload, fill in the listing-page fields yourself" rule in SKILL.md), and an activity must never enter public discovery under one — so listing is a two-step reply rather than one command. Propose a title they can accept or replace, set .poe-tile.json → displayName to the one they land on and handle to a slug of that same name (lowercase, digits and single hyphens — Texas Hold'emtexas-holdem), upload a build carrying the new name, then run poe-tiles tiles visibility <tileHandleOrId> public. Do not pin the live version yourself, and do not touch channels. The platform pins it after this turn's upload lands, so that the build you just made is the one players get; pinning here would freeze them on the build from before the creator asked you to publish. Proposing a name here is not the question the rule above forbids: they have already played the activity and already decided to list it, so the name is the only thing left to settle, and it is offered as a suggestion rather than a gate. If they volunteer a name at any point, use it and drop the placeholder. Set the handle only on this first naming. A scaffolded activity's handle carries a random suffix nobody has seen, so replacing it costs nothing; once an activity is listed its handle is in links people have shared, and renaming it breaks them silently. If the slug is already taken by another of their activities the rename is refused — say so, and use the closest free variant rather than leaving the placeholder.
  • Read an activity's visibility, never remember it. poe-tiles tiles get <tileHandleOrId> reports it — as Visibility, or the visibility field under --json — for an activity you own. Whether an activity is listed decides whether your changes reach real players, and it is changed from the platform UI as well as by poe-tiles tiles visibility, so a value you were told when the session started, or inferred from what you did earlier in it, can already be wrong. Query it at the moment you need it — before promoting, and before telling the creator who can see their activity. A response with no visibility field means you are not the owner — it does not mean unlisted.
  • Once an activity is listed, keep your edits off the people playing it. Pinning the live version is what does that: uploads still happen on every change and still give the creator something to open, but they record a TEST VERSION instead of moving what players run. The platform pins it for you, on the first upload that lands while the activity is listed — which is the listing turn itself, and any later turn if that pin ever failed. You do not run auto-advance at all: doing it yourself always pins the build from BEFORE your upload, which is how a listed activity ends up with players on the previous version. Every turn after the pin records a test version — an unpinned listed activity ships every half-built turn to real players. The test channel the preview link addresses is not yours to create either — the platform creates it on the activity's FIRST upload, long before any of this, and re-creates it on the next upload if that ever fails; it follows the newest version from then on. That is why the link you hand over is the same link from the first preview to the last, and why set-channel is not a command you run: pointing an existing channel at the version you have in hand aims it at the build from before your upload, which is the pinning mistake in another costume. A pinned live alone would leave that link on the build players already have, because a bare activity URL resolves through live. Say in one sentence that you have done it, and on each later change say that players are still on the live version, so they never have to remember which mode they are in. Pushing to players is theirs to ask for: when they do, run poe-tiles tiles promote <tileHandleOrId> <version>, and leave live pinned afterwards rather than restoring follow-latest, so the next edit is safe by default too. Never promote because the work looks finished.
  • Any time the user can try out a new state, upload it again, including while iterating: bun run publish-to-poe-tiles -- --change-summary "<concise user-facing summary>" from the app's directory. Uploading never sets visibility. An activity is unlisted when its first build goes up, and stays wherever it is on every upload after that — there is no --visibility flag, and a .poe-tile.json visibility key is not applied by uploading, so an upload can never move an activity in or out of discovery. To list an activity publicly (or take it back out), run poe-tiles tiles visibility <tileHandleOrId> <public|unlisted> — only on an explicit request, and never as a step in ordinary iteration. Unlisting removes a live activity from discovery and the user does not necessarily see it happen. Because a first build is unlisted, take the scaffold's fast path on it: after its authoritative test:all check passes and required first-upload metadata/profile art are ready, upload immediately before optional browser tooling or broader visual exploration. Continue the remaining task-appropriate verification afterward and upload again if it changes the app; this improves time to a usable preview without weakening the final source. Describe the meaningful changes in one short sentence; the Creator chat receipt includes it with the activity name and launch link. Report appUrl as a clickable markdown link.
  • Say what changed, in one line, every turn that changes the activity. Write it to .poe-tile-change-summary beside .poe-tile.json — plain text, one sentence, no markdown. The upload at the end of the turn reads that file and records it as the version's summary, then deletes it, so write a fresh one each turn. The creator reads these back in Version History when deciding what to publish to players, so write what a person would want to see there — "Made the tap target smaller and fixed the score not resetting", not "updated App.tsx" and not "various improvements". Skip the file on a turn that changed nothing; the version then records that it was an automatic preview, which is true. Do not describe work you did not finish, and do not use it to talk to the creator — it is a changelog entry, not a reply.
  • Know when players actually receive a published update, and say it accurately. A newly published version is live immediately, but players are not all switched over at once: someone who has opened the activity on that phone before gets the previous version for one more open (the new one downloads in the background), and someone with the activity open on screen gets no automatic prompt — they must tap Settings > Reload activity once their session notices the new version, or leave and reopen. An activity never swaps versions out from under a player mid-play. Read publish-rollout.md before telling the user "everyone has it now", and before debugging a report that a published change "didn't take effect".
  • When an upload prints a Multiplayer simulation link, hand it to the user as a clickable markdown link and say what it does. The platform adds that row whenever the activity's declared players range makes it worth testing with more than one person, and clicking it opens a multiplayer simulation directly: several dev-user phones side by side in one page, all already in the activity, driven by the user — no need to recruit real players to see whether the multiplayer half actually works. Say it in one short sentence, mentioning the phone count — e.g. "Test with 4 players → multiplayer simulation — four dev users side by side in this activity", with "multiplayer simulation" linking to the printed URL. Use the URL exactly as the CLI gave it; never hand-assemble or edit one. The same link is posted into the Creator chat automatically, so this is a convenience, not the only path — but a user reading the terminal should not have to go looking for it. When the row is ABSENT on an activity you believe is multiplayer, that is a signal .poe-tile.json → players is wrong or missing: fix the declaration and upload again rather than inventing a link.
  • bun run publish-to-poe-tiles runs bun run build && poe-tiles tiles publish. Despite the name, that command only uploads a build — it takes no visibility input at all, there is no flag and no manifest key, so it can never move an activity in or out of discovery; poe-tiles tiles visibility is the only thing that does. The CLI reads the listing metadata (including players, categories, profilePicture, and screenshots), runtimeBundle.dir, and sourceBundle from .poe-tile.json in the current directory. Override any field with the corresponding flag (--handle, --dir, etc.) for one-off uploads; explicit flags always win over config.