P1/P2 Domain-Gaps UI Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use
- [ ]. Review gates route through ECCflutter-reviewer(Dart/widgets). Source-of-truth for the gap list:.superpowers/sdd/p1p2-audit.md.
Goal: Wire the SP1-built SDK domain (assignment, home-room, watch-only, per-room assignees, per-member steps, tempBonus, goal lifecycle, multi-per-day, recommender) into the rewhaven app UI — the app currently consumes none of it.
Architecture: Follow the app's conventions exactly (scouted): pages under app/lib/inside/routes/authenticated/<name>/page.dart (@RoutePage, registered in router.dart → build_runner regen router.gr.dart); blocs extend AppBloc<E,S> with @JsonSerializable state + state.g.dart + sealed events; repositories (RepositoryBase) thin-delegate to client.* and register in RepositoriesAll; editors use showDsSheet + DS atoms (DsTextField, DsButton, DsSwitch, DsSegmented, DsEmojiChip/showDsEmojiPicker); the KidStep (setup/widgets/steps/kid_step.dart) is the form-body pattern. Tests use the flow harness (testAppBuilder + MocksContainer, template test/flows/setup_test.dart) + unit blocs.
Tech Stack: Flutter 3.44 / Dart 3.9 (FVM), auto_route, bloc, drift (SDK), design_system.
Global Constraints
- FVM. TRUE exit codes (
<cmd> > /tmp/t.txt 2>&1; echo "EXIT=$?"); never pipe tests to tail/grep. App tests:cd app && fvm flutter test. SDK:cd packages/client_sdk && fvm dart test. - Presentation imports ONLY the
client_sdkfacade — neverdrift/supabase. Repositories carry no domain logic (one-liner delegations). - Codegen: after adding/changing a bloc state or a route, run
dart run build_runner build --delete-conflicting-outputsfromapp/thengit diff --statand restore any clobbered hand-maintained.g.dart/.gr.dartsiblings from HEAD (known hazard). Newstate.g.dart+router.gr.dartregen is expected. - Watch-only members never appear in assignment pickers or earning/Today surfaces (use
client.assignableMembers()). - Async-throw tests use
await expectLater(future, throwsA(...)). - New blocs register their mock repo in
MocksContainer; flow tests mirrortest/flows/setup_test.dart. - Commit per task on
feat/p1p2-domain-gaps; do NOT push. Each task ends green (app analyze clean + tests pass).
Confirmed design decisions (2026-06-23, user)
- Both editors are full-page
@RoutePageroutes — chore editor AND member editor (not sheets).DsCard-sectioned forms. (Supersedes the earlier "member editor = sheet" note.) - Bottom nav = Home · Catalog · Household · More (4 tabs).
- Catalog tab → the chore/bounty (+ reward) catalog: the list + the chore editor entry point.
- Household tab → rooms + members/family management (the Members list + member editor).
- Home = Today; More = settings/misc.
- Execution prerequisite (new T1.5): verify the app's current bottom-nav shell and adjust it to Home/Catalog/Household/More before the tab pages attach. (
graphify query "bottom navigation tab shell router AutoTabsScaffold"to find the current shell.)
Task sequence (T0 done)
T0 ✅ SDK foundation (37fc2dc). Then: T1 repo CRUD → T2 chore editor (core) → T3 chore editor (assignment + per-room + watch-only) → T4 chore editor (per-member steps + tempBonus + multiPerDay + estimate) → T5 members page + member editor (home-room + watch-only) → T6 Today wiring (assignment filter + by-room grouping + per-member steps render) → T7 goal-request UI → T8 recommend dialog + app-level daily sweep caller.
Task 1: Repository CRUD methods (chores + members)
Files: Modify app/lib/outside/repositories/chores/chores_repository.dart, app/lib/outside/repositories/household/household_repository.dart; tests app/test/unit/chores_repository_test.dart, household_repository_test.dart.
Interfaces — Produces (consumed by T2–T7):
-
ChoresRepository:Future<Chore> createChore(Chore chore),Future<Chore> updateChore(Chore chore),Future<void> deleteChore(String id)— each a one-line delegation to the matchingclient_sdkfacade method. -
HouseholdRepository:Future<HouseholdMember> createMember(...),Future<HouseholdMember> updateMember(HouseholdMember m),Future<void> removeMember(String id),Future<List<HouseholdMember>> assignableMembers(),Future<HouseholdMember> setMemberHomePlace({required String memberId, required String? placeId})— delegate to facade (addMember/updateMember/removeMember/assignableMembers/setMemberHomePlace). -
Step 1 — confirm facade methods exist.
graphify query "Client facade createChore updateChore addMember updateMember removeMember". If a chore create/update/delete method is missing from theClientfacade, add it (delegating toChoreService) in a sub-step first, with an SDK test (runclient_sdksuite green). Record which existed vs added. -
Step 2 — failing tests: unit tests asserting each new repository method forwards to the matching
clientmethod (mockSdkClientProvider/MockClient). Run → FAIL. -
Step 3 — implement the one-line delegations (mirror existing methods in
chores_repository.dart). -
Step 4 — register mocks: add the new method stubs to
MockChoresRepository/MockHouseholdRepositoryinapp/test/util/mocks/repositories.dart. -
Step 5 — run app unit suite green + analyze clean; commit
feat(app): chore + member repository CRUD (P1/P2 T1).
Task 2: Chore editor — core (create/edit base fields)
Files: Create app/lib/inside/routes/authenticated/chore_editor/{page.dart,bloc.dart,state.dart,events.dart} + widgets/chore_editor_body.dart; modify router.dart (+ ChoreEditorRoute), the Chores/Today entry point (add an edit affordance — a trailing DsIconButton on TodayChoreRow + a "+" on the chores section that context.router.push(ChoreEditorRoute(choreId: ...))). Tests test/flows/chore_editor_test.dart.
Interfaces — Produces: ChoreEditorRoute({String? choreId}) (null = create). ChoreEditorBloc loads the chore (if editing) and saves via ChoresRepository.create/updateChore.
- Step 1 — failing flow test (mirror
setup_test.dart): openChoreEditorRoute(choreId: null), fill name/emoji/kind/frequency/tokenValue, tap Save →MockChoresRepository.createChorecalled with the builtChore. Run → FAIL. - Step 2 — bloc + state + events (
AppBloc,@JsonSerializablestate with the editable fields +status/errorMessage; eventsStarted(choreId?),FieldChanged…,Saved). Provide viawrappedRouteBlocProvider. - Step 3 — editor body (full page — see Design note):
DsTextField(title),DsEmojiChip+showDsEmojiPicker,DsSegmentedforChoreKind(expectation/bounty) +ChoreFrequency,DsTextField(tokenValue, numeric, shown only for bounty since expectation pays 0), minAge. MirrorKidStep(StatefulWidget owns controllers; stateless body). - Step 4 — route + entry affordance; regen
router.gr.dart(build_runner fromapp/, restore clobbered siblings). - Step 5 — run, expect PASS; analyze clean; commit
feat(app): chore editor — core fields (P1/P2 T2).
Design note (needs the screen design approved before building): editor is a full-page
@RoutePage(chores have many fields — assignment, steps, incentives — too tall for a sheet), styled withDsCardsections likeHomePage. Member editor (T5) can be ashowDsSheet(fewer fields). Confirm before T2 build.
Task 3: Chore editor — assignment + per-room + watch-only
Files: Modify the T2 chore editor (bloc/state/body); test additions.
- Step 1 — failing test: assigning members to a chore persists
assignedMemberIds; assigning members to a specific room persistsroomAssignees[placeId]; watch-only members are absent from the picker (the picker usesHouseholdRepository.assignableMembers()). Run → FAIL. - Step 2 — implement: a member multi-select (chips/checkboxes from
assignableMembers()) →assignedMemberIds(empty = all eligible); a per-room section (for eachPlace, a member multi-select →roomAssignees). Show the resolved union read-only (assignedMemberIdsUnion). - Step 3 — run PASS; commit
feat(app): chore editor — assignment + per-room assignees (P1/P2 T3).
Task 4: Chore editor — per-member steps + tempBonus + multiPerDay + estimate
Files: Modify the T2/T3 chore editor; test additions.
- Step 1 — failing tests: (a) per-member subtask override editor sets
stepsPerMember[memberId]andstepsForMember()resolves it; (b) a temp-bonus setstempBonusTokens+tempBonusUntil(today/thisWeek/untilOffviaDsSegmented) andeffectiveTokensreflects it; (c)frequency=multiPerDayreveals amaxPerDayfield; (d)estimateMinpersists. Run → FAIL. - Step 2 — implement the four field groups (a per-member steps sub-editor reusing the subtask list UI; tempBonus row; multiPerDay→maxPerDay reveal; estimate numeric field).
- Step 3 — run PASS; commit
feat(app): chore editor — steps overrides + tempBonus + multiPerDay + estimate (P1/P2 T4).
Task 5: Members page + member editor (home-room + watch-only + attributes)
Files: Create app/lib/inside/routes/authenticated/members/{page.dart,bloc.dart,state.dart,events.dart} (the net-new members list page) + member_editor sheet (showDsSheet); modify router.dart (+ MembersRoute) and add a nav entry (a "Family/Members" entry — likely from the Today header or a new tab/More entry). Tests test/flows/members_test.dart.
Interfaces — Produces: MembersRoute (lists members via watchMembers); a showDsSheet member editor saving via HouseholdRepository.createMember/updateMember + setMemberHomePlace.
- Step 1 — failing flow test: open MembersRoute → list shows members; tap a member → editor sheet; set home room (a
Placedropdown/segmented →setMemberHomePlace), toggle watch-only (DsSwitch→watchOnly), edit name/emoji/color → Save callsupdateMember. Run → FAIL. - Step 2 — implement the members list page (mirror a
DsSection/DsCardlist) + the editor sheet (mirrorKidStepbody insideshowDsSheet): name (DsTextField), emoji (DsEmojiChip), color, role, age, homePlaceId (places picker), watchOnly (DsSwitchwith helper text "tracked for goals, not assigned chores"). - Step 3 — route + nav entry; regen router; restore clobbered siblings.
- Step 4 — run PASS; analyze clean; commit
feat(app): members page + member editor — home-room + watch-only (P1/P2 T5).
Task 6: Today wiring — assignment filter + by-room grouping + per-member steps render
Files: Modify app/lib/inside/blocs/today_chores/{bloc,state}.dart + home/page.dart + widgets/today_chore_row.dart.
- Step 1 — failing tests: (a) a member sees only chores where they're in
assignedMemberIdsUnion(or unassigned = all eligible by minAge), excluding watch-only members entirely; (b) a "By room" grouping usesmembersForRoom/home-room (verify the existing By-kid/By-room toggle, if any, actually consumes the SDK — the audit says it doesn't); (c) a chore row renders the per-member resolved steps (stepsForMember) for the kid it's shown under. Run → FAIL. - Step 2 — implement the Today bloc filtering + grouping using the new facade methods; render resolved steps in
TodayChoreRow. - Step 3 — run PASS; commit
feat(app): Today — assignment filter + by-room grouping + per-member steps (P1/P2 T6).
Task 7: Goal-request UI (kid requests → parent approves)
Files: the goals surface (locate via graphify query "goal UI page bloc requestGoal approveGoal"); add a request affordance + a parent approval entry for GoalStatus.requested.
- Step 1 — failing test: a kid can request a goal (→
requestGoal, statusrequested); a parent sees requested goals and can approve (→approveGoal, statusactive). Mirror the existing approvals-queue pattern if present. Run → FAIL. - Step 2 — implement the request button (child) + the requested-goals approval list (parent), via the repository → facade (
requestGoal/approveGoal). - Step 3 — run PASS; commit
feat(app): goal-request → parent-approve UI (P1/P2 T7).
Task 8: Recommend dialog wiring + app-level daily tempBonus sweep
Files: the recommend surface (graphify query "recommend dialog ChoreRecommendation"); the app lifecycle/rollover hook.
- Step 1 — recommend dialog: confirm a recommend affordance calls
client.recommend()(the SP1 stub returns an emptyChoreRecommendation) and renders suggestions gracefully (empty state ok — the real AI service is a later sub-project). If no affordance exists, add a minimal one. Test the wiring (stubbed result). - Step 2 — daily sweep caller (SP1 follow-up b): call
client.sweepExpiredTempBonuses(DateTime.now())on the existing daily-rollover/app-resume hook (locate viagraphify query "daily rollover app resume lifecycle"). Test it fires once per day boundary. - Step 3 — run PASS; commit
feat(app): recommend dialog wiring + daily tempBonus sweep (P1/P2 T8).
Self-review
- Coverage: the 9 gaps + 3 SP1 follow-ups map to tasks — assignment/per-room (T3,T6), home-room/watch-only (T5,T6, +T0 filter), per-member steps (T4,T6), tempBonus (T4 + T8 sweep), goal lifecycle (T0 preconditions + T7), multiPerDay/estimate (T4), recommender (T8), goal preconditions (T0). ✅
- Net-new vs reuse: 2 editors + 1 members page net-new; editor frame/atoms/test-harness reused (scout-confirmed).
- Open design decision (gate before T2 build): chore editor = full page; member editor = sheet. Get user sign-off on the editor form factor + the new Members page placement before building T2/T5.
- Risk: router/state codegen clobbering hand-maintained siblings — Global Constraint covers restore. The "By room" toggle may exist but be unwired (T6 verifies).