Skip to content

progress

Xopher edited this page Oct 28, 2026 · 1 revision

SDD ledger — plan: /home/xopher/www/x/do-it-for-the-xp/docs/superpowers/plans/2026-10-23-project-towers.md

Spec: /home/xopher/www/x/do-it-for-the-xp/docs/superpowers/specs/2026-10-23-project-towers-design.md (the worktree's docs/ submodule is empty: read both from the main checkout path). Branch feat/project-towers off origin/master 026ce404; worktree .claude/worktrees/feat+project-towers; node_modules symlinked. Board: epic #1539. Plan task -> board: T1 #1540, T2 #1541, T3 #1542, T4 #1544, T5 #1543, T6 #1545, T7 #1546, T8 #1547, T9 #1548, T10 #1549, T11 #1550, T12 #1551. Process (owner): chemx for reads/search/tests and the board; builders never run git (the lead commits each task by path); builders claim/comment their board task; one implementer at a time; Sonnet implementers (Opus for T6, T8), Sonnet reviewers, Opus final review. Archive this workspace into docs before the worktree is removed (lesson from the classic temples run).

Pre-flight scan

Pair / task Produces vs consumes Finding
T1 -> T6 ProjectQuestLink {projectId, floor, dueOn, wokenAt?} -> questsForProject writes project link, 1F wokenAt=now consistent
T1 -> T8 ProjectQuestLink -> wakeFloor sets project.wokenAt; extendProject rewrites startsOn + project.dueOn consistent
T2 -> T4 ProjectChoreSuggestion.who: PackBand / "any" / "team" -> suggestChores "any" has no test or rule. Ruling R1
T2 -> T6 ProjectType.element/phases/finale -> record element, floors consistent
T3 -> T6 PhaseWindow; roof window = last phase's -> floors carry windows, quests startsOn = opensOn consistent
T3 -> T8 floorMultiplier(window, clearedOn YYYY-MM-DD) -> settleFloor has ISO completedAt conversion unstated. Ruling R2
T4 -> T6 ProjectChore.roomId groups chores into rooms; finale phaseId "roof" finale roomId unstated. Ruling R3
T5 -> T6 content.locked 'floorCleared' + solver -> stairs-up written by T6, solve test consistent (T6 after T5)
T5 -> T10 room context floorCleared?: boolean (type) -> filled by TempleGrounds consistent
T6 -> T7 ProjectTower.floors -> newProjectRecord; input.projectId who makes projectId unstated. Ruling R4
T7 -> T8 ProjectFloor / ProjectRecord / ProjectDb -> settleFloor, projectService consistent
T8 -> T9, T10, T11 startProject, extendProject, abandonProject, markRewardGiven, markSeen consistent
T9 -> T12 ProjectDraft -> prefillMovingOut; both edit PacksSettings.vue sequential, fine
T10 -> T11 T10's test "no active but a cleared one shows the trophy (Task 11's sheet)" T11's sheet does not exist at T10. Ruling R5
T6 -> T12 T6 spec uses tests/unit/movingHouseFixtures.ts; T12 deletes movingTemple specs fixtures must survive. Ruling R6
T1 self tests vs code consistent
T2 self Fibonacci-only difficulties and finale >= 8 vs mapped MOVING_TEMPLATES values may clash with content. Ruling R7
T3 self [4,6,3,1] for 14 days; split 400/41 three ways arithmetic checked: consistent
T4 self clamp to band vs "team" chores team clamp unstated. Ruling R8
T5 self tests vs code consistent
T6 self fixtures are MovingAnswers-shaped vs ProjectHouse input map rooms/basement; consistent
T7 self tests vs code consistent
T8 self tests vs code consistent
T9 self tests vs code consistent
T10 self see R5 ruled
T11 self "lead asks owner before committing credits.ts" a local commit publishes nothing. Ruling R9
T12 self e2e needs a server on the worktree's files own port, stopped after; consistent with CLAUDE.md

