Skip to main content

P1/P2 Domain-Gaps UI Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use - [ ]. Review gates route through ECC flutter-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.dartbuild_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_sdk facade — never drift/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-outputs from app/ then git diff --stat and restore any clobbered hand-maintained .g.dart/.gr.dart siblings from HEAD (known hazard). New state.g.dart + router.gr.dart regen 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 mirror test/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 @RoutePage routes — 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 matching client_sdk facade 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 the Client facade, add it (delegating to ChoreService) in a sub-step first, with an SDK test (run client_sdk suite green). Record which existed vs added.

  • Step 2 — failing tests: unit tests asserting each new repository method forwards to the matching client method (mock SdkClientProvider/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/MockHouseholdRepository in app/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): open ChoreEditorRoute(choreId: null), fill name/emoji/kind/frequency/tokenValue, tap Save → MockChoresRepository.createChore called with the built Chore. Run → FAIL.
  • Step 2 — bloc + state + events (AppBloc, @JsonSerializable state with the editable fields + status/errorMessage; events Started(choreId?), FieldChanged…, Saved). Provide via wrappedRoute BlocProvider.
  • Step 3 — editor body (full page — see Design note): DsTextField (title), DsEmojiChip+showDsEmojiPicker, DsSegmented for ChoreKind (expectation/bounty) + ChoreFrequency, DsTextField (tokenValue, numeric, shown only for bounty since expectation pays 0), minAge. Mirror KidStep (StatefulWidget owns controllers; stateless body).
  • Step 4 — route + entry affordance; regen router.gr.dart (build_runner from app/, 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 with DsCard sections like HomePage. Member editor (T5) can be a showDsSheet (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 persists roomAssignees[placeId]; watch-only members are absent from the picker (the picker uses HouseholdRepository.assignableMembers()). Run → FAIL.
  • Step 2 — implement: a member multi-select (chips/checkboxes from assignableMembers()) → assignedMemberIds (empty = all eligible); a per-room section (for each Place, 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] and stepsForMember() resolves it; (b) a temp-bonus sets tempBonusTokens + tempBonusUntil (today/thisWeek/untilOff via DsSegmented) and effectiveTokens reflects it; (c) frequency=multiPerDay reveals a maxPerDay field; (d) estimateMin persists. 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 Place dropdown/segmented → setMemberHomePlace), toggle watch-only (DsSwitchwatchOnly), edit name/emoji/color → Save calls updateMember. Run → FAIL.
  • Step 2 — implement the members list page (mirror a DsSection/DsCard list) + the editor sheet (mirror KidStep body inside showDsSheet): name (DsTextField), emoji (DsEmojiChip), color, role, age, homePlaceId (places picker), watchOnly (DsSwitch with 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 uses membersForRoom/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, status requested); a parent sees requested goals and can approve (→ approveGoal, status active). 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 empty ChoreRecommendation) 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 via graphify 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).