Shipped 2026-05-18. Tracked followup from Slice F (2026-05-15) — the Payment Methods admin test
had to fall back to a hard page.goto() reload because the just-saved row didn't
appear in the rendered list on prod cold cf-workers, even though revalidatePath +
the client's router.refresh() both fired and the DB row was confirmed.
page.goto fallback) · 51/51
regression green — 56/56 total.
Applies Slice J's architecture (client owns list state, action returns mutated row,
router.refresh() dropped) to three Settings tabs:
payment-methods, payment-diff-reasons, modifiers.
All three confirmed save → row visible in-place on get-coffee prod.
3 actions refactored to return the mutated row:
createPaymentMethodAction + updatePaymentMethodAction return
{ ok, row: PaymentMethodView };
createDiffReasonAction + setDiffReasonActiveAction return
{ ok, row: DiffReasonView };
createAttributeAction + createAttributeValueAction return
{ ok, row: AttributeView | AttributeValueView }. New exported view types
project the full DAO row to the shape the client list renders.
Update + delete actions return { ok } — the client knows what
it sent on update (no re-fetch needed) and what id it asked to delete. Simpler contract,
fewer round-trips.
3 clients refactored to own list state: useState<X[]>(initialX)
initialized from props; mutation callbacks splice/replace/filter local state directly.
router.refresh() and useRouter removed throughout the affected
components.
Modifiers (the gnarliest): nested attribute↔value relationship. Top-level
ModifiersClient holds the canonical attributes: AttrRow[] state
with six mutation helpers (addAttr/patchAttr/removeAttr/addValue/patchValue/removeValue)
passed down to AttrCard/AttrModal/ValueModal. When a
new attribute lands, it gets values: [] spread on so the nested shape stays
consistent.
revalidatePath dropped from all CRUD actions on these three
tabs. setProductAssignmentsAction kept its revalidatePath("/products")
(different page, different list). The cashier-callable
createDiffReasonForCloseShiftAction also dropped revalidatePath — the cashier
is in the close-shift dialog, not the Settings list.
Schema name typo in the prod test. First prod run was 4/5: the diff-reasons
check failed because the test queried commerce.payment_diff_reasons but the
table actually lives in cafe.payment_diff_reasons (the prefix
commerce was guessed from the column-level test test-id commerce/diff-reason-…
— there's no commerce table). Fixed the test SQL, re-ran 5/5. Worth checking: the
commerce. prefix exists for shops and pin_identities;
payment_diff_reasons always was a cafe. table.
page.goto)Raw: result.json
| test-phase1-prod.mjs | 11/11 |
| test-phase2-sso-outdoor-prod.mjs | 6/6 |
| test-phase2-cafe-multishop-prod.mjs | 6/6 |
| test-m1-prod.mjs | 10/10 |
| test-r7-prod.mjs | 14/14 |
| test-r8-prod.mjs | 4/4 |
Modified:
lib/actions/payment_methods.ts ·
lib/actions/payment_diff_reasons.ts ·
lib/actions/product_attributes.ts ·
app/(authed)/settings/payment-methods/client.tsx ·
app/(authed)/settings/payment-diff-reasons/diff-reasons-client.tsx ·
app/(authed)/settings/modifiers/modifiers-client.tsx
Commit: 403a743
shopId ASC, sortOrder ASC, name ASC)
would place them elsewhere. Reload corrects this. Admin UX — acceptable.revalidatePath("/products"). Different page, different list, no documented
bug — out of scope for K.