Skip to content

task 10 report

Xopher edited this page Oct 28, 2026 · 1 revision

Task 10 report: the tower in play (header clock, fanfare, Hometown)

Implementer @sonnet-pt10. Status: DONE_WITH_CONCERNS (see Concerns).

What I built

Pure rules (all with specs):

  • src/lib/services/projects/floorClock.ts: floorClockLine, bonusOfferLine (brief signatures), plus floorClockState, floorClockView(project, floorKey, today, voice) (what the header shows), phaseNameOf (phase name, "The Roof", or the floor key). "due today" on the due day, "late" after, "cleared" for a settled floor whatever its dates say (ruling 7); the offer is floorMultiplier(window, today).toFixed(1) with the multiplication sign, null when cleared or x1.
  • src/lib/services/projects/towerView.ts (new, not in the brief): currentFloorOf, clearedFloorKeys, floorRowsOf, unseenClearedFloors, withFloorSeen, endDateProblem. Kept apart from projectProgress.ts (364 lines) so the settle rules file stays small.
  • src/lib/services/projects/towerPrompts.ts (new): floorClearedPrompt, abandonPrompt (reveal-prompt options, in-world, three voices).
  • projectProgress.ts: wakeOnArrival(project, floorKey, quests, now) (ruling 1, pure part; null when the project is not running, the floor is unknown or already awake).
  • projectIds.ts: freshProjectId(projectId, taken) (ruling 11).
  • projectRecord.ts: ProjectFloor.seenBy?: string[].
  • projectService.ts: onFloorReached(floorKey, now) (wake for the family, then the settleReady pass, in the service's turn queue), readTower() (running project + floors that read cleared), markFloorSeen(heroId, floorKey); startProject builds under a fresh id and forgets the heroes' saved walks; abandonProject forgets them too. ProjectStores gained progress (TempleProgressDb) and engine (forgetTempleRun, lazily imported so the service does not load the temple engine until a project starts or ends). The private floorNameOf now uses phaseNameOf (one "The Roof").
  • Copy: src/lib/services/copy/catalog/projectTower.ts registered in the catalogue index (clock, offer, floor-cleared, abandon question, sheet lines, spot cue; simple/adventure/classic; no em dashes).

Screens (thin wiring):

  • templeGrounds/useProjectTower.ts (new composable) + TempleFloorClock.vue + TempleGrounds.ts/.vue. Inert for every temple that does not start with gen-project- (no read, no watch, isFloorCleared answers undefined). For a tower: on each ionViewDidEnter and on each floor change it takes its turn to (1) onFloorReached(level), (2) templeSystem.refreshQuestRooms (always: the run read the board when it opened, and the next floor's chores woke after that, so without it 2F's rooms would still read asleep), (3) readTower(), (4) tell each unseen cleared floor. The header clock sits in a slim row under the toolbar (not inside the toolbar, whose title is absolutely laid out), with data-cy="temple-floor-clock", temple-floor-clock-line, temple-floor-offer hooks for Task 12's walk.
  • useRoomOutcomes.ts: new optional dep isFloorCleared; getLiveContext adds floorCleared to the context only when it is defined (other temples' context object is byte-identical).
  • HomeTown.ts/.vue: project-tower spot (default x 50, y 46: the free middle of the plaza, above the Old House at 50/72), towerSpot.ts (pure: name, cue "Now on 2F: Deep Clean", scene id), ProjectTowerSheet.vue + _ProjectTowerSheet.scss.
  • ProjectTowerSheet.vue: tower name, current floor, its clock and offer, every floor with a state (cleared / open / asleep), "Enter the tower"; Game Master (canAccessGameMaster()) also "Move the end date" (native date input min = today, endDateProblem refuses before today and not-a-day, then extendProject(newEnd, today); a service refusal shows the "too soon" line) and "Abandon project" (reveal-prompt confirm, then abandonProject). Actions sit in the bottom bar (thumb reach). It calls settleReadyFloors() when it opens. Empty state is a named slot empty (default a plain line): Task 11 puts the trophy there (and HomeTown must also show the spot for a cleared project; today it hides it with no active project, ruling 7/R5).
  • Voxel: buildingShapes.ts project-tower (plain id = 4 floors, wind) and project-tower-<floors>-<element> ids via projectTowerSceneId; PROJECT_TOWER_THEMES for the nine elements; one wall band per floor (1 to 8, band height shrinks so 8 still fit the camera: top <= 16, width +-3.5), stepped roof and pennant on top. XpBuildingModel.vue knows tower scene ids (no shop sublabel, the click is left to the caller). BGM untouched.

How "seen" is remembered (ruling 3)

On the project record: ProjectFloor.seenBy (hero ids), written by markFloorSeen before the prompt shows (so a second refresh cannot show it twice). It is family data like reward.seenBy, so it travels in a backup and in Family Sync; a hero on a second device is not shown it twice. The roof is never listed (its clear is the project's finish; T11's reveal tells it, and a cleared project is no longer getActive() anyway).

Context ruling 4 (deleted key-guard chore): already works, now pinned

A sealed mini-boss room whose questIds all point to deleted quests: resolveRoomQuest -> resolvePicks finds no live pick (questRooms.ts ~L267-279), no template, so the resolution is emptyLair (L333). syncQuestRooms (engine/core/dungeons/templeSystem/questRooms.ts) puts every non-lurking quest room (empty lairs included) into clearedRooms, so the room does not seal. roomActions (RoomInteractions.ts L242-258) builds the quest sheet with fight: false and isDropWaiting(room, ctx, false) (roomInteractions/treasure.ts L140) is true while the Boss Key drop is not taken, so the sheet offers "Take it" (takeDrop -> showDropReveal -> claimDropItems, which hands out the key). No production change. tests/unit/TowerKeyGuard.spec.ts (new, passes against unchanged code) pins: the empty lair offers only "Take it" with drop: "waiting", it counts as cleared after the board sync, and a key guard with one live chore keeps its Fight and offers no key. (I tried the case inside QuestRoomSheet.spec.ts first and moved it out: that file would have crossed the 500-line budget.)

Context ruling 3 and 4 summary

  • 3: Floor cleared! is a reveal prompt (floorClearedPrompt, title "Floor cleared!", the phase name, the early bonus line with the multiplier when above x1, one Onward), shown once per hero per floor; remembered in ProjectFloor.seenBy.
  • 4: no code needed, see above.

Other rulings

  • 1: arrival wake through the project (onFloorReached), family-wide, never the per-hero wake map; settleReadyFloors() runs on tower load (inside onFloorReached) and when the Hometown sheet opens.
  • 2: floorCleared in the live context = the tower's answer for the live level: true when every chore of the floor was paid (an empty floor reads true), false until the tower is read (and when it cannot be read), true when no project is running any more (so a hero is never trapped in a finished tower), undefined for other temples.
  • 5: startProject and abandonProject clear every hero's saved walk in the tower's temple (getProgressForTemple then clearProgress, so no hero list is needed) and drop the engine's in-memory run; a failure is warned and never stops the project.
  • 11: startProject suffixes ~2, ~3 when the id is held by any record in ProjectDb, and builds the tower and the record under it.

TDD evidence

RED (specs written first; every one failed to run because the modules/exports did not exist, ts-jest diagnostics):

  • chemx test tests/unit/floorClock.spec.ts -> Test suite failed to run ... floorClock.spec.ts:6:94 import { bonusOfferLine, ... } from "@/lib/services/projects/floorClock"
  • towerView.spec.ts -> failed to run (towerView missing; Property 'seenBy' does not exist on type 'ProjectFloor')
  • towerPrompts.spec.ts, projectIds.spec.ts -> failed to run (modules/exports missing)
  • projectProgress.spec.ts -> Test suite failed to run (wakeOnArrival missing)
  • projectService.spec.ts -> TS2305 ... has no exported member 'onFloorReached'
  • buildingVoxel.spec.ts -> TS2305 ... no exported member 'projectTowerSceneId'
  • TempleGroundsModules.spec.ts -> TS2305 ... no exported member 'markFloorSeen'
  • ProjectTowerSheet.spec.ts -> Test suite failed to run (component missing)
  • TowerFloorLock.spec.ts: written after the 3-line useRoomOutcomes edit; I proved it by disabling the edit: 2 failed out of 4 ... Expected: true / Received: undefined, then restored it.
  • TowerKeyGuard.spec.ts: characterization of existing behaviour, green on first run against unchanged production code (ruling 4).
  • towerSpot.spec.ts: written together with towerSpot.ts (no RED captured; it is a 20-line pure function).

GREEN: one combined run, all 14 specs below: NODE_PATH=$PWD/node_modules/.pnpm/node_modules chemx test tests/unit/floorClock.spec.ts tests/unit/ProjectTowerSheet.spec.ts tests/unit/TempleGroundsModules.spec.ts tests/unit/projectProgress.spec.ts tests/unit/projectService.spec.ts src/components/organisms/BuildingModel/__tests__/buildingVoxel.spec.ts tests/unit/voices.spec.ts tests/unit/HexLiteralRatchet.spec.ts tests/unit/towerView.spec.ts tests/unit/towerPrompts.spec.ts tests/unit/projectIds.spec.ts tests/unit/towerSpot.spec.ts tests/unit/TowerKeyGuard.spec.ts tests/unit/TowerFloorLock.spec.ts -> All tests passed (303 passed in 77870ms). Also green (existing specs that load files I changed): HomeTown.spec, RoomOutcomesApply.spec, TempleBattleReturnDrop.spec, TempleSystemCleared.spec (74 passed, run before the last small edits) and a final rerun of those plus XpProjectWizardModal.spec and QuestCompletion.spec after the last edits: All tests passed (153 passed).

Files changed

New: src/lib/services/projects/{floorClock,towerView,towerPrompts}.ts, src/lib/services/copy/catalog/projectTower.ts, src/app/Console/TempleGrounds/components/TempleFloorClock.vue, src/app/Console/TempleGrounds/templeGrounds/useProjectTower.ts, src/app/Console/MyPortal/HomeTown/components/{ProjectTowerSheet.vue,_ProjectTowerSheet.scss,towerSpot.ts}; specs tests/unit/{floorClock,towerView,towerPrompts,projectIds,towerSpot,ProjectTowerSheet,TowerKeyGuard,TowerFloorLock}.spec.ts. Modified: projectProgress.ts, projectService.ts, projectIds.ts, projectRecord.ts, copy/catalog/index.ts, TempleGrounds.ts/.vue, useRoomOutcomes.ts, HomeTown.ts/.vue, buildingShapes.ts, XpBuildingModel.vue; specs projectProgress.spec.ts, projectService.spec.ts, TempleGroundsModules.spec.ts, buildingVoxel.spec.ts.

Self-review

  • Every new/changed source file passes chemx check with no introduced hazards (named compound conditions; no inline anonymous types; the sheet's emits are the runtime array form).
  • No hex literals in the new .vue/.scss (kit --xp-app-* and --gs-* tokens only); HexLiteralRatchet green. Copy has no em dashes (spec asserts it for the lines it builds; also grepped).
  • I could not run chemx typecheck (rule). I read the .vue scripts for types by hand (jest does not type-check .vue): the clock prop is typed at runtime (PropType) so the SFC compiler never has to resolve an aliased import type; TempleGrounds.ts passes Ref<string>/Promise<boolean> exactly as useTempleEntry already gets them.
  • The bottom nav is untouched. Controls sit in the bottom bar of the sheet.

Concerns (why DONE_WITH_CONCERNS)

  1. TempleGrounds.ts was exactly 500 lines and is now 507 (7 lines of wiring: 2 imports, 1 component, 2 call, 1 dep, 1 spread). The audit flags files over 500 (LINE_BUDGET_FILE in chemx-ratchet.json, currently 39): if the gate counts it as a new file, the cheapest fix is moving the equipment-hand block (itemCatalog try/catch + useHand, ~22 lines) into a small composable. I did not split it myself, as asked.
  2. projectService.spec.ts (703 lines), projectProgress.spec.ts (574) and buildingShapes.ts (906) were already over 500; they grew but were already counted. The new service cases could move to their own spec if the owner wants the spec back under budget.
  3. buildingShapes.ts carries the tower shape (+72 lines) as the brief says; a sibling file would be tidier.
  4. The header clock is a row under the toolbar, not inside it beside the room name: the toolbar's title is laid out absolutely (the map and key buttons on both sides), and a second element there would overlap it.
  5. Task 11 must also change HomeTown so the tower spot shows for a cleared project (it hides today with no active project, per R5) and fill the sheet's empty slot.
  6. The FloorArrival.settled pass pays floor bonuses with new Date() as the log time; same as settleReadyFloors.
  7. Not verified in a browser (no dev server run, per the rules); the sheet and clock were exercised through specs only.

Task 10 fix round 1 (@sonnet-pt10)

Status: DONE. Base: 60cce69c. Five items, all fixed; no git writes.

1. TempleGrounds.ts back under the line budget (Important)

Moved the three route-param helpers (isBlankParam, toStartPosition, toStartLevel, byte for byte) from the top of TempleGrounds.ts into src/app/Console/TempleGrounds/templeGrounds/routeStart.ts and import the two it uses. No behaviour change (a move). New characterization cases for both helpers in tests/unit/TempleGroundsModules.spec.ts (RED first: routeStart missing, Test suite failed to run; GREEN after the move).

  • wc -l src/app/Console/TempleGrounds/TempleGrounds.ts: 490 (was 507; 500 before Task 10).
  • chemx audit src/app/Console/TempleGrounds --no-triage --unroll: Line Budgets & Monolith Decomposition PASS 0, Largest File: TempleGrounds.ts (490 lines), Line Budgets & Monolith Decomposition (0 violations). The one hazard it lists for TempleGrounds.ts is the existing HOOK_SATURATION at line 84.

2. The running project must belong to the temple on screen (Important)

  • projectService.onFloorReached(templeId, floorKey, now, stores): returns null, wakes and settles nothing, when no project runs or project.templeId !== templeId.
  • useProjectTower: passes its temple id; after readTower() it keeps the snapshot only when read.project.templeId === temple, otherwise treats it as no project running: no clock, no offer, no prompt, markFloorSeen never called, and isFloorCleared answers undefined (no floorCleared in the live context, so the stairs behave as in any temple).
  • Decision to flag: the lead's text equates "another temple's project" with "no project running" and says floorCleared undefined. My first version answered true when no project ran at all (so a hero could climb a finished tower). I made both cases undefined, so a finished tower's stairs-up stay shut. No UI route re-enters a finished tower today, and the roof (the only place a hero stands when a project finishes) has no stairs up; if the owner wants finished towers climbable, the place to say so is isFloorCleared in useProjectTower.ts (one line).
  • Specs: projectService.spec.ts "is quiet for a hero in another project's tower" (Spring Cleaning runs with its 1F paid and ready; onFloorReached("gen-project-moving-out", "2F", ...) is null, the record is untouched, 2F still asleep, 1F not settled, no bonus paid). TempleGroundsModules.spec.ts "takes the running project of another tower for no project" (floorClock null, isFloorCleared undefined on both floors, no prompt although a cleared floor is unseen, markFloorSeen not called, the service asked as gen-project-moving-out), and the "project is over" case now expects undefined. All existing call sites moved to the new signature.

3. In-world words out of code, into the copy catalogue

src/lib/services/copy/catalog/projectTower.ts gained projectTower.roofName, .keeper ("Tower Keeper"), .onward, .abandon.yes (simple "Give up", else "Abandon"), .abandon.no (simple "Keep going", else "Keep it"), .spot.fallbackName ("Project tower"), each with simple, adventure and classic. Used by floorClock.phaseNameOf(typeId, floor, voice = FALLBACK_VOICE) (the ROOF_NAME constant is gone; the wallet label in projectService keeps the adventure default, "Floor bonus: The Roof", as before), towerView.floorRowsOf(project, voice), towerPrompts (speaker, Onward, the two buttons), towerSpot (fallback name, and the roof in the cue in the hero's voice), the sheet (rows named in the hero's voice) and useProjectTower (the cleared-floor prompt names the phase in the hero's voice). Specs: floorClock.spec (roof name in each voice and by default), towerPrompts.spec (speaker and labels from the catalogue, simple "Give up"), towerSpot.spec (fallback name per voice, roof cue in classic), towerView.spec (roof row in classic). Voices spec green (simple lines <= 10 words, no duplicate key).

4. Tokens only in TempleFloorClock.vue

background: var(--gs-window, rgba(0, 0, 0, 0.7)) is now background: var(--gs-window). Searched the three new style sources (TempleFloorClock.vue, ProjectTowerSheet.vue, _ProjectTowerSheet.scss) for rgba( and #: none left. HexLiteralRatchet green.

5. Exact bonus count in projectService.spec

The "arrival and a payment together settle the floor once" case now asserts paidBonus called exactly heroes.size times, the paid heroes equal the workers, and floor 1F has clearedAt and bonusPaidAt set (it passed vacuously before).

Covering run

NODE_PATH=$PWD/node_modules/.pnpm/node_modules CHEMX_AGENT_ID=@sonnet-pt10 chemx test tests/unit/TempleGroundsModules.spec.ts tests/unit/projectService.spec.ts tests/unit/projectProgress.spec.ts tests/unit/towerSpot.spec.ts tests/unit/towerPrompts.spec.ts tests/unit/floorClock.spec.ts tests/unit/ProjectTowerSheet.spec.ts tests/unit/TowerFloorLock.spec.ts tests/unit/voices.spec.ts tests/unit/towerView.spec.ts tests/unit/HexLiteralRatchet.spec.ts -> All tests passed (266 passed in 72883ms). chemx check on every changed source file: 0 hazards.

Files changed in this round

New: src/app/Console/TempleGrounds/templeGrounds/routeStart.ts. Modified: TempleGrounds.ts, templeGrounds/useProjectTower.ts, projectService.ts, floorClock.ts, towerView.ts, towerPrompts.ts, HomeTown/components/towerSpot.ts, HomeTown/components/ProjectTowerSheet.vue, TempleGrounds/components/TempleFloorClock.vue, copy/catalog/projectTower.ts; specs TempleGroundsModules, projectService, floorClock, towerView, towerPrompts, towerSpot.

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