diff --git a/.planning/phases/18-payment-gateway/18-UI-SPEC.md b/.planning/phases/18-payment-gateway/18-UI-SPEC.md index 0dd0b83..ef2ddbb 100644 --- a/.planning/phases/18-payment-gateway/18-UI-SPEC.md +++ b/.planning/phases/18-payment-gateway/18-UI-SPEC.md @@ -1,7 +1,8 @@ --- phase: "18" slug: "payment-gateway" -status: draft +status: approved +reviewed_at: "2026-10-12" shadcn_initialized: false preset: none created: "2026-10-12" @@ -849,7 +850,7 @@ The PHP `_stripe_info.htm` instructions (dashboard steps, test card numbers) mov - **Editable (Placed, when the API allows item changes):** - the Quantity cell becomes a stepper: `−` button, count, `+` button, each 2.75rem; - `−` is disabled at 1; - - the item cell gains a `pg.item.remove` text button (danger), which opens `PgConfirm`; + - the item cell gains a `pg.item.remove` text button (danger), which opens `PgConfirm`. With one item left, the button is not rendered: an order is never empty, and the customer abandons it with "Cancel order"; - each change calls the API and re-renders from the response, with the row at `opacity: .6` and `aria-busy` while in flight. - **Narrow screens (≤ 40rem):** the table stays a table and scrolls horizontally inside its wrapper. The total row stays visible because it sits in its own `tfoot`. @@ -921,7 +922,7 @@ Applicable state considerations were resolved with the ui-consideration-probe on | `PgOrderStatus` | static-content | | `PgOrderList` | list-collection | -Every applicable pair is covered, backstopped or dismissed with a reason, and 0 are unresolved. Rows group pairs that share a resolution. The copy for empty and error rows lives in the Copywriting Contract. +A post-verification re-run of the probe (with the kinds above as overrides) raised 109 applicable pairs. The pairs the first pass left without a row were added at the end of the table on 2026-10-12, and the user decided the PgCart last-item rule. Every applicable pair is covered, backstopped or dismissed with a reason, and 0 are unresolved. Rows group pairs that share a resolution. The copy for empty and error rows lives in the Copywriting Contract. | Category | Element(s) | Status | Resolution / Reason | |----------|------------|--------|---------------------| @@ -976,6 +977,33 @@ Every applicable pair is covered, backstopped or dismissed with a reason, and 0 | zero-one-many | `PgCart` | ✅ covered | One item: `−` is disabled at count 1; many: rows in cart order; a shipping row when present | | populated | `PgOrderStatus` | ✅ covered | The tone bar, title, body, code and total, the read-only cart and the per-state actions in L5 | | empty / loading / error / zero-one-many | `PgOrderList` | ✅ covered | The empty title and body; 3 skeleton rows; `pg.error.load` with Retry; pagination when there is more than one page | +| loading / error / zero-one-many | Money list column | ➖ dismissed | The cell is part of its host list; loading, errors and paging belong to that list (see the orders, payments, shippings rows) | +| partial | Money list column (no currency) | ✅ covered | The amount renders without the ISO code, as in the money field partial row | +| loading / long-text | Danger record actions | ✅ covered | The existing RecordActions dialog keeps both buttons disabled while the POST runs; labels are fixed 1–3-word copy and the confirm body wraps inside the dialog | +| error / long-text | Preview Edit rule | ➖ dismissed | A pure function of the loaded schema with the fixed "Edit" label; it has no failure path and no variable text | +| overflow / zero-one-many | Orders list | ✅ covered | The existing DataTable scrolls horizontally; 20 rows per page with the existing pager; zero uses the empty copy | +| loading / error | Order and payment previews | ✅ covered | The existing PreviewView skeleton and error panel; the hint partial uses the PartialHost skeleton and failure box | +| populated | Order preview | ✅ covered | The hint stats, the card with cart, payments and details, and the per-state footer actions in P2 | +| overflow / long-text | Payment preview | ✅ covered | `description` and the `_payment_data` values wrap with `overflow-wrap: anywhere`; the uuid `` breaks anywhere; stats wrap in the existing `summer-stats` grid | +| populated | Payments list | ✅ covered | Localized state and operator names, right-aligned money columns, newest first | +| partial | Payments list | ✅ covered | An empty `native_amount` or `description` shows the muted "—" | +| overflow / zero-one-many | Payments list | ✅ covered | The existing DataTable horizontal scroll and pager; zero uses the empty copy | +| loading / error | Shippings and quote items | ✅ covered | The existing ListView and FormView skeletons, load errors and 422 field errors | +| partial | Shippings and quote items | ✅ covered | An empty currency is set to the default on save (P5); a legacy shipping without a logo shows the fileupload's empty state; an empty repeater shows its `add_custom_field` prompt | +| overflow / zero-one-many / long-text | Shippings and quote items | ✅ covered | The existing DataTable scroll and pager; names wrap in text cells; repeater rows stack | +| partial | Quote cart element | ✅ covered | A refused cell change (empty description, invalid net) restores the last saved row; the read-only state (quote placed) drops the inputs and the add row | +| long-text | Quote cart element | ✅ covered | A long description scrolls inside its input; in read-only cells it wraps with `overflow-wrap: anywhere` | +| empty / loading / error / long-text | Settings forms | ✅ covered | Unset fields show their defaults (P8 timeouts 7/2/7/14) or are empty; the existing SettingsFormView skeleton and load error; a 422 shows inline errors and the tab error chip (F4); comments wrap under their fields | +| long-text | Navigation | ✅ covered | Labels are 1–3 words in EN and PL ("Płatności i zamówienia" is the longest); the existing sidebar label handling applies | +| overflow / long-text | `PgOrderPage` | ✅ covered | The column is capped at `--pg-width`; the order code and long values break with `overflow-wrap: anywhere` | +| partial / long-text | `PgCheckout` | ✅ covered | With no shippings the fieldset is not rendered; empty bank fields are omitted (L6); method names wrap (L3); the alert block wraps its text | +| loading / error | Payment/shipping methods | ➖ dismissed | The methods come with the order load owned by `PgOrderPage`; selection is local until Pay, so the cards have no own request | +| populated / partial / overflow | Payment/shipping methods | ✅ covered | Cards with logo, name and cost (L3); a method without a logo shows its name only; the auto-fill grid wraps into more rows | +| empty | `PgCart` | ✅ covered | A Placed order always has at least one item: **with one item left, the Remove button is not rendered** and the customer uses "Cancel order" to abandon the order | +| loading / error | `PgCart` | ✅ covered | The changed row at `opacity: .6` with `aria-busy`; a refused change re-renders from the last response and shows `pg.error.process` in the alert block | +| populated / partial | `PgCart` | ✅ covered | The L4 table; the shipping row only when present; read-only outside Placed | +| overflow / long-text | `PgOrderStatus` | ✅ covered | The body wraps; the `dl` uses `auto-fit` columns; the order code breaks with `overflow-wrap: anywhere`; the cart scrolls in its wrapper | +| populated / partial / overflow | `PgOrderList` | ✅ covered | The L7 table; an empty total shows "—"; the table sits in an `overflow-x: auto` wrapper below 40rem | --- @@ -992,12 +1020,16 @@ Every applicable pair is covered, backstopped or dismissed with a reason, and 0 ## Checker Sign-Off -- [ ] Dimension 1 Copywriting: PASS -- [ ] Dimension 2 Visuals: PASS -- [ ] Dimension 3 Color: PASS -- [ ] Dimension 4 Typography: PASS -- [ ] Dimension 5 Spacing: PASS -- [ ] Dimension 6 Registry Safety: PASS -- [ ] Dimension 7 Inventory Provenance: PASS +- [x] Dimension 1 Copywriting: PASS +- [x] Dimension 2 Visuals: PASS +- [x] Dimension 3 Color: PASS +- [x] Dimension 4 Typography: PASS +- [x] Dimension 5 Spacing: FLAG (non-blocking). The quote cart element's 36px inputs, 6px cell radius and 10px select radius are not in the fixed dimensions list; the planner should list them there with a reason. +- [x] Dimension 6 Registry Safety: PASS +- [x] Dimension 7 Inventory Provenance: PASS -**Approval:** pending +Checker notes for the planner: +- D-07 says "minor units" while F1 sends decimal strings at scale 4 (`"85.4193"`). Confirm the wire shape as part of RESEARCH A1 before plan 1. +- F4–F7 are not in RESEARCH's plan 1 list. Put them in plan 1, or raise them at the plan-count checkpoint. + +**Approval:** approved 2026-10-12