Repository navigation
fix(web): friend page, settle up and one-on-one tab fixes from dogfooding - #35
Merged
Merged
Conversation
expenseBetween is one expense's step of pairwiseDebts between two people: what the other owes (or is owed) on it, 0 when both are in it but nothing passes between them, null when one is not in it. Summed with their payments it is their pairwise debt, which a property test checks. The one-friend export's between column now uses it instead of its own copy, and pgTAP gets the same two cases for private.pairwise_debts. Co-authored-by: Ferrin <177692050+Ferrin-Y@users.noreply.github.com> Co-authored-by: noorelmosrati <182049385+noorelmosrati@users.noreply.github.com>
Everyone on the balances tab (and the balances preview on import) reads as one sentence per row for screen readers, with the viewer's own grammar: "You are owed $1,535.80", "You owe $12.00 in total". The small label above the amount follows too. Co-authored-by: Ferrin <177692050+Ferrin-Y@users.noreply.github.com> Co-authored-by: noorelmosrati <182049385+noorelmosrati@users.noreply.github.com>
Paying part of a debt showed the amount still owed as the big red headline. The headline is now the payment, in green, with what is left underneath in plain type. Settling in full still ticks down to $0.00 with confetti. Co-authored-by: Ferrin <177692050+Ferrin-Y@users.noreply.github.com> Co-authored-by: noorelmosrati <182049385+noorelmosrati@users.noreply.github.com>
The hidden 1:1 group is stored as "Direct" and that leaked into the CSV Group column
and file name, the expense and payment back links, the activity feed ("Just the two
of you", plus joining the tab as news) and notifications ("New expense in Owen
Owner"). groupDisplayName in core names a tab after the other person, groupTitle uses
it on the web, backups export it that way, and notifications say "your tab with Owen".
Push copy never names a group for a tab, and email already leaves the name out.
Co-authored-by: Ferrin <177692050+Ferrin-Y@users.noreply.github.com>
Co-authored-by: noorelmosrati <182049385+noorelmosrati@users.noreply.github.com>
The balance card said "Plus balances in EUR" with no amount. It now lists what you are owed and owe in each other currency, unconverted: "Plus you are owed €12.00 and you owe £5.00". Its group rows name a one-on-one tab after the friend. Co-authored-by: Ferrin <177692050+Ferrin-Y@users.noreply.github.com> Co-authored-by: noorelmosrati <182049385+noorelmosrati@users.noreply.github.com>
Answers whether a new expense with a person would go in, by the same rules the writes apply (an existing tab refuses only the blocked side; a new one needs a contact and no block either way). A plain no, like not_a_friend, so a block is never named. Co-authored-by: Ferrin <177692050+Ferrin-Y@users.noreply.github.com> Co-authored-by: noorelmosrati <182049385+noorelmosrati@users.noreply.github.com>
Each expense row on a friend's page showed the viewer's whole lead in the expense
("Concert, you lent $200" when the friend owes $160). Rows now show expenseBetween:
what that expense left between the two of you, or "nothing between you" when you both
owe someone else. With simplify off they add up to the group's standing; with it on the
standing is the simplified debt and the rows are the history behind it.
The menu also exports the one-friend spreadsheet (the same file as on Me), and Add
expense is off with a short reason when you blocked them or can_split_with says no.
Co-authored-by: Ferrin <177692050+Ferrin-Y@users.noreply.github.com>
Co-authored-by: noorelmosrati <182049385+noorelmosrati@users.noreply.github.com>
musa-kal
force-pushed
the
fix/friends-settle
branch
from
November 14, 2026 07:02
e9147ab to
636fb36
Compare
This branch was successfully deployed
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.
Intent
Fixes from the full-app dogfood pass:
Must keep: money in integer minor units, additive migrations, no new anon-callable functions, i18n for every string.
What Changed
expenseBetween, which is one expense's step of the pairwise-debt rule, with matching pgTAP cases in23_payments. The one-friend CSV export uses the same value, and its row is now also in the friend page menu. Add expense is disabled with a reason when a new expense would be refused. That reason comes from a newcan_split_with(person)RPC:security definer, callable byauthenticatedandservice_roleonly, covered by30_can_split_withand01_schema. When the friend blocked you, it shows the genericnot_a_friendmessage, so it never says who blocked whom.groupDisplayNamenames a one-on-one tab after the other person, never "Direct" or its stored name. It is used on the home card, CSV and backup exports, back links, the activity feed and in-app notifications.netLabelhelper, so screen readers hear "You are owed" / "You owe" for the viewer.Risk Assessment
✅ Low: The diff covers all seven intent items: an additive migration with authenticated-only grants, integer money via expenseBetween (property-tested against pairwiseDebts), i18n in every locale, and both prior-round fixes (can-split invalidation on block and unblock, hiding only group.created and member.joined in 1:1 tabs) verified in code and tests with no remaining sibling sites.
Testing
Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
apps/web/src/features/me/blocked-people.tsx:22- ThepeopleKeys.canSplit(friendId)query is never invalidated when a block changes. Global staleTime is 30s (apps/web/src/lib/query-client.ts:13). Repro: Ava blocks Ben, opens Ben's page (can_split_with returns false and is cached; the youBlocked note shows), unblocks him under Me within 30s, then returns to Ben's page. Themy_blockscache is invalidated, so the page skips the youBlocked note. It then reads the cachedcanSplit.data === falseand shows the generic 'You can only split with friends.' with Add expense disabled until the cache goes stale. The same missing invalidation is in friend-page.tsx:368-371, whererun()invalidates blocks/friends after blockUser but not can-split. Fix: also invalidate['potluck','people','can-split'](orpeopleKeys.canSplit(id)) in both places.apps/web/src/features/activity/activity-feed.tsx:71- Simplification: the activity feed now also drops everymember.*event of a one-on-one tab (item.verb.startsWith('member.')), where before it only droppedgroup.created. Intent item 3 asks for a tab to be named after the friend in activity, not for activity rows to be hidden, so no intent requirement needs this filter. It also hides any later member event in a tab (e.g. a member.left when a friend closes their account) along with the opening ones. Recommend removing themember.clause, or narrowing it to the join events emitted when the tab is opened, if the author wants to keep it.packages/api/src/plans.ts:156- fetchHomeBalances now embedsgroup_members (profiles (id, display_name))for every group the viewer belongs to, though onlyis_directgroups need the friend's name. Payload on the home screen grows with total membership across all groups. Acceptable at current scale; noted as a tradeoff.🔧 Fix applied.
1 info still open:
packages/api/src/plans.ts:156- fetchHomeBalances now embedsgroup_members (profiles (id, display_name))for every group the viewer belongs to, though onlyis_directgroups need the friend's name. Payload on the home screen grows with total membership across all groups. Acceptable at current scale; noted as a tradeoff.⏭️ **Test** - skipped
Step was skipped.
✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.