task 9 context - xopherdeep/do-it-for-the-xp GitHub Wiki

Task 9 context from the lead (decisions the brief cannot know)

Earlier tasks you consume (on this branch; read with chemx read <file> --outline / --symbol=<name>):

  • T2 src/lib/services/core/packs/projects/projectTypes.ts (+ springCleaning.ts, movingOut.ts, index.ts): PROJECT_TYPES, ProjectType (phases with share, defaultDays, finale, towerName, element), ProjectTypeId.
  • T3 src/lib/services/projects/projectSchedule.ts: phaseWindows(phases, startOn, endOn), minimumEndOn(phaseCount, startOn).
  • T4 src/lib/services/projects/projectChores.ts: ProjectHouse, ProjectRoom, ProjectChore, suggestChores(type, house, heroes), choresFromLastRun(last, heroes).
  • T6 src/lib/services/projects/projectQuests.ts: ProjectTowerInput (projectId, type, house, chores, windows, now, heroes?, ...).
  • T7 src/lib/databases/ProjectDb.ts: getProjectDb().getActive(), .lastOfType(typeId).
  • T8 src/lib/services/projects/projectService.ts: startProject(input, rewardText) (refuses while a project is active).
  • The wizard shell: src/components/organisms/WizardShell/XpWizardShell.vue and src/lib/services/wizard/wizardSteps.ts; find an existing wizard that uses them (chemx q -g "XpWizardShell") and follow it.
  • The Moving House dialog src/app/SideMenu/XpSettings/components/MovingHouseModal.vue (rooms markup ~L40-77, and how it gets the family's heroes as PackHero[]).

Rulings (decided; follow them):

  1. Build the project id in ONE exported helper in projectWizard.ts: projectIdOf(typeId, startOn) = ${typeId}-${startOn}; the modal uses it for ProjectTowerInput.projectId.
  2. Pass the family's heroes (PackHero[], the way MovingHouseModal gathers them) into suggestChores, choresFromLastRun AND ProjectTowerInput.heroes: the heroes drive approvals (a little/kid chore of difficulty 5+ with no grown-up on it needs approval). Never drop them.
  3. Copy before editing: PROJECT_TYPES suggestion objects are shared singletons, and the chores list the draft holds must be fresh copies, so a GM edit never changes the type's data.
  4. A re-run (choresFromLastRun) has no house parameter. The builder already re-homes a chore whose room is gone to the whole house (projectTower.ts ~L211, room id "house", named "The Whole House"); the wizard does the same in the draft (drop nothing silently) so the chores step shows the GM where each chore lands; the finale stays on the roof. Reuse one exported id rather than a third copy of "house" (projectChores.ts ~L74 and projectTower.ts ~L54 both hold it today: export it from projectChores.ts and import it).
  5. Every phase, including the last, needs at least one chore (the last phase's biggest chore guards the Boss Key): the chores step's rule covers every phase of the chosen type; the roof's finale is not a phase chore and is not counted there.
  6. Dates: startOn is today (local YYYY-MM-DD); validate that endOn is a real YYYY-MM-DD date before calling phaseWindows/minimumEndOn (an unparsable date would otherwise read as today); refuse an endOn before minimumEndOn(phaseCount, today) with a message naming that date.
  7. rewardText (the family reward) is asked on the dates step or the build step (your pick, say which in the report); Build is blocked while it is empty.
  8. The size slider snaps to the Fibonacci steps: reuse FIBONACCI_STEPS / nearestDifficulty from src/lib/services/core/packs/packCommon.ts (no new copy of the steps). An edited assignee keeps the chore's size; do not clamp it (the GM's choice wins).
  9. "Finish or abandon first": is the active project's tower name. With an active project the modal shows that line and no Build button; it must not call startProject.
  10. All visible copy goes through the copy catalogue (src/lib/services/copy/catalog/, three voices simple / adventure / classic, read with copyFor(key, voice, vars); find the voice the settings screens use). No em dashes in new copy; no hex colour literals in .vue/.scss (use --gs-* tokens: a ratchet spec counts hex literals). Controls (sliders, chips, action buttons) sit at the bottom of the phone screen; content reads above.
  11. The new entry in PacksSettings.vue sits beside openBuilder("moving"), behind allowedToManagePacks(); do not change the Moving House entry (Task 12 does).
  12. One small extra (the board gate on T8 flags it): in src/lib/services/projects/projectProgress.ts:246, name the condition before the if (e.g. const isRunning = project.status === "active"; if (!isRunning) return null;), the audit's CONTROL_FLOW_INLINE_BOOLEAN rule. Run tests/unit/projectProgress.spec.ts after it. Name your own compound conditions the same way in the new files.
  13. T8's startProject(input, rewardText) throws while a project is active; the modal reads getProjectDb().getActive() first and shows rule 9's line instead of Build, so the throw is only a guard.
⚠️ **GitHub.com Fallback** ⚠️