task 11 report - xopherdeep/do-it-for-the-xp GitHub Wiki
Implementer @sonnet-pt11. Status: DONE_WITH_CONCERNS (see Concerns). No git writes. Audit: chemx audit at the worktree root reads gate: pass (ratchet) (322 hazards, the same ratchet the lead set; the two hazards the lead named, CONTROL_FLOW_INLINE_BOOLEAN in credits.ts and COMPLEXITY_CYCLOMATIC_HIGH in XpApprovals.vue, are fixed).
Owner ruling 2026-10-27 applied: the credits drop the ages. Maker is { name, role }; there is no birth year, no makerAge, and no spec, fixture or snapshot mentions an age or a birth year (I grepped my new files for birth, age, aged, makerAge: nothing).
Pure rules (all with specs):
-
src/lib/services/copy/credits.ts(new):Maker,MAKERS(the six makers, role and name, in the brief's order; the one place to trim),HeroCredit,heroCredits(project, quests, paid, heroes),bossQuestIds(project). -
src/lib/services/projects/projectFinish.ts(new):daysEarly,trophiesOf(cleared runs, newest first),isRewardToGive/rewardToGive,finishStepFor(project, heroId)("reward", then "credits", then null),creditHeroesOf(profiles),creditsViewOf,trophyOf(the trophy sheet's view of a run: dates, days early, reward, each floor's multiplier, x1 when none),towerNameOf. -
src/lib/services/projects/finishPrompts.ts(new):rewardRevealPrompt(project, voice)(Tower Keeper, "Family reward unlocked!", the tower, the family's own words verbatim, who hands it over, one Onward). -
src/lib/services/copy/catalog/projectFinish.ts(new, registered incatalog/index.ts): reward reveal, credits headings, trophy sheet, Approvals row, trophy spot cue; simple/adventure/classic where the voices differ, no em dashes, the makers' names and roles are data in credits.ts. -
projectService.ts:readTrophies(),readFinish(projectId?)(the cleared run with the quests and only the PAID log entries),readRewardToGive();markRewardGivennow uses the sharedrewardToGiverule (same behaviour).
Screens and wiring (thin):
-
src/components/organisms/CreditsRoll/CreditsRoll.vue(new): full-screen CSS scroll inStatusPlz, cream text on the theme window (--gs-*tokens only, no hex), "The Heroes of " with Started / Finished and "N days early!", each hero (name the class, Chores, Bosses, Floors), then "Do It For The XP" with each maker's role and name, then "Thank you for playing!"; Skip at the bottom (thumb reach);doneemitted once (scroll end or skip);prefers-reduced-motionshows it static. BGM untouched. -
src/app/Console/MyPortal/HomeTown/components/ProjectTrophySheet.vue+_ProjectTrophySheet.scss(new): cleared runs newest first (a tap picks one), "Watch the credits" (everyone), "Start again" (Game Master only), actions in the bottom bar. -
HomeTown.ts/.vue: the spot now stands for the running project, else the newest cleared run (the trophy:standingProjectOfintowerSpot.ts, cue "Cleared! See the trophy"), else nothing; a tap opens the tower sheet or the trophy sheet; "Start again" closes the trophy and opensXpProjectWizardModalon the run's type; the plaza looks again when the wizard closes.ProjectTowerSheet.vueis untouched (the trophy opens from the spot, not through itsemptyslot). -
XpProjectWizardModal.vue: one optional proptypeId; when given, the draft opens on that type withlastOfType's chores (the existing rerun path), so "Start again" starts from last time's list. -
src/hooks/useProjectFinish.ts(new) +MyPortal.ts/.vue(+4 lines, +3 template lines; MyPortal.ts 319 to 329): on portal open and on each route change, a hero owed the reward gets the reveal prompt once, then the credits once. The hero ismarkSeenBEFORE it shows (a second look cannot show it twice; if markSeen fails nothing shows). Held back while the hero is in a temple, in a fight, or played by a Game Master; also waits for the profiles to load. -
XpApprovals.vue+ApprovalFamilyRewardRow.vue(new): the row at the top of the achievements tab, "Family reward earned: " with "Mark as given" (callsmarkRewardGiven, then the page reads again); the segment badge counts it.dashboardTiles.approvalsTotal(chores, rewards, familyReward = 0)counts an earned, ungiven reward as one;XpDashboard.tsfeeds it.
bossQuestIds(project) reads the project record's own chore list (project.chores, the list the tower was built from), not the temple record or the live quests: for each phase (the roof included) the biggest chore by difficulty, the first on a tie, as quest id projectQuestId(project.id, chore.id). That is exactly how projectTower.ts picks the boss of each floor (biggestOf: the floor boss, the last phase's key guard) and the roof's single finale. Reasons: heroCredits has no temple argument, the type's temple is replaced by a re-run (so an old trophy's tower is gone), and a Game Master deleting a quest must not hand the boss to another chore. Drift guard: credits.spec.ts builds real towers (both types, three room sets, two families = 8) and checks the ids equal the phase biggest chores plus the finale, that each is held ALONE in a difficulty: "boss" room of the real tower, that exactly one is the sealed miniboss (and sits on the last phase's floor) and exactly one the boss finale.
RED (spec written first; command NODE_PATH=$PWD/node_modules/.pnpm/node_modules chemx test tests/unit/<name>.spec.ts):
-
credits.spec.ts:Test suite failed to run ... credits.spec.ts:8:68 import { MAKERS, bossQuestIds, heroCredits ... } from "@/lib/services/copy/credits"(module missing). -
projectFinish.spec.ts:Test suite failed to run ... Cannot find module '@/lib/services/projects/finishPrompts'. -
projectServiceFinish.spec.ts(+ therewardToGivecases inprojectFinish.spec.ts):TS2305 ... has no exported member 'readRewardToGive'. -
XpDashboard.spec.ts:TS2554: Expected 2 arguments, but got 3(approvalsTotal(2, 3, 1)). -
ApprovalFamilyRewardRow.spec.ts:Could not locate module @/app/Admin/XpApprovals/components/ApprovalFamilyRewardRow.vue. -
ProjectTrophySheet.spec.ts:Could not locate module .../HomeTown/components/ProjectTrophySheet.vue. -
towerSpot.spec.ts+HomeTown.spec.ts+XpProjectWizardModal.spec.tstogether:5 failed out of 31(standingProjectOfnot exported;wrapper.vm.openTower is not a function; the wizard's new typeId case). -
CreditsRoll.spec.tsanduseProjectFinish.spec.ts: written before their modules, but I did not run them until the implementation existed (first run GREEN), so there is no RED run. Instead a mutation check: removing the once-only guard inCreditsRoll.vueand movingmarkSeenafter the show inuseProjectFinish.tsmade4 failed out of 27(says done once, however it ends: Expected length 1, Received 3;remembers the hero was told before the prompt shows;shows nothing when the hero could not be remembered;roll once the reward was answered); reverted, green again.
GREEN, one combined run of every spec named in the dispatch plus the neighbours that load files I changed:
chemx test tests/unit/credits.spec.ts tests/unit/CreditsRoll.spec.ts tests/unit/ApprovalFamilyRewardRow.spec.ts tests/unit/projectFinish.spec.ts tests/unit/projectServiceFinish.spec.ts tests/unit/ProjectTrophySheet.spec.ts tests/unit/useProjectFinish.spec.ts tests/unit/ProjectTowerSheet.spec.ts tests/unit/towerSpot.spec.ts tests/unit/HomeTown.spec.ts tests/unit/XpDashboard.spec.ts tests/unit/XpProjectWizardModal.spec.ts tests/unit/voices.spec.ts tests/unit/HexLiteralRatchet.spec.ts tests/unit/XpApprovals.spec.ts tests/unit/projectService.spec.ts
-> All tests passed (276 passed in 46507ms). (voices.spec covers the new catalogue file; HexLiteralRatchet covers the new .vue/.scss.)
New: src/lib/services/copy/credits.ts, src/lib/services/copy/catalog/projectFinish.ts, src/lib/services/projects/projectFinish.ts, src/lib/services/projects/finishPrompts.ts, src/hooks/useProjectFinish.ts, src/components/organisms/CreditsRoll/CreditsRoll.vue, src/app/Console/MyPortal/HomeTown/components/ProjectTrophySheet.vue, .../_ProjectTrophySheet.scss, src/app/Admin/XpApprovals/components/ApprovalFamilyRewardRow.vue; specs tests/unit/{credits,CreditsRoll,ApprovalFamilyRewardRow,projectFinish,projectServiceFinish,ProjectTrophySheet,useProjectFinish}.spec.ts.
Modified: src/lib/services/copy/catalog/index.ts, src/lib/services/projects/projectService.ts, src/app/Admin/XpApprovals/XpApprovals.vue, src/app/Admin/XpDashboard/dashboardTiles.ts, src/app/Admin/XpDashboard/XpDashboard.ts, src/app/Console/MyPortal/MyPortal.ts, src/app/Console/MyPortal/MyPortal.vue, src/app/Console/MyPortal/HomeTown/HomeTown.ts, .../HomeTown.vue, .../components/towerSpot.ts, src/app/SideMenu/XpSettings/components/XpProjectWizardModal.vue; specs tests/unit/{XpDashboard,towerSpot,HomeTown,XpProjectWizardModal}.spec.ts.
Not mine: chemx-ratchet.json shows modified (the local chemx rewrote it, as the lead said).
- Rules in pure modules with specs; screens and MyPortal.ts stay thin (portal-open logic is the composable). None of the files the lead listed as under parallel repair (
floorBonus,projectChores,projectSchedule,projectTower,projectQuests,ProjectDb, ...) is edited; I only import fromprojectQuests(projectQuestId). - Every condition before an
ifis named; no switch; no passthrough booleans. Largest new file: ProjectTrophySheet.vue (~190 lines). XpApprovals.vue stayed under complexity 12 by moving the reward read intoreadFamilyReward(zero new branches inloadApprovals). - Tokens only: no hex literal in any new .vue/.scss (
--gs-*for the roll,--xp-app-*for the sheets/rows); HexLiteralRatchet green. No em dashes in the new copy. - Controls at the bottom (Skip, Watch the credits, Start again); the cleared tower is a sheet, not a walk.
- I could not run
chemx typecheck(rule). I read the.vuescripts for types by hand; the.tsfiles were type-checked by ts-jest diagnostics when their specs ran. Not looked at in a browser (no dev server). - A few edits were made through short python scripts in Bash before I saw the chemx hook note (spec edits,
catalog/index.ts,projectService.ts,dashboardTiles.ts,XpDashboard.ts, the first XpApprovals.vue pass, onecat >>append totowerSpot.spec.ts); after the note I used the Edit/Write tools. The hook also namedwidgetCatalog.ts,widgetSyncTrigger.ts,ThemeSettings.vueanduser/persistence.ts: those are not mine (peer writes).
-
Team chores (ruling 1 wording). The lead's text says a team finale counts "for every assignee who has a paid entry". A Team Effort (
collaborate) chore pays ONLY the hero who did it (QuestSchedule: "one assignee's clear fills the window for every assignee, and only the hero who did it is paid"), so that reading would credit one hero for the family's finale. I followed the brief's "a team finale counts for every assignee": a paidcollaboratequest counts for every assignee of the quest (any team chore, not only the finale); anyone with a paid entry on it counts too. One small function (creditedHeroesin credits.ts) to change if the owner wants the narrow reading.competequests are not treated as team chores. - Heroes with nothing cleared are left out of the roll (and anyone no longer in the family's list). "The Heroes of ..." lists who did the work; a "0 chores" line seemed unkind. Say so if the roll should list everyone.
- "Start again" is Game Master only (like Move the end date and Abandon on the tower sheet): the wizard picks chores and heroes. The brief did not say; hero kids see only "Watch the credits".
-
The reveal and the credits follow the NEWEST cleared run (that is what
markSeenwrites to). A hero whose profile is created after a run was cleared will see that run's reveal and credits once; the trophy lets anyone rewatch. A cutoff by age of the run is a one-line addition infinishStepForif wanted. -
credits.tsholds both the roster (the one data file) andheroCredits, as the brief lists them together; if the owner wants the roster alone in a file,heroCredits/bossQuestIdsmove toprojectFinish.tswithout other changes. Since the ages are gone, the lead can commitcredits.tswith the rest. -
XpProjectWizardModal.vue(Task 9's file) gained the optionaltypeIdprop (3 lines + its spec case). -
HomeTownnow statically imports the wizard modal; it is only mounted open on demand, so the cost is bundle size only.
Status: DONE. Base: 55b7b584. Three items, all fixed; no git writes. (This report is appended with a shell heredoc; the code edits were made with the Edit/Write tools.)
- New
src/lib/services/projects/projectOrder.ts:newestFirstBy(stamp)(newest first by a stamp, a tie to the later id) andnewestClearedFirst(byclearedAt, a missing one last). It imports only a type. -
projectProgress.ts(newestCleared, which markSeen and markRewardGiven use),projectFinish.ts(trophiesOf, which the reveal hook reads[0]of) andProjectDb.ts(newestBy, behindgetActiveandlastOfType) now all sort with it; the three copies ofcompareTextare gone. - Deviation from the dispatch, with the reason: I put the comparator in a small module of its own rather than exporting it from
projectProgress.ts.ProjectDb.tsis imported byBackupService.ts, andprojectProgress.tsimportspackCommon, which importsBackupService: importingprojectProgressfrom the store would close a cycle (BackupService, ProjectDb, projectProgress, packCommon, BackupService).projectOrder.tshas no runtime imports, so it cannot. - Specs: new
tests/unit/projectOrder.spec.ts(the comparator itself, RED first:Test suite failed to run ... projectOrder.spec.ts:6:51 import { newestClearedFirst, newestFirstBy } ...);projectFinish.spec.ts"starts with the run newestCleared picks (the one markSeen stamps), over a tie and a run with no clear time, in any input order" (three input orders, the whole list orderrun-b, run-a, run-old, run-undated, and an all-undated pair);ProjectDb.spec.ts"picks the very run newestCleared picks over a tie and an undated run, whatever order they were saved in". The last two passed before the change too (the three copies were identical): they are the guard that keeps them from drifting apart again.
-
ApprovalFamilyRewardRow.vue:isSavingis no longer reset after a successfulmarkRewardGiven; it resets only on failure. The row stays disabled until the page reads again. - A consequence the lead's suggestion alone would have missed: with two ungiven rewards the page re-reads and the SAME row instance would show the next run's reward, still disabled for good.
XpApprovals.vuenow keys the row by the run (:key="familyReward.id"), so the next reward gets a fresh, enabled row (a one-attribute change in the template; the page itself has no unit spec). - Specs (RED first:
expect(received).toBeDefined() ... Received: undefinedon the disabled attribute): "stays shut after it saved: a quick second tap cannot hand over the next run's reward before the page reads again" (disabled,markRewardGivencalled once,givenemitted once) and "opens again only when it did not save".
-
useProjectFinish.ts: the loop checksisHeld()again at every moment, beforemarkSeen(so a held credits moment is NOT marked as seen):const hasNothingToTell = !owed || isGone || isHeld();. The existing watcher onbattleStore.activegoing false runslook()again, so the credits come when the fight is over. The reward prompt already on screen when the fight begins stays (it cannot be taken back); only the full-screen roll is held. - Spec (RED first:
expect(received).toBeNull() ... Received: {"daysEarly":2,... "towerName":"The Spring Spire"}): "does not roll the credits over a fight that began while the reward showed, and rolls them when it is over" (the fight starts under the prompt, the prompt is answered: no credits, nomarkSeen(.., "credits"), noreadFinish; the fight ends: credits roll and the hero is marked).
RED (before the changes): chemx test tests/unit/projectOrder.spec.ts tests/unit/projectFinish.spec.ts tests/unit/ProjectDb.spec.ts tests/unit/ApprovalFamilyRewardRow.spec.ts tests/unit/useProjectFinish.spec.ts -> 2 failed out of 62 plus the new suite failing to run.
GREEN: NODE_PATH=$PWD/node_modules/.pnpm/node_modules CHEMX_AGENT_ID=@sonnet-pt11 chemx test tests/unit/projectOrder.spec.ts tests/unit/projectFinish.spec.ts tests/unit/ProjectDb.spec.ts tests/unit/ApprovalFamilyRewardRow.spec.ts tests/unit/useProjectFinish.spec.ts tests/unit/projectProgress.spec.ts tests/unit/projectServiceFinish.spec.ts tests/unit/ProjectTrophySheet.spec.ts
-> All tests passed (148 passed in 73489ms).
Neighbours that load the changed files: chemx test tests/unit/projectService.spec.ts tests/unit/XpProjectWizardModal.spec.ts tests/unit/HomeTown.spec.ts tests/unit/credits.spec.ts -> All tests passed (118 passed in 61872ms). (ProjectDb.spec.ts imports BackupService, and passed with the store now importing projectOrder: no cycle.)
Audit: chemx audit at the worktree root -> chemx audit .: grade A+ (99/100), 322 hazards: 0 critical, 251 high/medium, 71 low and gate: pass (ratchet).
New: src/lib/services/projects/projectOrder.ts, tests/unit/projectOrder.spec.ts.
Modified: src/lib/services/projects/projectProgress.ts, src/lib/services/projects/projectFinish.ts, src/lib/databases/ProjectDb.ts, src/app/Admin/XpApprovals/components/ApprovalFamilyRewardRow.vue, src/app/Admin/XpApprovals/XpApprovals.vue, src/hooks/useProjectFinish.ts; specs tests/unit/{projectFinish,ProjectDb,ApprovalFamilyRewardRow,useProjectFinish}.spec.ts.