2026 10 15 account settings design - xopherdeep/do-it-for-the-xp GitHub Wiki
Status: approved in conversation, 2026-10-15. Owner: "rn since we link the account via townhall. maybe this is the more
'through the app menu' way to do it? i dont have settings to link the google/apple accounts yet so we can put that on
the roadmap." Parked page: Settings, Account (Coming soon, commit d97779a7); the develop branch's original is kept at
tools/reference/develop/AccountSettings.vue.
The family's account is the Game Master's Town Hall link: one WordPress account on doit.forthexp.com, linked through
WordPress's authorize-application page and stored on the device as login, display name and an Application Password
(TownHallLink, src/lib/store/stores/townHall.ts). Heroes have no accounts (COPPA decision, town-hall-wp-bridge spec).
The plan is the XP Pro licence (src/lib/store/stores/pro.ts). There is no other sign-in and no new backend.
Settings, Account is the menu's way to the same link. The Town Hall page keeps its clerk conversation (that is where a
hero bumps into needing it); both call one composable, so the flow has one implementation.
Today the flow lives inside src/app/Console/MyPortal/HomeTown/TownHall/TownHall.ts (goToAuthorizePage,
registerFamily, finishLinkReturn, announceLink, the native lastLinkResult watch). It moves to
src/app/Console/MyPortal/HomeTown/TownHall/components/useTownHallLink.ts with this shape:
useTownHallLink({
store, // useTownHallStore()
heroId: () => string, // whose state is remembered (the hero on screen, or the current user in Settings)
returnRoute: () => string, // "/town-hall/<id>" or "/xp-settings/account": where WordPress sends the web parent back
isGameMaster: () => boolean,
announce: (result: LinkResult) => Promise<void>, // the page's own voice: the clerk's prompt, or a settings toast
}) => {
loadLink(): Promise<void>; // store.init, errors logged
goToAuthorizePage(): Promise<void>; // beginLink, authorizeUrl, web navigates / native opens the Browser
unlink(): Promise<UnlinkResult>;
finishReturn(): Promise<boolean>; // takeLinkReturn + completeLink + announce; false on an ordinary visit (web)
// native: watches store.lastLinkResult and announces it (the page need not)
}-
townHallReturnUrl(userId, loc, hash)intownHallLink.tsbecomesreturnUrlFor(route, loc, hash)(same output for/town-hall/<id>); the boot capture (pendingReturn.ts) is already route-agnostic and keeps working. - The native return (
main.ts→completeLink→lastLinkResult) is unchanged; the composable's watch replaces the page's. - The Town Hall keeps
registerFamily's clerk prompts andofferRegistration; it only calls the composable underneath. Its behaviour does not change, and the existing specs (townHallLink, townHallPendingReturn, townHallStore, townHallRegistrationOffer) stay green. New spec:tests/unit/useTownHallLink.spec.ts(web return announced once, an ordinary visit announces nothing, native result announced, authorize URL carries the page's return route).
-
townHallStore.fetchProfile(): Promise<TownHallProfile | null>readswp/v2/users/me?context=editwith the link's auth and returns{ login, displayName, email }; null when not linked or when the site refuses (nothing is stored, the page just shows what the link already holds). Spec intownHallStore.spec.ts. -
townHallSite.tsgainsprofileUrl(site)→<site>/wp-admin/profile.phpandpasswordResetUrl(site)→<site>/wp-login.php?action=lostpassword. Email and password change on the site: WordPress confirms an email change by mail and a new password does not touch the Application Password, so the app never edits them.
A hero sees one card: "Only a Game Master manages the family's account." (canManageFamily(), as Chores does).
-
Family account card.
- Linked: display name as the title, "Signed in as
<login>", the email fromfetchProfile(a dash while loading or when the site did not answer). Buttons in thumb reach, outline style like XP Pro's: Edit on the site (profileUrl), Change password (passwordResetUrl), bothwindow.open(..., "_blank", "noopener")with the line "You'll sign in on the site."; Unlink this device (confirm, thenunlink(); the result as anrpg-toast, theremoved-here-onlycase naming the site like the clerk does). - Not linked: "Link your For the XP account to post for your family from any device." Link account
(
goToAuthorizePage) and Create one (SIGNUP_URL). WhensignedInThroughWebsite, the first line says so. - On mount:
loadLink(), thenfinishReturn(); a result shows as anrpg-toast(middle, close button).
- Linked: display name as the title, "Signed in as
-
Plan card: "XP Pro is on,
<plan label>" or "XP Pro is off", one button XP Pro →/xp-settings/pro.PLAN_NAMESmoves to where both pages can import it (src/lib/services/pro/), no copy. -
Sign-in options card:
XpComingSoonCardwithACCOUNT_COMING_SOONreduced to Google and Apple.
Tile (XpSettingsHome.ts): soon off, description "The family's Town Hall account, plan and sign-in options."
Help (helpContent.ts): the data chapter's line and the FAQ entry say what the page does now (link, unlink, where to
change email and password). AccountSettingsSoon.spec.ts becomes AccountSettings.spec.ts and checks the route, the
reduced planned list and the help lines.
A site job: a social login plugin on the network (owner-installed, rehearsed on the local Docker WP first) shows Google and Apple on the sign-in and authorize pages; Apple also needs an Apple Developer account and Sign in with Apple set up. The app's flow then uses them for free; the page lists them as linked sign-ins. No app-side OAuth.
- AC1 the composable (section 2) and AC2 the store and site helpers (section 3), in parallel.
- AC3 the page, tile, help, spec (section 4).
- Review, then one typecheck + lint + the touched specs; push and read the PR's Tests check.