Ruling R1: who "any" assigns round-robin across all heroes, sized to that hero's band — the only reading consistent with "little"/"team" — costs a re-assign rule if wrong. Ruling R2: clearedOn = local calendar day (dateKey) of the latest completedAt among the floor's paid entries — matches the plan's whole-day bonus math — costs off-by-one near midnight if wrong. Ruling R3: the finale chore has phaseId "roof" and roomId "roof" (one room on the roof) — the roof holds only its arrival and the finale — costs a rename if wrong. Ruling R4: projectId = ${typeId}-${startOn}; the temple id stays gen-project-<typeId> and a re-run replaces it (trophies live in ProjectDb, not TempleDb) — one active project at a time makes collisions a same-day restart only — costs an id suffix rule if wrong. Ruling R5: T10's sheet hides the Hometown tower spot when there is no active project; the "cleared project shows the trophy" behaviour and its test move to T11, which wires ProjectTrophySheet into the spot — costs nothing, only ordering. Ruling R6: T12 deletes movingTemple.spec.ts and houseRecord specs but keeps tests/unit/movingHouseFixtures.ts (T6's matrix imports it) — costs a broken T6 spec if wrong. Ruling R7: movingOut.ts rounds a template difficulty that is not on the slider to the nearest slider value (ties up), and the finale's difficulty is max(8, load-van's) — the spec fixes boss tier at 8+ — costs a content tweak if wrong. Ruling R8: a "team" chore (several assignees) is not clamped to a band; it keeps its suggested difficulty — a team chore has no single band — costs a clamp rule if wrong. Ruling R9: credits.ts is committed on the branch like any task; the owner's confirmation gates the PUSH/PR (asked at finish, since publishing is what exposes the names and ages) — costs nothing until push.

Tasks

Task 1: dispatched (BASE 026ce404, implementer @sonnet-pt1, board #1540) Task 1: minor (deferred): packExport.ts strips createdBy/draft but not project; drop the project link if project quests can reach pack export (implementer concern 2) Task 1: implementer done (DONE_WITH_CONCERNS: QuestList.ts guard outside the file list, spec green); committed ddebbcaa; review dispatched Task 1: minor (deferred): isQuestActiveOn ignores date for a woken project quest, so past-day boards (TownHall.ts:164, MyCalendar.vue:196) may show it before it woke; compare date with wokenAt there if it matters (plan-mandated rule) Task 1: minor (deferred): a malformed project.dueOn parses as today (parseLocalDate fallback) and never goes overdue; T6's questsForProject must always write a valid dueOn (carried to T6) Task 1: minor (deferred): a project chore must be basicSchedule "once" or getQuestWindow gives it a daily window (carried to T6) Task 1: minor (deferred): no test for wokenAt with a past endsOn, or Extra Time on a project chore; QuestSchedule now imports services/core/packs/heroAge (follows precedent) Task 1: complete (commits 026ce404..ddebbcaa, review clean) Task 2: dispatched (BASE ddebbcaa, implementer @sonnet-pt2, board #1541) Ruling: Moving Out's tower name is "The Moving Keep" (implementer's pick; spec named none) — a tower name is one string — costs a rename if wrong. Ruling: ProjectChoreSuggestion gains perHero?: boolean, set for Moving templates with hero:true; T4's suggestChores must expand a perHero suggestion into one chore per hero of its band (carry to T4) — keeps the Old House's per-kid chores — costs a field if wrong. Ruling: movingPack.ts exports movingBeastId(slug) and both builders use it (no duplicated id format) — DRY with the pack — costs nothing. Task 2: implementer done (fix of 3 rulings applied before review); committed 00111277; review dispatched Task 2: review: Approved except 1 Important (little-band "Open the Windows" on the whole house: fall risk) -> fix round 1 sent to the implementer Task 2: minor (deferred): the slider steps are copied in movingOut.ts and the spec; questWizard.ts SIZE_CHOICES has them; one exported FIBONACCI_STEPS + nearestDifficulty in packCommon.ts would serve T4/T9 (carry to T4) Task 2: minor (deferred): projectTypes.spec copies movingPack.spec's 28-line DB mock block because projectTypes imports movingPack (storage); moving the template data to its own module would drop it Task 2: minor (deferred): content: no dusting chore (little has nothing of its own on Deep Clean); doorknob/switch wipe is any (a little may get liquid near switches); kitchen deep clean is thin (no fridge/cabinets) Task 2: minor (deferred): PROJECT_TYPES suggestion objects are shared singletons: T4/T9 must copy before the GM edits (carry to T4, T9) Task 2: fix round 1/5 committed 3abcb353 (window chore to kid 2, Fluff the Cushions added); scoped re-review dispatched Task 2: fix round 1/5 (1 addressed, 0 open; commits 00111277..3abcb353) Task 2: complete (commits ddebbcaa..3abcb353, review clean) Task 3: dispatched (BASE 3abcb353, implementer @sonnet-pt3, board #1542) Ruling: phase days come from rounded cumulative boundaries, a 0-day phase borrows 1 day from the largest; extend re-spreads uncleared phases from today by renormalised shares — reproduces the plan's [4,6,3,1] — costs a day's shift between phases if wrong. Ruling: splitFloorBonus gives the remainder to lastWorkerId only if they are among the workers, else to the first worker; no workers -> {} — the remainder must land on someone who worked the floor — costs a few XP/GP if wrong. Task 3: implementer done (concerns: design notes, float settle to 6 decimals, extra exports daysBetween/FloorBonus); committed; review dispatched Task 3: review: Approved except 1 Important (the DST tests cannot fail: no TZ is pinned anywhere) -> fix round 1 Ruling: fix the DST finding by renaming the tests and commenting daysBetween, not by pinning TZ in jest config — pinning TZ in config makes every PR run the full suite, and a per-spec process.env.TZ is unreliable under jest's sandbox; the reviewer verified DST safety under TZ=America/New_York — costs an unguarded regression if someone swaps daysBetween for ms arithmetic. Task 3: minor (deferred): the roundSettled guard is not pinned (no test input where it matters; shares [0.05, 0.2, 0.05, 0.1] over 6 days would pin it); the test comment cites 0.1+0.2 wrongly Task 3: minor (deferred): an unparsable date reads as today (parseLocalDate fallback): T9's wizard must validate date strings before extendWindows/phaseWindows (carry to T9) Task 3: minor (deferred): roof window has no helper (windows[windows.length-1]) — T6/T8 must use the last phase's window for the roof (carry to T6, T8) Task 3: minor (deferred): extendWindows from today can overlap a floor cleared early whose dueOn is still ahead (ruling-consistent); T10's extend UI should expect it; dayAfterStart helper sits mid-file in the spec Task 3: fix round 1/5 committed d9368c5f; scoped re-review dispatched Task 3: fix round 1/5 (1 addressed, 0 open; commits 98bb29a9..d9368c5f) Task 3: complete (commits 3abcb353..d9368c5f, review clean) PR #166 (board sweep) MERGED 2026-10-23; sweep board tasks closed; sweep worktree removed Task 4: dispatched (BASE d9368c5f, implementer @sonnet-pt4, board #1544) Ruling: chore ids are ${phaseId}:${slug}:${roomId} (+ :${heroId} for perHero); the finale is a team chore unless its who names a band; clamping lands on the nearest in-range slider value — stable ids let re-runs and edits line up — costs an id migration if wrong. Ruling: a suggestion listing several room kinds is ONE chore in its first matching room ("any of these rooms", as the Moving pack reads templates); a suggestion split across rooms names each chore with "(Room Name)" — no duplicate-named chores on a hero's board — costs a naming tweak if wrong. Ruling: band fallback little->kid->grown-up, kid->grown-up->little, grown-up->kid->little (implementer's table) — keeps chores near their size — costs a re-assign if wrong. Task 4: carry to T9: choresFromLastRun has no house param; the wizard must drop or re-home chores whose roomId no longer exists in the current house (custom chores to "The Whole House"). Task 4: gate note: in this worktree jest could not resolve jest-serializer-vue through the node_modules symlink; the builder ran specs with NODE_PATH=$PWD/node_modules/.pnpm/node_modules (check before the final gate) Task 4: implementer done (rulings 2/3 applied before review); committed; review dispatched Task 4: ⚠️ packCommon.ts/movingOut.ts edits outside the brief's file list: confirmed, the lead's dispatch asked for one FIBONACCI_STEPS/nearestDifficulty in packCommon Task 4: minor (deferred, carry to T9): re-runs guess team chores from assignee count (no who stored); team/roof chores don't pick up heroes who joined since; a re-assigned chore keeps its size unclamped and clampToBand is private (T9 may export it) Task 4: minor (deferred): movingOut.ts re-exports nearestDifficulty only for projectTypes.spec's import (repoint and drop the shim); re-run collision suffix "~r2-2" is not stripped next run; garbled test title at projectChores.spec.ts:482 Task 4: complete (commits d9368c5f..d7b9b284, review clean) Task 5: dispatched (BASE d7b9b284, implementer @sonnet-pt5, board #1543) Ruling: for the floorCleared lock a fight room is type monster/miniboss/boss or a non-empty content.questIds, on the stairs' own floor; unreachable fight rooms make the temple unsolvable with the stairs named — matches how quest rooms seal elsewhere — costs a solver tweak if wrong. Task 5: implementer done (concerns: ctx.floorCleared filled in T10, audit unchanged by design, unreachable fight room -> unsolvable); committed; review dispatched Task 5: carry to T10: add floorCleared to the liveContext object literal in useRoomOutcomes.ts (getLiveContext) Task 5: minor (deferred, final review should triage): solveTemple now runs deadFloorClearedStairs (one extra explore) and roomsToClearByFloor inside every explore for EVERY temple, none of which has the lock today; an early return when no door is floorCleared would save the generator's loop the cost Task 5: minor (deferred): untested variants: a stairs room that itself holds questIds; the floor's only chore room beyond the stairs; a chore room behind a small-key or boss door Task 5: complete (commits d7b9b284..5d44c39b, review clean) Task 6: dispatched (BASE 5d44c39b, implementer @opus-pt6, board #1545) Ruling: tower record = id gen-project-, name towerName, generated, element from type, themeId "home", seed project:; the floor boss holds the phase's single biggest chore (first on ties), other chore rooms typed by their biggest chore's tier, no seals; quests: achievementName = chore name, categoryId "", beastId only when the chore has one — mirrors the Old House record — costs a field rename if wrong. Task 6: implementer NEEDS_CONTEXT: the plan's room types (a type:"boss" room per floor, no Boss Key) can never be entered in play (isBossDoor) and fail the solver/audit (one boss, a miniboss-guarded Boss Key, sealed boss rooms, a Master Room) Ruling: towers use the Old House pattern stacked: chore rooms are open monster rooms with the tier in content.difficulty; floor bosses 1F..(n-1)F are monster rooms with difficulty "boss" beside the floorCleared stairs; the last phase's floor boss is the one sealed miniboss and drops the Boss Key; the roof is arrival + finale (the one sealed boss, behind the Boss Key door) + a Master Room "The Trophy" — the only shape the engine, solver and audit accept without engine changes; deviates from spec section 5 ("the roof holds only the final boss") by one trophy room — costs a roof redesign if wrong. Ruling: ProjectTowerInput gains heroes?: PackHero[]; a chore requires approval when difficulty >= 5, a little/kid is assigned and no grown-up is on it; none without heroes — follows the Moving pack's kid-5+ rule for any assignment — costs a field if wrong. Ruling: the roof floor key is ${phaseCount + 1}F (roofFloorKey), not "RF", so climbOrder/floorHeight put it on top; phaseId stays "roof" — "RF" sorted below 1F — costs nothing. Ruling: ROOM_SETS and FAMILIES are exported from tests/unit/movingHouseFixtures.ts (movingTemple.spec imports them back) — survives T12's deletion of movingTemple.spec (R6) — costs nothing. Task 6: implementer done (concerns: duplicate room-name cells beside a floor boss; plain-string cell names as in the Old House; basement unread; heroes needed for approvals; empty last phase refused) Task 6: carry to T8: rewrite startsOn with projectStartsOn (local midnight) when extending; carry to T9: every phase needs >= 1 chore (the last phase's boss holds the Boss Key) and pass heroes to the builder Task 6: review (opus): Needs fixes: 1 Important (~9% of towers strand a last-floor chore room behind the sealed key-guard miniboss: the planner's single zone) -> fix round 1 sent Task 6: ⚠️ resolved: an empty middle floor must read floorCleared true and wake the next floor (carry to T8, T10); a deleted key-guard chore must still drop the Boss Key (carry to T8); T8/T9 must pass heroes (carried) Ruling: tower cell names ("Front door", "Stairs up", "The Roof", "The Trophy") stay plain record data like the Old House's room names, not catalogue copy — they are place names stored in the temple record, as "Kitchen" is — costs unvoiced place names if wrong. Task 6: minor (deferred): last phase with a single chore not pinned by a test; "nothing ticked" excluded from the matrix though it builds; WHOLE_HOUSE_ID "house" duplicates projectChores.ts:74; finale re-check duplicated (projectTower.ts:187); roof window taken by array order; two adjacent cells can share a room name (boss's room + its other chores) Task 6: fix round 1/5 committed (guard in its own zone placed last, stairs last in the house zone, reachability matrix); scoped re-review dispatched Task 6: fix round 1/5 (1 addressed, 0 open; commits 878a7aa5..7197d3fc) Task 6: minor (deferred): on a very dense floor the guard (placed last, wing zone) could fail to find a spot touching only its anchor (no matrix case gets near); the key guard can sit beside a different room than its name Task 6: complete (commits 5d44c39b..7197d3fc, review clean after fix round 1) Task 7: dispatched (BASE 7197d3fc, implementer @sonnet-pt7, board #1546) Ruling: __projects joins SYNC_STORES with scope "family" (the implementer added it so syncScopes.spec holds) — the spec wants one record Family Sync can carry — costs a sync exception if wrong. Task 7: implementer done; committed c0466bb6 (BASE 7197d3fc); REVIEW NOT YET DISPATCHED (usage limit reached 2026-10-24). Resume: review-package 7197d3fc..c0466bb6, dispatch the Task 7 reviewer, then Task 8. Resume 2026-10-27 (@towers-lead): origin/master moved to 73ca9395, a merge of master into feat/project-towers made by the owner (Xopher, 14:45) and pushed: T1-T7 (026ce404..c0466bb6) are ALREADY ON origin/master. The branch equals origin/master; the rebase was a no-op. The PR will carry T8-T12 only. Resume check: 13 specs T1-T7 touched green (1180 tests, chemx test, NODE_PATH set); chemx typecheck clean. Owner ruling (2026-10-27, relayed by the coordinator): the credits roll drops the ages. The makers' part shows names and roles only; no age (or birth year) field in the roster data file, the credits component or its specs; no spec or fixture asserts an age. PR description says "credits show names and roles only, ages dropped on the owner's ruling" in place of the names/ages caveat. Carry to T11 (supersedes the "ages from the roster" line of the brief and the birth-year part of the credits privacy gate). Task 7: review dispatched 2026-10-27 (sonnet, package review-7197d3fc..c0466bb6.diff); board #1546 handed off @sonnet-pt7 -> @towers-lead Ruling: the "deleted key-guard chore must still drop the Boss Key" carry moves from T8 to T10 — it is room play (questRooms resolvePicks with no live quest), and T8 has no temple-screen files — costs a T10 fix if the room seals forever. Ruling: empty floors cascade: when a settlement wakes a floor with no live chores, it settles in the same pass (no grants) and wakes the next; an empty roof finishes — the owner's "a deleted chore no longer counts" leaves no payment event to settle such a floor otherwise — costs a test rewrite if wrong. Ruling: the floor bonus pool counts each existing chore's own XP/GP once (a team chore paid to three heroes counts once), workers = distinct userIds of the floor's paid entries (rewardsApplied), zero grants (x1) are not paid — spec section 4 "(m - 1) x the XP and the GP of every chore on the floor" — costs a recount if wrong. Ruling: markRewardGiven/markSeen act on the newest cleared record — the reward and credits exist only after a clear; no-ops when none — costs a project id param if wrong. Ruling: the roof bonus label uses the plain-string pattern of QuestCompletionService ("Floor bonus: ", like "Quest: "), not the copy catalogue — reward log labels are not voiced anywhere today — costs a catalogue key if wrong. Task 7: review (sonnet): Spec ✅, Approved, 0 Critical/Important. Task 7: minor (deferred): ProjectDb isRecord checks only id+status (a record without floors would throw downstream); id copied from input.projectId with no shared builder for ${typeId}-${startOn} (T9 should build it in one helper); ProjectFloor re-declares ProjectTowerFloor's fields; the JSON round-trip test's toEqual hides undefined keys Ruling: ⚠️ site table — the site refuses an unknown store per record (recordOutbox: a refusal is final, the batch goes on), so __projects needs a family row in the site's Xophz_Compass_Xp_Family_Record_Rules (xophz-compass-xp, outside this repo) before projects sync; noted for the PR, no change here — costs unsynced project records until the site row lands. Ruling: ⚠️ family scope vs who settles — the floor settle runs on the device that pays the chore; ProjectDb is family scope, so a hero phone's local settle never syncs; T8 keeps the settle derived from the quest log and guarded by clearedAt (idempotent per device); a cross-device double bonus is Family Sync's concern, flagged to the final review — costs a double floor bonus on a two-device family until sync settles projects centrally. Task 7: complete (commits 7197d3fc..c0466bb6, review clean) Task 7: board done held: chemx gate flags CONTROL_FLOW_INLINE_BOOLEAN at projectRecord.ts:75; folded into T8's dispatch (item 13), #1546 closes after T8's commit Task 8: dispatched (BASE 73ca9395, implementer @sonnet-pt8, sonnet, board #1547 held by @towers-lead); context $W/task-8-context.md carries 13 rulings Ruling: the wizard owns the project id helper projectIdOf(typeId, startOn) (T7 minor) and one exported whole-house id from projectChores.ts (T6 minor: "house" is duplicated) — one source per id — costs nothing. Task 8: implementer done (DONE_WITH_CONCERNS); committed 318fd39d (BASE 73ca9395); 36+25+57+10 tests green Ruling: keep the third settle hook in recordBattleCompletion — tower chores are won as fights, and a won fight with no approval is logged there, never by completeQuest — costs nothing (one line) if wrong. Ruling: keep the lazy await import of projectService in QuestCompletionService and the leaf projectIds.ts (pq- prefix) — a static import made a load cycle through packCommon/movingOut — costs nothing. Ruling: when the last phase is already cleared, extending gives the roof { opensOn: today, dueOn: newEndOn } — a cleared phase keeps its window, so the roof could not move otherwise — costs a roof date rule if wrong. Ruling: abandon may delete a chore with a held (pending) claim; approving that entry still pays its own rewards and the hook does nothing — only paid entries protect a chore (ruling 9) — costs an orphan log line if wrong. Task 8: carry to T10: reset heroes' saved runs (TempleProgressDb) in gen-project- on start/abandon (the Old House's forgetOldHouseWalks pattern); add a public "settle this floor now" entry for a floor emptied by deleting its last open chore (arrival or board refresh calls it); the Boss Key when the key-guard chore was deleted. Task 8: review dispatched (sonnet, review-73ca9395..318fd39d.diff) Task 8: gate after commit: chemx typecheck clean; chemx lint passed (9 warnings, 0 errors) Task 8: review (opus): Spec ✅; Needs fixes: 1 Important (a floor that reads cleared but never settled has no retry: one failed settle or a deleted last open chore strands it; no next floor wakes) -> fix round 1 Ruling: minors 2 (validate newEndOn/today as YYYY-MM-DD in rescheduleFloors), 3 (the climb skips a floor above that is already settled) and 4 (an empty floor settled in the cascade is stamped x1, not the window's x2) join fix round 1 as load-bearing — T10's arrival wake lets floors clear out of order, T10's extend UI feeds the dates, and T11's trophy shows each floor's multiplier — costs one more fix item each if wrong. Ruling: the Important fix is a public settleReadyFloors() in projectService (every woken, unsettled floor of the active project that reads cleared, lowest first, the cascade as settleFloor does); the payment hook calls it; T10's arrival wake and board refresh call it (replaces T10 context ruling 1's "settle at once" half) — one trigger for every path — costs nothing. Task 8: minor (deferred): class loot bonus (paidQuestRewards in applyQuestRewards) raises a Thief/Ninja's GP share after the even split — brief mandates applyQuestRewards; owner call, named in the PR Task 8: minor (deferred): "The Roof" written in projectService.ts:66 and projectTower.ts:140; no spec loads the real QuestCompletionService with the real projectService (the lazy-import cycle is covered only by the build/CI) Task 8: fix round 1 committed ef251dda (settleReadyFloors, date guard, climb skip, empty x1; 57+31+57 green); scoped re-review dispatched (sonnet) Task 8: gate after fix commit: typecheck clean; lint 0 errors (9 warnings) Task 8: fix round 1/5 (4 addressed, 0 open; commits 318fd39d..ef251dda) Task 8: minor (deferred): a retry re-stamps the floor above's quests with the later wokenAt; a crash between the record save and the bonus payment loses that bonus (never doubles it) Task 8: carry to T10: settleReadyFloors() has no caller yet: call it on arrival, tower load and the Hometown sheet Task 8: complete (commits 73ca9395..ef251dda, review clean after fix round 1) Task 8: board done held: gate flags CONTROL_FLOW_INLINE_BOOLEAN at projectProgress.ts:246; folded into T9's dispatch (item 12) Task 9: dispatched (BASE ef251dda, implementer @sonnet-pt9, sonnet, board #1548 held by @towers-lead); context task-9-context.md carries 13 rulings Task 9: implementer done (DONE_WITH_CONCERNS: reward text on the dates step; house step asks rooms + basement only (ProjectHouse has no floors field); mirrored the Moving House rooms markup (that dialog has e2e hooks and hex colours); extra _XpProjectWizardModal.scss; changing rooms rebuilds the chores list; endOn = today + defaultDays); committed 10038fbe (BASE ef251dda); 518 tests green Ruling: the reward text sits on the dates step (spec step 4) — the implementer's pick, matches the spec — costs a move if wrong. Task 9: review dispatched (sonnet) Task 9: gate after commit: typecheck clean; lint 0 errors (9 warnings) Task 9: review (sonnet): Spec ✅, Approved, 0 Critical/Important. Task 9: ⚠️ resolved: usersAz -> HeroProfile[] typechecks (gate clean); same-day abandon + restart reuses ${typeId}-${startOn} and so the abandoned run's paid quest ids: old paid entries would settle 1F at once -> real, carried to T10 (startProject picks a fresh id). Ruling: startProject never reuses a project id: when ProjectDb already holds input.projectId it suffixes "~2", "~3" (pure helper, tested) and the quests take the fresh id — the abandoned run's paid log entries name the old quest ids — costs an id rule change if wrong (supersedes R4's "same-day restart only" cost). Task 9: minor (deferred): a rooms change on the house step rebuilds the chores and also drops the GM's own custom chores (keep custom chores); "1 chores" in projectWizard.chores.count / projectWizard.build.chores (no plural); stale basement:true when storage is unticked (nothing reads it yet); no "sitting out" band like MovingHouseModal; per-row controls scroll with the list (thumb-reach); previewWindows builds copy to test a condition (return a code) Task 9: complete (commits ef251dda..10038fbe, review clean) Task 10: dispatched (BASE 10038fbe, implementer @sonnet-pt10, sonnet, board #1549 held by @towers-lead); context task-10-context.md carries 11 rulings Ruling: building Moving Out from the prefill deletes the open move-* quests it carried into the tower (paid ones and camp/unpack stay) — leaving them would put every moving chore on the board twice — costs restoring quests if wrong. Ruling: T12's e2e walk runs on the worktree's own dev server on port 8123, started once and stopped by port (lsof), per CLAUDE.md's worktree rule; the two pnpm commands are allowed for that task only — the shared :8100 serves the main checkout — costs a few GB for the run. Task 10: implementer done (DONE_WITH_CONCERNS: TempleGrounds.ts 500 -> 507 lines (line-budget ratchet?); clock is a row under the toolbar; seen = ProjectFloor.seenBy; key guard with deleted chores already an empty lair with "Take it" for the Boss Key, pinned by TowerKeyGuard.spec; T11 must show the spot for a cleared project and fill the sheet's empty slot); committed 60cce69c (BASE 10038fbe); 303 + 153 tests green Ruling: the floor clock sits as a row under the temple toolbar, not inside it — the toolbar title is absolutely positioned — costs a layout tweak if wrong. Ruling: "Floor cleared!" seen-ness lives on the record (ProjectFloor.seenBy), written before the prompt shows — one family record carries it across devices with Family Sync — costs a field if wrong. Task 10: review dispatched (opus: TempleGrounds is shared by every temple) Task 10: gate after commit: typecheck clean; lint 0 errors (9 warnings) Task 10: review (opus): Spec ❌ / Needs fixes: 2 Important (TempleGrounds.ts at 507 lines trips the audit's 500-line LINE_BUDGET_FILE rule under a capped ratchet; the tower code never checks the running project belongs to the temple on screen, so a cleared tower can wake/lock against another project) -> fix round 1 Ruling: three minors join fix round 1 because they break a stated global constraint — hard-coded in-world words (towerSpot "Project tower", towerPrompts "Tower Keeper"/"Onward"/"Abandon"/"Keep it", floorClock "The Roof") must go through the copy catalogue; the rgba() literal in TempleFloorClock.vue (tokens only); projectService.spec.ts:581 calls.length <= heroes.size passes when nothing is paid (assert the exact count) — costs a few lines each. Task 10: minor (deferred): extendProject failures all show "too soon"; a failed re-read after a successful move flips the sheet to "No tower"; the stairs lock refreshes only on page show / floor change (a GM approval mid-visit needs a re-show); buildingShapes.ts (906 lines) grew by 72 (a sibling projectTowerShape.ts would be tidier) Task 10: ⚠️ for the e2e (T12) and owner phone look: clock row height at 390x844; "Floor cleared!" stacking with a battle drop reveal Task 10: fix round 1 committed 2908506a (routeStart.ts, TempleGrounds.ts 490 lines; onFloorReached(templeId, ...); words to the catalogue; token-only clock; exact bonus asserts; 266 tests green); scoped re-review dispatched (sonnet) Ruling: a tower with no running project of its own (a trophy tower) reads floorCleared undefined, so its stairs stay shut — no UI route re-enters a finished tower today; T11 opens the trophy as a sheet, not a walk — costs a one-line change in useProjectTower.isFloorCleared if the owner wants trophy towers walkable. Task 10: gate after fix commit: typecheck clean; lint 0 errors (9 warnings) Task 10: fix round 1/5 (5 addressed, 0 open; commits 60cce69c..2908506a) Task 10: minor (deferred): useProjectTower.ts:53-62 comment (and the TempleGroundsModules spec title) says undefined floorCleared means "no lock"; travel.ts:143 keeps a floorCleared flight shut on undefined (ruled above: trophy towers are not walked); ProjectTowerSheet.vue:6 aria-label="Close" hard-coded; "Floor bonus: " label is plain English (ruled in T8) Task 10: complete (commits 10038fbe..2908506a, review clean after fix round 1) Task 11: dispatched (BASE 2908506a, implementer @sonnet-pt11, sonnet, board #1550 held by @towers-lead); context task-11-context.md carries the owner's no-ages ruling and 6 rulings Finding (2026-10-27): master's Tests check is red since 73ca9395 (the T1-T7 merge): Verify fails only on the audit's ratchet gate (tests 10438/10438 green, typecheck clean); E2E smoke was already red at 744e7e21 (a SyntaxError "Unexpected token '{'" in app code under Cypress 8's browser), not ours. Local chemx audit at 2908506a: AI_SLOP_REDUNDANT_PASSTHROUGH 0->5, CONTROL_FLOW_DISPATCH_SWITCH 36->38, CONTROL_FLOW_INLINE_BOOLEAN 8->22, LINE_BUDGET_FILE 39->40, every item in this feature's files (QuestSchedule.ts 491->519 lines; T5's switches in templeSolver.ts and travel.ts). Ruling: a ratchet-cleanup task (brief ratchet-brief.md) runs before the Gate, in parallel with T11 on disjoint files (projects/.ts rules files, ProjectDb.ts, templeSolver.ts, travel.ts, QuestSchedule.ts) — the PR runs the same whole-project audit and cannot go green otherwise, and the fix also turns master's Verify green — costs one sonnet run; T11 is told to keep MyPortal.ts under 500 lines. Ratchet cleanup: done (sonnet @sonnet-ratchet); committed a69f322a: PASSTHROUGH 0, DISPATCH_SWITCH 36, LINE_BUDGET 39 (QuestSchedule.ts exactly 500, project-quest rules in projectQuestSchedule.ts), all 14 INLINE_BOOLEAN sites named; 12 specs 619 green before and after. Remaining gate fails are T11 in-progress (credits.ts INLINE_BOOLEAN, XpApprovals.vue complexity 13) -> sent to the T11 builder. Ruling: never commit chemx-ratchet.json's local rewrite (ruleset 6 -> 11 from the local chemx, newer than CI's pin 26.10.19-530) — it is the tool's migration, not this feature's — costs nothing. Ratchet cleanup: minor (deferred): QuestSchedule.ts sits at exactly 500 lines (no headroom); travel.ts's isKeyLock/isFloorLock aliases rely on narrowing (the typecheck after T11 confirms); the brief's "switch at travel.ts ~L71" was the older doorActions switch, the new one (canOpenClimb ~L153) was replaced Ratchet cleanup: review folded into the final whole-branch review (mechanical, no behaviour change) Task 11: implementer done (DONE_WITH_CONCERNS: a paid team chore credits every assignee (Team Effort pays only the claimer, so "assignees with a paid entry" would credit one hero for the family finale); heroes who cleared nothing are left off the roll; "Start again" is GM only; the reveal and credits follow the newest cleared run; local audit gate: pass); committed 55b7b584 (BASE 2908506a; the ratchet commit a69f322a sits between, review range a69f322a..55b7b584); 276 tests green Ruling: a paid team chore credits every assignee in the credits roll (the brief's "a team finale counts for every assignee"), overriding the context's "with a paid entry" — Team Effort pays one claimer, so the family's finale would credit one hero — costs a credit-count rule if wrong. Ruling: heroes who cleared no project chore are left off "The Heroes of " — the roll lists the tower's heroes by what they did — costs a line per idle hero if wrong. Ruling: the trophy's "Start again" is Game Master only — starting a project is a GM act (the wizard sits behind allowedToManagePacks) — costs nothing. Task 11: review dispatched (sonnet) Task 11: gate after commit (covers the ratchet commit too): typecheck clean; lint 0 errors (9 warnings) Task 11: review (sonnet): Spec ✅ (age ruling verified strictly: Maker {name, role}, no age/birth anywhere), Approved with 1 Important (projectFinish.ts copies the "newest cleared" comparator from projectProgress.ts word for word; the reveal shows trophiesOf()[0] but markSeen stamps newestCleared: drift re-tells every hero) -> fix round 1 Ruling: two minors join fix round 1 as load-bearing — ApprovalFamilyRewardRow resets isSaving before the re-read, so a quick second tap marks the NEXT run's reward given (wrong family data); isHeld() is not re-checked before the credits, so an ambush during the reward prompt lets the credits roll over a battle — costs a few lines each. Task 11: minor (deferred): look() runs on every route change through the inTurn queue (cache "nothing to tell" until the next clear); XpApprovals/MyPortal/HomeTown template wiring has no mount test; the reward row sits above "No pending achievement approvals"; TrophyView.typeId typed string, untyped defineEmits in ProjectTrophySheet; XpDashboard [true]/[] shim; credits.spec hard-codes the six names (say so in credits.ts's comment); ProjectDb.ts holds a third copy of the comparator Task 11: fix round 1 (the builder's first resume died on an API auth error mid-round; resumed once more, finished) committed 334db221: one comparator in projectOrder.ts (not projectProgress.ts: ProjectDb -> projectProgress -> packCommon -> BackupService -> ProjectDb would cycle), row stays disabled after save and XpApprovals keys it by familyReward.id, isHeld re-checked before each moment; 148 + 118 green; audit gate pass; scoped re-review dispatched (sonnet) Task 11: gate after fix commit: typecheck clean; lint 0 errors (9 warnings) Task 11: fix round 1/5 (3 addressed, 0 open; commits 55b7b584..334db221) Task 11: minor (deferred): ProjectDb.lastOfType spells the cleared stamp itself (pinned to newestCleared by a spec) Task 11: complete (commits 2908506a..334db221 minus the ratchet commit a69f322a, review clean after fix round 1) Note: a chemx hook attributed an out-of-band edit of src/lib/services/widgets/widgetSyncTrigger.ts to my Bash call; the worktree shows no such change (a peer session's edit in the main checkout) Task 12: dispatched (BASE 334db221, implementer @sonnet-pt12, sonnet, board #1551 held by @towers-lead); context task-12-context.md carries 10 rulings incl. the worktree's own dev server on :8123 Task 12: implementer done (DONE_WITH_CONCERNS: the Moving House card still opens its dialog (MovingAnswers are stored nowhere), which hands over to the wizard from a "Build the Moving Out tower" button after the pack is built; changing rooms after the hand-over refills chores from suggestions, so nothing leaves the board; forgetRuns replaces forgetOldHouseWalks, movingTempleReset.ts deleted as unused; Moving Out retires the Old House on any build; a dialog-close bug that shut the wizard was fixed; e2e seam in main.ts + e2eProjectSeam.ts); committed 2f26bbc5 (BASE 334db221); 592 unit green; e2e on :8123: project-tower 3/3, moving-temple (rewritten) 3/3, moving-pack 6/6; server stopped; local audit gate pass Ruling: the Moving House dialog hands over to the wizard (a "Build the Moving Out tower" button) instead of the card opening the wizard directly — the answers live only in the dialog, and spec section 9 keeps the Old House working until Moving Out is started — costs an entry-point move if wrong. Ruling: building any Moving Out project retires the Old House, not only one from the prefill — spec section 9 "building it retires the Old House" — costs nothing. Task 12: review dispatched (sonnet) Task 12: gate after commit: typecheck clean; lint 0 errors (9 warnings) Task 12: review (sonnet): Spec ✅, Approved, 0 Critical/Important; family data, dev-only seam, no dangling imports, unweakened walk all checked. Task 12: minor (deferred, final review should triage — same as T9's): a room change on the handed-over wizard's house step refills chores from suggestions and drops the carried move- chores, so the board keeps them and the tower gets suggestions (the "every chore twice" case): withHouse should keep custom chores Task 12: minor (deferred): the "Moving Out retires the Old House" rule sits in XpProjectWizardModal.vue (move to projectWizard.ts); dead .moving-bands + .moving-check CSS and a stale movingPack.ts:75 comment about floors; project-tower.spec.js uses fixed cy.wait sleeps and tests 2-3 depend on test 1; moving-temple.spec.js run line points at :8100; unnamed compound conditions in movingOutPrefill.ts filter callbacks; PacksSettings Moving House card copy is a raw template string; prefill ignores moveDate for endOn Task 12: ⚠️ by ruling: abandoning a Moving Out run deletes its open project quests; the move-* quests it carried already left the board at build, so the family re-runs the Moving House pack to get them back Task 12: complete (commits 334db221..2f26bbc5, review clean) Ruling: the final whole-branch review covers the PR range 73ca9395..2f26bbc5 (T8-T12 + the ratchet cleanup, 93 files, +11.4k/-1.2k) with T1-T7's modules read as context — T1-T7 are already on master and were each task-reviewed — costs an unreviewed cross-task seam inside T1-T7 if wrong. Final review: dispatched (opus, review-73ca9395..2f26bbc5.diff, deferred list final-deferred.md) Final review (opus): With fixes: 0 Critical, 5 Important (wizard rooms change drops carried/custom chores -> board doubled; the Projects card's Moving Out skips the prefill yet retires the Old House -> board doubled; prefill ignores moveDate; team floors pay the whole bonus to the claimer; board refresh / chore delete never settles a floor), 6 Minor Ruling: the floor bonus workers include every assignee of a paid team (collaborate) chore, the remainder still to the claimer — supersedes the T8 pool ruling's "workers = distinct userIds of paid entries" — the roof finale is a whole-family team chore and the credits already credit the team; one tap must not take the family's roof bonus — costs a recount if wrong. Ruling: the final fix wave takes all 5 Important plus minors 6 (hand the dialog's heroes, with sitting-out, to the wizard), 9 (reveal/credits owed only to heroes who had a chore in the run), 10 (no settle when none of the project's quests exists: a failed/empty read must not finish the tower) and 11 (sheet title via towerNameOf, stale comments) — each is a wrong outcome a family would see — costs one fix dispatch. Final review: minor (deferred to the owner): credits play the portal's music, not the tower's (spec 7.2); one plaza spot hides the trophy while a new project runs (spec 7.3 says the trophy stays); cross-device double bonus must be fixed before the site's __projects row lands (settle/wake/mark-seen only on a device that may write family records) Final review: fix wave dispatched (one dispatch, resumes the T12 implementer) Final fix wave: committed 4accda78 (9 items; 702 specs green; audit gate pass; e2e moving-temple 3/3 and project-tower 3/3 on :8123 after a cold-start rerun; server stopped); scoped re-review dispatched (sonnet) Ruling: settleInBackground runs on a pq- quest delete and when the quest page mounts or is returned to, not on every board reload — the board reloads on each search keystroke — costs a woken chore appearing one board load later. Final fix wave: minor (deferred): picking Moving Out on the card skips the board merge when it starts from a re-run list; the stairs' cleared display still reads all floors cleared on an empty quest read (display only, settles nothing); MyQuests' two call lines have no mounting spec; XpProjectWizardModal.vue at exactly 500 lines Final fix wave: gate after commit: typecheck clean; lint 0 errors (9 warnings) Final fix wave: re-review (sonnet): 9/9 addressed, no new Critical/Important Final fix wave: minor (deferred): a cleared Moving Out re-run skips the board carry; refilledChores re-suggests a floor whose carried chores the GM dropped (on a later rooms change); mount-time settle in MyQuests has no spec; a profile-less assignee on a paid team chore takes an unpaid share; onFloorReached stamps a floor awake on an empty read (wakes no quests; predates the wave) Gate: chemx typecheck clean, chemx lint 0 errors (run after 4accda78, the last commit); local chemx audit gate pass (ratchet) Rebased on origin/master 55c9fd39 (no overlapping files); after the rebase: chemx audit gate pass (ratchet), typecheck clean. Pushed feat/project-towers; PR #169 opened, auto-merge (squash) on; waiting on the Tests check. HexRatchet: master's 55c9fd39 (another session's hero-phone fix) added 3 hex fallbacks in _HeroPhone.scss (10 -> 13), turning HexLiteralRatchet red on master and PR #169 (which ran all specs: movingTemple.ts deleted). Fixed in the PR (fallbacks -> --gs tokens, Ionic vars still win); pushed. PR #169: Verify passed on d2a0bb4c; auto-merge squashed it into master as 8e8e4f26 at 2026-10-28T06:50Z. All 12 board tasks done. Workspace kept: the record still has to be copied into docs/superpowers/plans/2026-10-23-project-towers-record/ (docs submodule) before this worktree is removed.

Table of Contents


Home - See Pages

Developer Docs

Start Screen

User Management

My Portal

My Home

The City (Hometown)

The World Map

The Dungeon

Game Master

Settings

Support & Glossary

About XP

Clone this wiki locally