H-phase — post-G-board enhancements (implementer brief)
Scoped on feat/mvp1-personas-authz @ 8850b13 (= main). Binding spec:
docs/decisions/2026-07-02-h-phase-enhancements.md.
G-2 account switcher is shipped (docs/decisions/2026-07-03-account-switcher.md);
H-5 may consume its substrate. Build loop: TDD vertical slices (one behavior → failing test → minimal impl → green).
Terminology (owner-stated, from spec): “kids” = the member role only. Restrictions are
role-based (member vs admin/helper), not member-kind-based.
Working copy: .superpowers/sdd/h-phase-plan.md mirrors this file for the autonomous controller.
Recommended build order
| Order | Item | Why |
|---|
| 1 | H-1 | No migration; unblocks step presentation everywhere (Today + editor + later print). |
| 2 | H-4 | No migration; fixes broken room print (placeId never written) + adds steps to PDF. Pairs with H-1 icons. |
| 3 | H-3 | Reuses H-1 step editor atoms; self-scoped write on existing stepsPerMember column. |
| 4 | H-5 | Hard-depends on G-2 (satisfied); folds G-2 keyboard-focus a11y debt. Presentation-heavy. |
| 5 | H-2 | New persistence + request→approve lifecycle (largest SDK/schema slice). |
| 6 | H-6 | IA restructure — biggest UX shift; consumes shipped E-6 earn + G-8 spend surfaces. Do last. |
Cross-cutting invariants (from controller prompt)
- One data path: Bloc → Repository → Client → Service → Adapter.
- Dual gate on privileged mutations (service + RLS).
viewingAs is presentation-only — never actingMemberId (app/test/unit/authz_invariant_test.dart).
- TDD: vertical red→green slices; flow tests mock repositories (
app/test/README.md).
- Suite baselines at last deploy: app 406 pass, SDK 864 pass — counts must not drop.
H-1 — Subtask icons + prominent visibility
Current state (verified)
| Layer | Location | Finding |
|---|
| Model | packages/client_sdk/lib/src/models/subtask.dart:14-22 | icon field exists (String? icon key). Stored in chore subtasks JSONB — no migration. |
| Schema | infra/supabase/migrations/20260612000002_tier0_domain.sql | subtasks jsonb not null default '[]'. |
| Today UI | app/lib/inside/routes/authenticated/home/widgets/today_chore_row.dart:219-229 | Steps render as • ${step.name} bullets — never reads step.icon. |
| Chore editor | app/lib/inside/routes/authenticated/chore_editor/widgets/chore_editor_body.dart:576-580 | New steps created with id + name + sortOrder only — no icon. No icon picker UI. |
| DS picker | packages/design_system/lib/src/molecules/ds_emoji_picker.dart | Shipped (Twemoji font + curated grid). Generic household emoji — not the POC subtask catalog. |
| POC parity | Chore_app/packages/client_sdk/lib/src/models/sdk/subtask_icons.dart | Curated SubtaskIconCategory list (~hundreds of emoji + labels + search tags). Port this catalog. |
Verdict
| SDK | Migration | Authorizer | Size |
|---|
Optional (port catalog as pure-Dart data in client_sdk or design_system) | No | No new capability | M |
Slices (TDD)
| Slice | Size | Content | Gate |
|---|
| H1-S1: catalog + render | S | Port POC subtaskIconCategories (+ searchSubtaskIcons) into rewhaven (prefer design_system data-only module — model-agnostic). Unit test: search returns expected emoji for a tag. | DS tests green |
| H1-S2: Today shows icons | S | today_chore_row.dart + timer row: when step.icon set, render emoji via Twemoji/DS typography (same path as chore emoji). TDD: widget/golden test with one subtask carrying icon key. | app green |
| H1-S3: editor picker | M | Extend _MemberStepsEditor: icon chip per step → opens curated subtask icon sheet (keyword search like POC; can reuse DsEmojiPicker shell with subtask dataset OR dedicated DsSubtaskIconPicker). Persist Subtask.icon on save (already serializes). Flow test: admin adds step with icon → Today shows it. | app green + deploy |
| H1-S4: default/shared subtasks | S | Wire icon picker for shared subtasks list (not only stepsPerMember overrides) if editor supports shared steps today — verify and close gap. | app green |
Test sketch
- DS: catalog search, emoji renders in golden.
- App unit: step list widget shows emoji when
icon non-null.
- Flow: chore editor save → Today row shows icon (headless screenshot optional).
POC adopt/adapt
Adopt curated catalog + search UX from POC subtask_icons.dart. Adapt to DsSheet/DsEmojiPicker patterns already in rewhaven DS.
H-4 — Print review (rooms + steps)
Current state (verified)
| Layer | Location | Finding |
|---|
| Print engine | app/lib/inside/print/printable_chore_list.dart | Member + place list selection works. PDF _choreRow prints chore name only — no subtasks, explicit comment that emoji omitted (:136-138). |
| Place filter | app/lib/inside/print/printable_chore_list.dart:38-39 | choresForPlace filters on Chore.placeId — correct IF placeId populated. |
| placeId write path | app/lib/inside/blocs/chore_editor/bloc.dart:319-365 | createChore / updateChore never pass placeId. Editor has per-room member overrides (roomAssignees) but no primary room tag. |
| Schema | infra/supabase/migrations/20260612000011_chore_place.sql | place_id uuid column exists on chores. Drift mirrors it. |
| Print UI | app/lib/inside/routes/authenticated/home/widgets/print_menu.dart | Room picker + buildPlaceList wired — room lists empty in practice because placeId always null. |
| POC parity | Chore_app/app/lib/inside/routes/authenticated/chores/widgets/chore_print_sheet.dart | Scope (kid/room/all), _includeSubtasks toggle, today/week range, subtasks under chore rows in PDF. |
Verdict
| SDK | Migration | Authorizer | Size |
|---|
No (optional: expose placeId on create/update if not already on repository signature) | No | No | M |
Slices (TDD)
| Slice | Size | Content | Gate |
|---|
| H4-S1: placeId on chores | M | Add room/place picker to chore editor (reuse app/lib/inside/routes/authenticated/shared/room_picker.dart); thread placeId through bloc → repository → SDK. TDD: save chore with place → getChores() returns matching placeId; place print list non-empty. | app + SDK green |
| H4-S2: steps in PDF | M | Extend _buildPdf / _choreRow to indent subtasks under each chore (text labels; optional icon as Unicode if font supports, else name-only per current B&W policy). Resolve steps via same stepsForMember logic as Today. Unit test in printable_chore_list_test.dart. | app green |
| H4-S3: room grouping in member print | S | Optional POC parity: group member list by room when placeId set (or separate section headers). | app green + deploy |
Test sketch
- Unit:
choresForPlace returns chores after H4-S1 seed.
- Unit: PDF bytes contain subtask names when chore has steps.
- Flow: print menu → room list → triggers print with count > 0.
H-3 — Self-scoped step customization
Current state (verified)
| Layer | Location | Finding |
|---|
| Model | Chore.stepsPerMember + editor | Map memberId → List<Subtask> — already persisted (infra/supabase/migrations/20260612000014_chore_steps_per_member.sql). |
| Admin editor | app/lib/inside/routes/authenticated/chore_editor/widgets/chore_editor_body.dart:259-278 | Parents edit any member's steps. |
| Member path | — | No member-facing step editor today. |
| Precedent | app/lib/inside/blocs/self_profile/bloc.dart | Self-pin at save — edits authenticated member only (G-4 pattern). |
Verdict
| SDK | Migration | Authorizer | Size |
|---|
Yes — guarded updateStepsForMember(choreId, memberId, steps) or narrow updateChore patch | No | Self-scoped: member may write only stepsPerMember[actingMemberId]; admin override unchanged | M |
Slices (TDD)
| Slice | Size | Content | Gate |
|---|
| H3-S1: SDK guard | M | Service method: reject if memberId != actingMemberId unless actor holds admin chore-edit capability. RLS: chore row update policy must allow member to patch own steps JSON only (may need migration if RLS blocks member writes today — verify live policy before assuming no migration). | SDK green |
| H3-S2: member UI | M | Today chore drill-down or profile path: member edits own steps only (reuse H-1 step row + picker). Cannot touch default subtasks or sibling keys. | app green + deploy |
Stop-and-ask
If Postgres RLS currently denies member UPDATE on chores, H3-S1 needs a narrow RLS policy (additive migration) — scoper could not confirm without live policy read; implementer must check infra/supabase/migrations/*chores* policies before slice commit.
H-5 — Today page rework (viewing-as)
Current state (verified)
| Layer | Location | Finding |
|---|
| G-2 substrate | app/lib/outside/repositories/selected_member/selected_member_repository.dart | Shipped. Catalog already consumes it. |
| Today | app/lib/inside/routes/authenticated/home/page.dart | Master view — all members' chores; approvals block always rendered for parental viewer (:463+). Does not read SelectedMemberRepository. |
| Deferred question | .superpowers/sdd/g2-plan.md §7 | Lens-vs-subject: parent viewing-as-child — master-with-preview vs child-limited surface. Must decide in H5-S1. |
| Carried debt | account-switcher.md | Switcher keyboard-focus a11y (focus ring, trapped menu, announce) — not built. Fold into H5 or touch switcher in same deploy. |
Verdict
| SDK | Migration | Authorizer | Size |
|---|
| Unlikely (presentation filter) | No | Approvals visibility = display predicate on Authorizer.can(selectedMember, helper) — hide only, never grant | L |
Slices (TDD)
| Slice | Size | Content | Gate |
|---|
| H5-S0: product decision | — | Owner picks lens semantics (recommend: tailored view — selected member sees only their items; parent-as-self keeps master view). Record in ADR appendix. | decision logged |
| H5-S1: Today filters by viewingAs | M | TodayChoresBloc / page consumes SelectedMemberRepository; when viewingAs ≠ self and viewer has viewHouseholdAll, filter groups to selected member. Flow test: switcher → Today shows one kid. | app green |
| H5-S2: approvals gate | S | Hide approvals section unless selected identity has helper (or authenticated viewer is parental and viewing self — preserve master oversight). | app green |
| H5-S3: switcher a11y debt | M | Focus ring + keyboard nav on AccountSwitcherChip sheet per g2-plan §3.4. | app green + deploy |
Test sketch
- Flow: account switcher select child → Today lists only that child's chores.
- Unit: approvals widget absent when viewingAs = member without helper.
H-2 — Members recommend/request chores
Current state (verified)
| Layer | Location | Finding |
|---|
| Spec | H-2 | Member cannot create chores; can request → admin approves → real chore. Mirror goal-request lifecycle. |
| Goal precedent | packages/client_sdk/lib/src/services/economy_service.dart requestGoal | Status transition + Capability.requestGoal / approveGoalRequest. |
| Stub models | packages/client_sdk/lib/src/models/chore_suggestion.dart | Ephemeral AI suggest shape — not a persisted member request. |
| UI stub | app/lib/inside/routes/authenticated/catalog/widgets/recommend_sheet.dart | Catalog “recommend” UX — not the H-2 approval queue. |
| POC | Grep Chore_app | No chore-request table precedent found — greenfield lifecycle (like goal requests in rewhaven). |
Verdict
| SDK | Migration | Authorizer | Size |
|---|
Yes — ChoreRequest aggregate + requestChore / approveChoreRequest / rejectChoreRequest | Yes — new table + RLS + status enum | New capabilities: requestChore, approveChoreRequest (mirror goals) | L |
Slices (TDD) — high level
| Slice | Size | Content |
|---|
| H2-S1: schema + model | M | Migration chore_requests (household-scoped, status, payload fields, append-only audit). Drift + cloud adapter. |
| H2-S2: service + authz | M | Service guards + RLS dual gate; Authorizer capabilities. SDK tests with MockClient. |
| H2-S3: member request UI | M | Request form (member role); no create chore button. |
| H2-S4: admin approve UI | M | Approvals or Admin queue → promotes to createChore. Flow test end-to-end. Deploy. |
Detailed slice breakdown deferred to H-2 implementer dispatch (after H-1/H-4/H-3/H-5 land).
H-6 — IA restructure (Earn + Rewards tabs; Catalog removed)
Current state (verified)
| Layer | Location | Finding |
|---|
| Shell | app/lib/inside/routes/router.dart:111-123 | Tabs: Home / Catalog / Household / More. |
| E-6 earn | app/lib/inside/routes/authenticated/catalog/page.dart | Unified earn list (chores + bounties) with filters — in Catalog today. |
| G-8 spend | app/lib/inside/routes/authenticated/catalog/page.dart + rewards/activities routes | Activities + rewards with spend gates (app/lib/inside/routes/authenticated/spend_gates/spend_gates_sheet.dart) — partially split (admin editors separate). |
| Spec | H-6 | New Earn tab (chores+bounties), Rewards tab (activities+rewards), remove Catalog; admin-only editing with deep links; extract SpendGatesRepository. |
Verdict
| SDK | Migration | Authorizer | Size |
|---|
| Minimal (mostly routing/repos) | No | Admin-only edit affordances — existing manageCatalog / admin guards | XL |
Slices (TDD) — phased
| Slice | Size | Content |
|---|
| H6-S1: SpendGatesRepository extraction | M | Move spend-gate reads behind repository (per spec); no UX change. |
| H6-S2: Earn tab route | L | New shell tab; migrate catalog earn half; deep links; flow tests. |
| H6-S3: Rewards tab route | L | Migrate spend half; retire catalog spend UI. |
| H6-S4: Remove Catalog + redirect | M | Delete CatalogRoute; guards + bookmarks redirect; admin edit deep links. Deploy. |
Do not start H-6 until H-1/H-4/H-5 stabilize Today + print + viewing-as — IA shift deletes the catalog surface users know today.
Stop-and-ask (owner)
- H5 lens semantics (H5-S0): tailored child-only Today vs master-with-highlight when viewing-as child? Recommendation: tailored (matches spec bullet “only THEIR items”).
- H3 RLS: confirm whether members can PATCH chore rows today — may add a migration slice.
- H4 print icons: B&W PDF with emoji vs text-only subtasks? Recommendation: text + optional Unicode (match current
printable_chore_list.dart comment policy unless owner wants emoji font embedding).
Commits / docs map
| Doc | Role |
|---|
2026-07-02-h-phase-enhancements.md | Product spec (committed) |
| This file | Implementer brief (committed) |
.superpowers/sdd/h-phase-plan.md | Controller working copy (gitignored) |
| Per-slice ADRs | Add under docs/decisions/ when a slice changes load-bearing behavior |
Unverified claims — re-verification summary
| Claim (lost scoping session) | Verdict |
|---|
| H-1/H-4 need no migration | Confirmed for H-1 and H-4 core work. H-3 may need RLS migration — TBD. H-2 needs migration. |
| Subtask.icon never written | Confirmed — field exists, editor omits it. |
| Today uses plain bullets | Confirmed — today_chore_row.dart:229. |
| placeId dead in editor | Confirmed — bloc save path omits placeId; schema column exists. |
| Print omits steps/rooms | Confirmed — room path empty; PDF has no subtask rows. |