Repository navigation
fix(wallet): retain card form ownership through dismissal - #753
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
NewWorldVenture
left a comment
There was a problem hiding this comment.
Independent current-main composition gate passed (2026-10-01 06:57 UTC).
Base: main 3dd5a7b5a407c7223ff3730de905a5f41d9fdb85. Composed exact heads #749 62a4a918, #751 07767475, #752 8bf35567, #753 3a564452, and ledger #750 3f52287c through normal merges. Local aggregate head a8397a3a90a496afed2aa5b5da04943ca81db7ab, tree 92cc33bba22e612f574112e8c3ded1d0281851ba.
- 1,158 unit/document checks across 29 files passed.
- 69 mounted Chromium/React 19 cases passed: 16 reveal + 20 pending-operation + 33 form cases; zero retries, skips, or flakes.
- Full TypeScript and changed-source lint passed.
- Independent source review verified exactly nine expected paths against the authorized PR blobs; all 5,546 other tracked entries match main, including authentication, migrations, catalogues, actions, and dependencies. All source-head ancestry retained.
The separate mounted suites use synthetic actions/provider SDKs; this is not live provider/iframe, server idempotency, cancellation of already-dispatched mutations, cross-tab/reload, or full Money workflow acceptance. Exact candidate hosted checks and post-integration CI/served SHA remain required. Main stays stable while its current post-merge E2E finishes.
Durable local evidence: money-ui-combined-0654-evidence/ (unit/browser/types/lint receipts and independent-source-review.json, SHA256 1db10d78ab1e7fd29ca5ea8beae59f4085326f43d7b06ef61798392fdf4bc212).
…rited claims Self-review of #811 (repair A and C), findings verified before fixing: - The owner was keyed on the client session (useAuthenticatedCacheScope). That identity settles in place (pending -> ready on the first agreeing bootstrap, no remount) and drops to 'unavailable' with a remount, so a claim filed under one owner was invisible after the switch and the control re-enabled while its request was still running. The owner is now the actor and family the page was rendered for (useApp().userId, familyId), fixed for the life of a mount; another account or family remounts the app with its own identity. The same actor signing in again sees their own in-flight order, which is still theirs. - A remounted view released an inherited physical-order or controls claim with no reconciliation (the unmounted form presents nothing, per #753), re-enabling Order over a stale list. The mounted view now re-reads (router.refresh) when a claim it did not make settles; its own claims are reconciled by their handlers as before. The unmounted instance's behaviour is unchanged. - Spec: forced dispatch used click() on a disabled button, which never reaches the handler; it now calls the control's React handler (as the existing specs do), so the held-key check itself is exercised. - ensureCardholder logs a failed or rejected duplicate re-read (still refusing), so an outage is not read as a uniqueness conflict. - The concurrency test's fallback timer is cleared, and both cases assert the barrier opened on both arrivals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXw2nu25BjyRfA6Fg3YiMS
… adopt only the exact duplicate cardholder (repairs A + C) (#811) * fix(cards): adopt only the exact duplicate cardholder a concurrent first order inserted JIMMY-SUPPORT-CARD-RETRY-20261001, repair C (owner review 5378576349). Two first orders for one child (Virtual and the physical dialog claim separately) both miss the cardholder lookup. The stable idempotency key gives both the same provider cardholder, so the second insert hit UNIQUE (family_id, member_id) and that order was refused, reported as "That already exists", with no card issued. On a 23505 only, ensureCardholder now re-reads (family_id, member_id) and adopts that row when its family, member and provider cardholder id all equal this call's. Any other outcome keeps the original refusal: a different provider cardholder, another family or member, no row, a returned re-read error (even beside exact data), a rejected re-read, and every non-23505 insert failure (which is not re-read at all). Tests (tests/stripe-cardholder-lookup.test.ts, sealed harness): exact adoption with the scoped re-read; five fail-closed re-read cases; a rejected re-read; three non-duplicate errors without re-read; and two concurrent first orders against the in-memory unique constraints (one row, both resolve; a different provider cardholder still refused). No schema, migration, provider call or business rule change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXw2nu25BjyRfA6Fg3YiMS * fix(cards): keep a pending card operation pending across a remount, per signed-in owner JIMMY-SUPPORT-CARD-RETRY-20261001, repair A (owner review 5378576349). MoneyCardsView held its pending claims in a per-instance ref. Leaving the page and returning before a request answered gave a fresh view with no claims and the same stale props, so a second freeze or a second card order for the same child went out while the first was in flight. Claims now live in a module registry keyed by owner: the signed-in user, that sign-in's session and the active family, from the existing useAuthenticatedCacheScope() partition. The same owner returning sees the operation still pending; another account, a new sign-in, another family, or an identity that is not settled ('pending', 'blocked', 'unavailable') never does. Unsettled views get a private per-mount owner, which is the previous behaviour. A claim is released under the owner that made it when its request settles, mounted or not, and only once. Completion of an already committed action (toast, refresh) is unchanged; the post-unmount announcement stays an open UX decision. Independent claim keys are unchanged, so Virtual and Physical for one child, and repeat orders after a settled one, still go out. Tests: new tests/e2e/money-cards-pending-owner.spec.ts (13 cases: remount for order and freeze, five identity changes, unsettled views, in-place identity change, completion while unmounted x3, virtual plus physical). The two existing view specs mock the new import with no settled identity, keeping their per-view semantics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXw2nu25BjyRfA6Fg3YiMS * test(cards): reset queued lookup answers per duplicate-insert case A case whose code path skips the re-read must not leave a queued answer for the next case; vi.clearAllMocks() does not clear once-values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXw2nu25BjyRfA6Fg3YiMS * fix(cards): own pending card work by actor and family; reconcile inherited claims Self-review of #811 (repair A and C), findings verified before fixing: - The owner was keyed on the client session (useAuthenticatedCacheScope). That identity settles in place (pending -> ready on the first agreeing bootstrap, no remount) and drops to 'unavailable' with a remount, so a claim filed under one owner was invisible after the switch and the control re-enabled while its request was still running. The owner is now the actor and family the page was rendered for (useApp().userId, familyId), fixed for the life of a mount; another account or family remounts the app with its own identity. The same actor signing in again sees their own in-flight order, which is still theirs. - A remounted view released an inherited physical-order or controls claim with no reconciliation (the unmounted form presents nothing, per #753), re-enabling Order over a stale list. The mounted view now re-reads (router.refresh) when a claim it did not make settles; its own claims are reconciled by their handlers as before. The unmounted instance's behaviour is unchanged. - Spec: forced dispatch used click() on a disabled button, which never reaches the handler; it now calls the control's React handler (as the existing specs do), so the held-key check itself is exercised. - ensureCardholder logs a failed or rejected duplicate re-read (still refusing), so an outage is not read as a uniqueness conflict. - The concurrency test's fallback timer is cleared, and both cases assert the barrier opened on both arrivals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXw2nu25BjyRfA6Fg3YiMS * test(cards): R5 after repair C — one cardholder, the same-millisecond card key refuses one With ensureCardholder adopting the exact duplicate (repair C), two first orders for one child no longer fail at the cardholder step. They now meet the inherited card-<wallet>-<ms> idempotency key, so in one millisecond the provider refuses the second with the action's own text. Same verified patch as #811 comment 5968508930. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXw2nu25BjyRfA6Fg3YiMS --------- Co-authored-by: Claude <noreply@anthropic.com>
Physical-card ordering and card-control saving could leave a disabled spinner after a rejected action, submit twice before a render or after reopening, and let an older successful request close a newer form with unsaved input. Pending ownership now survives child dismissal in the mounted card view. Each opening has an identity, so stale responses cannot close or toast into a newer interaction; completed successful mutations still refresh the mounted card data.
Only
money-cards-view.tsxand a paired mounted browser spec change in this follow-on. It stacks on #751 and retains its separate guard correction by normal merge. Actions, authorization, provider helpers, catalogues and database code are unchanged. Closing a form does not cancel a dispatched mutation; no automatic retry or cross-tab/reload idempotency is claimed.Validation:
a-write-result-is-never-discarded.test.ts), independently reproduced on unmodified fix(wallet): preserve pending ownership for card actions #751 source. The failure receipt is retained; no scanner guard was weakened.Audit scope:
COMPONENT-CF064547E2DE/PAGE-346/UI-ROUTE-0343. This is synthetic UI/action-boundary verification, not live provider or financial workflow acceptance. Root retains the final audit ledger.