2026 10 22 town hall site powers design - xopherdeep/do-it-for-the-xp GitHub Wiki
Date: 2026-10-22. Status: design approved in chat by the owner, section by section (2026-10-21/22); this spec awaits review.
The Town Hall reads doit.forthexp.com (News, Patch Notes, the boards, comments) and heroes send topics, comments and bug reports to it, where they wait as pending. Everything after that happens in wp-admin: publishing a family's topic, approving a held comment, writing the news and the patch notes, reading a bug report. The owner, who is an administrator of the site, wants to do those jobs from inside the game as well, on the phone, in the Town Hall, even though no one but the owner will ever see the tools.
Success: with the owner's family linked on a phone, every pending thing on the site can be published or binned from the Town Hall, a news post or a patch note can be written and published there, and the tools never show for any other family's link.
In: site powers read from the account's capabilities; drafts, pending topics, held comments and new bug reports shown in place in the Town Hall with Publish, Approve, Resolve, Edit and Bin; a news writer in the compose sheet; a count of what waits on the clerk's button; a topic approved on a device that can publish goes up published.
Out (see section 9): featured images and rich text, scheduling, spam, hard deletes, editing comments, a Desk tab, a plugin or site change of any kind, the Game Master's local approval queue (unchanged), the multiplayer road.
- Both jobs, equally: clearing the pending queue and writing News and Patch Notes.
- In place, on the cards. Pending items sit where they belong (top of their board, under their post, top of News) with their buttons on the card. No sixth tab, nothing in the Admin console.
- Plain WordPress REST through the link the family already has (approach A). No plugin route, no new backend, no wp-admin in a web view.
- Publish on approval. On a device whose link can publish, the Game Master's approval is the review: the topic is sent published. Other families' links keep sending pending topics for the owner to publish in the game.
- The site decides who is an admin, not the game: the linked account's capabilities, read from the site. The Game Master role means nothing here.
The link check (checkAndStore) already reads GET wp/v2/users/me?context=edit for the display name. The same
answer carries capabilities, a map of capability name to true. A pure function in a new module,
src/lib/services/townhall/townHallDesk.ts, turns it into the powers the Town Hall cares about:
export interface SitePowers {
/** Publish, edit and bin posts and topics, including other people's: `publish_posts` and `edit_others_posts`. */
publish: boolean;
/** Approve and bin comments: `moderate_comments`. */
moderate: boolean;
/** When the site said so (ms). */
readAt: number;
}
export function powersOf(me: { capabilities?: Record<string, unknown> } | null | undefined, now: number): SitePowers;An administrator or editor has both; an author has neither (edit_others_posts is missing); a Contributor (every
other family) has neither. The two booleans are all the screens read.
-
Stored on the link.
TownHallLinkgainspowers?: SitePowers, written at link time. An older stored link withoutpowersreads as no powers until the first refresh.TownHallDbneeds no migration: the link is saved whole. -
Refreshed on each Town Hall visit. The store gains
refreshPowers(): one read ofusers/me?context=edit, best effort. A good answer replaces the stored powers (and saves the link); a failure of any kind keeps the stored ones and logs. The clerk composable calls it on mount, afterinit(). - Hosted inside the site (nonce auth). The same read runs on mount and the powers are held in memory only (there is no link to save them on). So an administrator opening the app inside doit.forthexp.com gets the tools too.
-
Dropped on a refusal.
dropPowers()sets both false and saves. Section 6 says when. -
Exposed as
store.powers(aSitePowerswith both false when unknown) andstore.canPublish/store.canModeratecomputeds. The Game Master's approvals page and everything else in the game ignore them.
Every reader below runs only when the matching power is true; without it the tabs behave exactly as today. All reads
go through townHallFetch with store.auth, the same as the feed. townHallFeed.ts gains the readers; the request
builders for actions live in townHallDesk.ts (section 4); a new composable
src/app/Console/MyPortal/HomeTown/TownHall/components/useTownHallDesk.ts holds the busy flags, the counts and the
calls, so the tab components stay thin.
Chips (ion-chip in the card header, the class names is-draft, is-pending, is-held, is-new): Draft,
Waiting, Held, New. The chip names the state in the clerk's words; the buttons sit in the card's footer row, the same
row the hero's Comment and Reply buttons use, so nothing moves for a hero.
-
Drafts first.
GET wp/v2/posts?status=draft,pending&context=edit&_embed=true&per_page=5&categories_exclude={patch}renders above the published news with the Draft chip. Buttons: Publish, Edit, Bin. - Published cards get Edit and Bin after the existing buttons.
- Held comments. See Comments below: the card's comments button shows "(2 held)" when the post has any.
- The same as News for posts in the Patch Notes category (
categories={patch}instead ofcategories_exclude). While the site has no Patch Notes category the drafts reader is skipped, as the notes reader is today. -
Bug Net card, between the notes and Game Statistics, for a link that can publish:
GET wp/v2/compass_bug?status=pending,publish,draft&context=edit&per_page=10&orderby=date&order=desc, filtered on the device tobug_statusnot inresolved,closed. Each bug shows its title, date, the New chip (or the status it has), the route and the first lines of the environment. Buttons: Resolve (setsbug_status: "resolved"through Bug Net's REST field), Bin.
-
Pending topics first.
GET wp/v2/cafeteria_topic?status=pending&context=edit&cafeteria_board={id}&_embed=true&per_page=10renders above the published topics of the board being looked at, with the Waiting chip. Buttons: Publish, Edit, Bin. - Published topics get Edit and Bin after Reply.
- The hero's own waiting posts (
TownHallWaiting, the local outbox) stay where they are, above everything.
- A moderator's comments reader asks for everything:
GET wp/v2/comments?post={id}&status=all&context=edit&per_page=50&order=asc. Held ones carry the Held chip and the buttons Approve and Bin; approved ones look as today. - To find them without opening every post, the desk composable reads the held comments once per visit:
GET wp/v2/comments?status=hold&context=edit&per_page=20and keeps them bypostid. A News card or a topic card whose id has held comments shows the held count on its comments button. A held comment on a post that is not on the first page of its tab is reached by scrolling, as any post is; this is accepted.
The clerk's ion-fab (bottom centre, the building FAB) gets a badge with the number of things waiting, so "is anything
waiting?" is answered on arrival without opening the tabs: drafts (news and patch notes together) + pending topics
(all boards) + held comments + bug reports not resolved. The desk composable reads them once per visit, all best
effort (a failed read counts nothing and shows nothing):
- drafts: the
X-WP-Totalheader ofGET wp/v2/posts?status=draft,pending&context=edit&per_page=1; - pending topics: the header of
GET wp/v2/cafeteria_topic?status=pending&context=edit&per_page=1; - held comments: the header of the held-comments read above (which the cards share);
- bugs: the Bug Net card's read above (which the card shares), filtered on the device, so at most ten count. A header
total would count resolved bugs too, since Resolve changes only
bug_status.
The badge vanishes at zero. The per-tab chips appear as each tab loads its items.
The compose sheet (TownHallCompose.vue) gains a kind, news, for a link that can publish:
-
Fields: Title (required, 100 characters) and the body (plain text,
NEWS_BODY_MAX = 8000characters). WordPress renders the paragraphs. Site news carries no hero signature. - Bottom bar, where the board picker sits for a topic: a two-way picker, News | Patch Notes, and two sends, Publish (primary) and Draft. Patch Notes is offered only while the site has that category; otherwise the picker is hidden and the post is news.
-
Opened from the clerk.
clerkMenuChoicesgains, for a link that can publish, Write news and Write a patch note (the FAB menu is where the hero's Post on the board lives; there is no separate Write button). The choices carry apenicon and sit after the hero's choices. -
Editing. Edit on a draft, a post, a topic or a patch note reads the item alone in edit context
(
GET wp/v2/{type}/{id}?context=edit) fortitle.rawandcontent.raw, then opens the same sheet with those words, the heading Edit, the board or category picker hidden (neither changes on edit), and one send, Save. Editing a hero's topic keeps the signature line, which is ordinary text in the body. A failed read says the courier is late and opens nothing. -
Limits and problems go through
draftProblem, extended fornews(title required, 8,000 characters). - Comments are not edited: Approve or Bin.
townHallDesk.ts builds every admin request, pure, in the shape requestFor already uses ({ path, body } plus a
method), with one spec pinning each:
| Action | Request |
|---|---|
| Publish a draft or a pending topic |
POST wp/v2/{posts|cafeteria_topic}/{id} { status: "publish" }
|
| Create news |
POST wp/v2/posts { title, content, status: "publish" | "draft", categories: [patchId]? }
|
| Save an edit |
POST wp/v2/{posts|cafeteria_topic}/{id} { title, content }
|
| Bin a post or topic |
DELETE wp/v2/{posts|cafeteria_topic}/{id} (the site's trash, never force) |
| Approve a comment |
POST wp/v2/comments/{id} { status: "approved" }
|
| Bin a comment |
DELETE wp/v2/comments/{id} (trash) |
| Resolve a bug |
POST wp/v2/compass_bug/{id} { bug_status: "resolved" }
|
| Bin a bug | DELETE wp/v2/compass_bug/{id} |
Admin actions are not queued: each is one call through townHallFetch, the card is busy while it waits, and on
success the item changes in place (a published draft moves down into the list, a binned item leaves, an approved
comment loses its chip, the FAB count drops by one). Nothing is written to the outbox or the Town Hall database.
Bin asks once. Binning anything published or approved asks the clerk's question first (reveal prompt: "Take it down? It goes to the city's bin; the site can bring it back for thirty days."), with Take it down and Leave it. Binning a draft, a pending topic, a held comment or a bug does not ask. Publish, Approve, Resolve and Save never ask.
Publish on approval. requestFor(post, boardIds, options) gains { canPublish: boolean }: a topic is sent with
status: "publish" when canPublish is true, "pending" otherwise. Comments and bug reports are unchanged (the site
already approves a moderator's comments on arrival; bugs are never published). flush() passes store.canPublish.
postedOutcome gains the outcome published (a topic sent by a device that can publish) and postedNotice the line
"It's up on the wall already."; the hero whose post waits for the Game Master still hears the same as today.
- The tools speak in the clerk's classic voice only: the owner is an adult, and no one else sees them. No simple or adventure variants in the catalogue for these lines.
- Buttons use the Town Hall accent and the Earthbound box; icons from the duotone set (
uploadPublish,checkApprove,penEdit,trashBin,flag-checkeredResolve). Every button does something real. - Nothing in the bottom nav, the segment or the board chips changes. The FAB badge is the one new thing in the footer area.
| Reply | What happens |
|---|---|
| 401 on any call | As today: the link is forgotten, the clerk asks the family to register again |
| 403 on an admin action or reader | The site took the keys back: dropPowers(), the tools and chips vanish, the clerk says "The city has taken back your keys. Register again if that's a mistake." Not retried |
| Offline, 5xx, timeout | The buttons stay but a tap gets the courier-late prompt (retry or never mind); nothing is queued. Readers show nothing extra, never an error card |
| 400 (the site refused the text) | The site's message shows on the card, as a hero's failed post does |
refreshPowers() fails |
The stored powers stand; a failed refresh never hides the tools by itself |
| The edit read fails | Courier late; the sheet does not open |
Inside the building these are reveal prompts, never Ionic alerts.
-
Pure specs (
tests/unit/):townHallDesk.spec.ts(powers from capabilities: admin, editor, author, contributor, no capabilities; each request in the table; the readers' queries; the count sum),townHallPosts.spec.ts(publish on approval; the news draft problems),townHallFeed.spec.ts(drafts, pending topics, held comments, bugs readers; the moderator's comments query),townHallOutbox.spec.ts(thepublishedoutcome),townHallStore.spec.ts(powers stored at link time, refreshed on open, kept on a failed refresh, dropped on 403, nonce-hosted powers in memory only). -
Component specs:
TownHallNews,TownHallUpdates,TownHallBoard, the comments block andTownHallComposeshow the tools only with the power, emit the action with the id, show the chips, and the news kind's picker and two sends;useTownHallDesk.spec.tsfor busy flags, in-place changes and the 403 drop. -
Cypress (
tests/e2e/specs/town-hall.spec.js): an admin run where theusers/me?context=editstub answers with capabilities, the drafts, pending topics, held comments and bugs stubs answer, the chips and the FAB badge show, Publish hits the stubbed write, and the same page with a Contributor's answer shows none of it. Writes stay stubbed (the spec already refuses unstubbed writes with 599). -
Rehearsal on the local Docker WordPress (
http://localhost, Bulletin Board and Bug Net are active there) with the local admin's link through the development site override, after making the Patch Notes category there. Then doit.forthexp.com with the owner's go-ahead. No site change is needed for the feature.
A feature branch (feat/town-hall-site-powers), a PR opened with auto-merge, the Tests check as the gate. Board tasks
from the implementation plan (writing-plans next), in the order: desk module and feed readers; store powers; the
outbox change; the compose sheet; the tabs; the FAB count; Cypress and rehearsal.
Files touched: src/lib/services/townhall/townHallDesk.ts (new), townHallTypes.ts, townHallFeed.ts,
townHallPosts.ts, townHallOutbox.ts, src/lib/store/stores/townHall.ts,
src/lib/services/hometown/hometownPrompts.ts, components/useTownHallDesk.ts (new), useTownHallPosting.ts,
useTownHallClerk.ts, TownHallCompose.vue, TownHallNews.vue, TownHallUpdates.vue, TownHallBoard.vue,
TownHall.vue/.ts, the specs above.
- Featured images, rich text, scheduling: edit on the site (the open-on-the-site link stays on every card).
- Marking spam, hard deletes, restoring from the trash, editing comments, replying as the site (the owner's comments already go through the hero flow and are approved on arrival).
- A second page of drafts or of held comments; a Desk tab; counts per tab in the segment.
- Other administrators: the tools show for any link whose account has the powers, which today is the owner alone.
- The game reporting into
xp/v1(the flagship audit) is a separate piece of work.