feat(family): Manage Family (part 2: blocs, navigation, notifications) - #542
Open
plusmobileapps wants to merge 10 commits into
Open
plusmobileapps wants to merge 10 commits into
plusmobileapps wants to merge 10 commits into
Conversation
Adds the client/family/manage/impl module and the first of its three BLoCs: the list of families the user belongs to, with the "New family" dialog. The list loads on every resume rather than in init, so a family renamed, left or deleted on the detail screen is reflected on the way back. Only the first load shows a spinner — a later refresh that fails keeps the list already on screen rather than replacing it with an error. Creating a family opens it straight away: a brand-new family has nobody in it, so inviting is the only thing left to do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One family's members and invites, with the actions the viewer's role allows. Every per-row action is derived from FamilyPermissions rather than hand-set, so the UI only offers what the server would accept: an owner manages admins and deletes, an admin invites and removes members and can leave, a member can only leave. The server stays the authority — this just avoids offering a button that would fail. Leaving and deleting take the family away from the viewer, so they close the screen instead of reloading it. Failures that have no field to attach to (role changes, rename, leave, delete) surface as an app-wide toast; a rejected invite stays inline under the email field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The stack behind the Manage Family entry: the family list, pushing a detail screen per family. Back and Closed both pop to the list. Closed needs no extra signal to refresh it — the list reloads on resume, which popping triggers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the Manage Family row, shown while the manage_family flag is on. Unlike Notifications, the row appears in every auth state rather than only for a signed-in, non-anonymous user: it is the feature's only entry point, and hiding it from a signed-out user would leave them nothing to tap. Root navigation takes them through sign-in on the way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Routes the More tab's Manage Family row into the root stack. A signed-in user goes straight there. Anyone else — signed out, or on an anonymous guest session — is sent to sign-in first and lands on Manage Family once it succeeds, whether that finished at the credentials screen or at the OTP screen. A guest counts as signed out here because family membership is keyed on an account email, which they don't have. Backing out of that sign-in forgets the destination, so an unrelated sign-in later doesn't strand the user on a screen they no longer asked for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds AppNotification.FamilyInvite and merges pending family invites into the same stream that already backs the Notifications screen and the More-tab badge. Each one-shot source is now caught on its own, so a family fetch that fails while the user is offline no longer blanks the grocery and recipe-book invites alongside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Previews for the family list and detail screens, wrappers in screenshot-test, and the recorded references. The detail previews are generated per role through FamilyPermissions — the viewer is whichever accepted row holds that role — so the owner, admin and member references show exactly the actions each of them really gets rather than a hand-picked set that could drift from the rules. Also refreshes the Notifications references for the new family invite card and adds one for the flagged Manage Family row in the More tab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the family list and detail robots, a Manage Family row on MoreRobot, and a flow test through runRootBlocTest covering: the row hidden while the flag is off, opening the list from the More tab, opening a family, a member seeing neither Delete nor the invite form, creating a first family landing on its detail screen, and a signed-out user being sent to sign-in instead. Families are online-only, so TestFamilyRepository replaces the production repository in the test graph — without it every Manage Family screen would render its offline error state. FakeFamilyRepository enforces the same permission rules the server does, so the flows behave realistically. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extends the existing invite-email trigger to families and teaches the edge function 'family' as a third kind. Two things differ from the other kinds. A family invite is created by the invite_family_member RPC, which inserts a new row or flips a previously rejected one back to pending — so the trigger fires on INSERT OR UPDATE OF status and, on update, only when the row has just become pending, otherwise accepting an invite or changing a role would re-send the email. And the copy reads "invited you to join the family" rather than "to collaborate on", with a matching heading. The per-kind parent table and wording move into one lookup rather than a pair of ternaries, so a fourth kind is a single entry. Needs `supabase functions deploy send-invite-email` alongside the migration. Operator setup (the project_url / invite_hook_secret Vault secrets) is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`a_signed_out_user_is_sent_through_sign_in_first` passed on JVM but failed on iOS with "LocalInteropContainer not provided": tapping the row composes the auth screen, whose PlusAutofillTextField.ios renders a UIKitView, and runComposeUiTest provides no interop container. No other common UI test opens that screen, so nothing had hit this before. The test now stops at asserting the row is offered while signed out, which is the part this layer is actually good at proving. The routing it used to reach for — sign-in first, landing on Manage Family once it succeeds, and the anonymous and abandoned-flow variants — is already covered by four RootBlocTest cases that drive the outputs directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes Manage Family, picking up the checklist left in #541. That PR landed the schema, the data layer and the screen contracts; nothing was wired into the app. This wires it all up.
Remaining items from #541
client/family/manage/impl: bloc and view-model implementations with unit tests.screenshot-testwrappers, reference PNGs).impl-robotsand a flow test throughrunRootBlocTest.supabase db push) — left to you, see below.send-invite-email.Notable decisions
The list reloads on resume, not in
init.FamilyListBlocImplrefreshes vialifecycle.doOnResume, so a family renamed, left or deleted on the detail screen is reflected on the way back without the navigation bloc having to poke the list. Only the first load shows a spinner; a later refresh that fails keeps the list already on screen rather than swapping it for an error.Per-row actions are derived, not hand-set.
FamilyDetailViewModelbuilds everyMemberItemthroughFamilyPermissions, so the UI can only offer what the server would accept. The same helper generates the per-role snapshot previews, so the owner/admin/member references can't drift from the rules.Manage Family is offered in every auth state. Unlike the Notifications row (signed-in, non-anonymous only), this one shows whenever the flag is on. It's the feature's only entry point, so hiding it from a signed-out user would leave them nothing to tap.
RootBlocImplremembers the destination, opens sign-in, and lands them on Manage Family once auth succeeds — at the credentials screen or at the OTP screen. Backing out of that sign-in forgets the destination, so an unrelated sign-in later doesn't strand them somewhere they didn't ask for. An anonymous guest counts as signed out here: family membership is keyed on an account email, which they don't have.Notification sources are now caught independently. Adding families as a third source meant a single
runCatchingaround the merge would let one unreachable source blank the others. Each is caught on its own now.The family invite email trigger fires on INSERT or UPDATE. Grocery and recipe-book invites are plain inserts;
invite_family_memberinstead flips a previously rejected row back topending. The trigger guards on the row having just become pending, so accepting an invite or changing a role doesn't re-send the mail. The edge function's per-kind parent table and wording moved into one lookup, so a fourth kind is a single entry.Still needed before this can ship
supabase db pushfor20260916_add_families.sql(from feat(family): Manage Family (part 1: schema, data layer, screens) #541) and20260921_family_invite_email.sql. I left these unapplied — prod's migration history is unreconciled with the CLI, so that's a call for you to make.supabase functions deploy send-invite-email.manage_familyaboverollout_percent = 0when you're ready.Test plan
./gradlew test— green, including 26 new view-model tests, 8 root-navigation tests and 6 flow tests../gradlew :client:ui:screenshot-test:validateDebugScreenshotTest— green; 12 new Manage Family references plus refreshed Notifications and More-tab ones, all visually inspected../gradlew :client:composeApp:compileDebugKotlinAndroid :client:composeApp:compileKotlinJvm :client:composeApp:compileKotlinIosSimulatorArm64🤖 Generated with Claude Code