Repository navigation
progress
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).
| 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.
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: 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: ${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: 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: ${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: 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:
- Architecture
- Component-Library
- Hooks-Reference
- Temple-System
- Battle-Field-Architecture
- current_context - For AI/Human Handoff
- User Menu
- General Settings
- Family Settings
- Sound Settings
- Notification Settings
- Reward Settings
- Party Settings
- Theme Settings
- Chore Settings