fix(store): rebuild the checkout line so its columns share an axis #91
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/checkout-line-layout"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Reported as messy. I measured the four-line cart before touching anything, and the mess is three separate defects, not a styling problem.
What was actually wrong
1. The quantity stepper floated.
.co-controlswas a flex row holding both the stepper and the stacked fulfilment pair withalign-items: center— so a 46px control was centred against a 138px sibling, sitting 46px down with dead space above and below it. On every line.2. Unusable width beside the fulfilment pair, and different on every line: 80.1 / 53.0 / 69.3 / 69.3px.
3. Nothing shared a vertical axis. The amount column was
auto, and each line is its own grid, so a content-sized track can't match the line above:The moving column edge is what reads as messy.
The rebuild
The line is now an explicit four-cell grid — thumb, body and amount on row 1, the fulfilment pair on row 2 indented to the body's axis. Placement is explicit because auto-flow would push whichever child follows a full-width row onto a third row. Markup keeps its reading order: name, quantity, fulfilment, then price and remove.
.co-controlsholds only the stepper and the indent tag now, which really are the same height, so centring them is honest. The remove link becomes a block —margin-topalways intended that, but inline it sat beside the price and the two read as one string.The amount track is a fixed 120px: the widest realistic price,
Rp 150.000.000, measures 111px at this size.Two things the fix exposed
The pair's two-up threshold has to be container-driven. It uses
repeat(auto-fit, minmax(min(17rem, 100%), 1fr)). 17rem is what one option needs to keepOngkos pasang dihitung di chat.(209.8px + 56px of radio, gap and padding) on one line. A media query guessed wrong at 900px, where the sticky summary left 172px columns and wrappedPasang di bengkel (Situbondo)onto three lines. Themin(…, 100%)is load-bearing: a bareminmax(17rem, 1fr)still forces a 272px track in a narrower container and overflowed the 229px line at 375. Same idiomkontak.htmlalready uses.A pre-existing defect worse than anything reported.
.checkoutgrew its 320px summary column at 701px, leaving the main column 309px — narrower than the summary beside it, for all four panels on the page, not just the cart. At 701 that squeezed the line's body track to 23px and wrapped a product name onto ten lines. The summary now waits until 900px. Body width never drops below 174px from 320 to 1600, and the name never exceeds two lines..mode-pairitself is untouched, so the product page's buy column still stacks its pair — verified single-column at 386px.Verification
.checkoutcolumns plus.summaryposition match the mockup at every width including the new 900 boundary — the mockup renders.checkouttoo, so both sides move togetherAmbil di bengkel, remove works, zero page errorsCorrections after review
Both reviewers landed findings that changed this PR materially. Recording them here because the original body asserted the opposite.
The premise was false. I claimed no mockup uses
.co-line,.co-controls,.co-amount,.mode-pairor.qty. All five were wrong.assets/app.js— loaded by all six mockups, vendored by none — containscoLineMarkup, which renders the checkout page's cart lines.checkout-whatsapp.htmlis an empty[data-checkout-lines]mount point without it. So the vendored mockups had been 404ing a script since #86, and that absence is exactly what made the premise look true.Consequently the original change silently restyled the approved design's own line (amount track 138.95→120px, price x 724→765,
.co-controlsgap 16→12px, remove link inline-block→block), and the headline "42 route × width, delta 0" was hollow on the changed surface — it read delta 0 for.co-*only because that mockup rendered zero lines.Fixed at the cause:
app.jsis vendored (0df4accc, 9044 bytes);tokens.cssis back tomainplus only the.checkout→900px rule, which genuinely belongs in the design; everything else moved to the port's app-additions beside the.pdp > .buy-colprecedent.Proof the design is untouched: with app.js served the mockup renders 3 real lines and its own
.co-lineis pixel-identical tomain— tracks84px 479.047px 138.953px, amount x724 w139, buttoninline-block, gap 16px,.switch-rowpresent,.co-fulfilabsent.The track was wrong twice.
Rp 150.000.000is 135.91px, not the 111px I quoted —.co-amount .priceis1.15remso it renders at 18.4px, and I measured at the inherited 15px, ignoring a rule three lines below the one I was justifying. 120px overflowed fromRp 10.000.000up, which ten of the fixture's own tyres reach. It is now8.75rem: a px track holding rem content stops matching when the reader changes font size (three of four prices overflowed at a 20px root; the price painted over the.summarycard at 24px). Verified 140 / 175 / 210px at 16 / 20 / 24px roots, no overflow at any.DOM order flipped. I had kept fulfilment before the amount for destructive-last, and asked to be overruled if the reading went the other way. It did, on a better ground than mine: forward traversal passes every
Hapusanyway to reach the next line, so destructive-last bought nothing measurable, while the backwards focus jump was real 4× per cart at both widths..co-fulfilnow follows.co-amount— zero CSS change, since explicitgrid-rowalready pins the visual result. DOM order now equals visual order; the residual step is −22px within row 1, down from −98px across rows.Accessibility completed. Every per-line name now carries the fulfilment mode: the same product can sit on two lines at once (two PDP clicks;
setMode's merge comment proves it is modelled deliberately), and in that state the earlier fix had restored every duplicate. The option group is now afieldsetwith ansr-onlylegendmatching the design's ownMau diapakan?grouping, rather thanrole="radiogroup"as a second convention for one widget.Also reverted as incidental:
.co-controls' gap is back to--s4. And the "margin-topalways intended a block" line is gone — a<button>is inline-block and does honourmargin-top, so the margin worked and the line box was the problem. Right fix, wrong reason.Known, pre-existing, not addressed here
Every cart mutation drops focus to
<body>, andsetModeis remove-then-add so the line jumps to the end of the list. Arrowing through the fulfilment radios therefore commits each option, teleports the line to the bottom, and loses focus.keyandsetModeare untouched by this PR, so it is not a regression — but it is a larger keyboard defect than either of the two this PR fixed, sitting in the control it touched. Worth its own issue.Reported as messy. Measured the four-line cart before touching it, and the mess is three separate defects rather than a styling problem: 1. The quantity stepper floated. `.co-controls` was a flex row holding both the stepper and the stacked fulfilment pair with `align-items: center`, so a 46px control was centred against a 138px sibling — the stepper sat 46px down with dead space above and below it, on every line. 2. 53-80px of unusable width sat beside the fulfilment pair, and the amount differed per line: 80.1 / 53.0 / 69.3 / 69.3px. 3. Nothing shared a vertical axis. The amount column was `auto`, and each line is its own grid, so a content-sized track cannot match the line above: it measured 128.1 / 155.2 / 139.0 / 139.0px wide at x 734.9 / 707.8 / 724.0 / 724.0. The column edge moved on every row, which is what reads as messy. The line is now an explicit four-cell grid: thumb, body and amount on row 1, the fulfilment pair on row 2 indented to the body's axis. Placement is explicit because auto-flow would push whichever child follows the full-width row onto a third row. Markup keeps its reading order — name, quantity, fulfilment, then price and remove. amount track auto -> 120px (widest realistic price, Rp 150.000.000, measures 111px at this size) amount x 707.8-734.9 -> 743 identical on all four lines amount width 128.1-155.2 -> 120 identical on all four lines void beside 53.0-80.1 -> 0 line height 222.5/222.5/264.5/222.5 -> 211.5/211.5/213/211.5 `.co-controls` now holds only the stepper and the indent tag, which really are the same height, so centring them is honest. The remove link becomes a block: `margin-top` always intended that, but inline it sat beside the price and the two read as one string. ## Two things the fix exposed The fulfilment pair goes two-up via `repeat(auto-fit, minmax(min(17rem, 100%), 1fr))`. 17rem is what one option needs to keep "Ongkos pasang dihitung di chat." (209.8px plus 56px of radio, gap and padding) on one line. It is container-driven on purpose: a media query guessed wrong at 900px, where the sticky summary left 172px columns and wrapped "Pasang di bengkel (Situbondo)" onto three lines. The `min(…, 100%)` is load-bearing, not decoration — a bare `minmax(17rem, 1fr)` still forces a 272px track in a narrower container and overflowed the 229px line at 375. Same idiom kontak.html already uses. Then a pre-existing defect, worse than anything reported: `.checkout` grew its 320px summary column at 701px, leaving the main column **309px** — narrower than the summary beside it, for all four panels on the page. At 701 that squeezed the line's body track to **23px** and wrapped a product name onto **ten lines**. The summary now waits until 900px, so the main column keeps full width until there is genuinely room to split it. Body width is never below 174px at any viewport from 320 to 1600, and the name never exceeds two lines. `.mode-pair` itself is untouched, so the product page's buy column still stacks its pair — verified single-column at 386px. ## Verified - 13 viewports 320-1600: no horizontal overflow anywhere, amount axis shared on every line at every width, option labels unwrapped from 480 up - 42 route x width combinations against the mockups (1440/1120/901/900/701/ 700/375 x six routes): delta 0 everywhere, and `.checkout` columns plus `.summary` position match the mockup at every width including the new 900 boundary — the mockup renders `.checkout` too, so both sides move together - interactions after the DOM restructure: increment updates price and storage, mode switch flips the line and re-labels a non-shippable item to "Ambil di bengkel", remove works, zero page errors - 504 tests, both typechecks exit 0, gate PASSED, port idempotent, anchor guard still exits 1, all seven vendored files byte-identical to upstreamFound while checking my own restructure, before review reached it. Two defects of the same shape, both pre-existing and both made more visible by the rebuild. `Hapus` was the ONLY duplicated accessible name on the page — one per line, so a screen-reader user pulling up a button list heard "Hapus, Hapus, Hapus" with nothing to distinguish them. The quantity buttons immediately beside it already name their line ("Kurangi jumlah Oli TOP 1 GEAR 90"), so this was an inconsistency inside one component. Now `aria-label="Hapus <name> dari keranjang"`; the visible text stays short. The fulfilment pair was an unlabelled group. Each radio takes its name from its own label, so the options were reachable, but with several lines the page carries several identical pairs and the user heard "Kirim ke alamat saya, radio button, 1 of 2" with no way to tell which item it set. Now `role="radiogroup"` with `aria-label="Pengiriman <name>"`. Verified in the CDP accessibility tree with three lines: three uniquely named remove buttons, three uniquely named radio groups, and zero duplicated button names anywhere on the page (previously exactly one duplicate, "Hapus"). Interaction unaffected — mode switch still flips one line, remove still takes 3 lines to 2. ## Recorded for review rather than decided quietly Explicit `grid-row` placement means DOM order and visual order now differ by one cell: DOM is thumb, body, fulfilment, amount; visually the amount sits on row 1 and the fulfilment below it. So tab order runs name -> quantity -> options -> remove, and the focus ring jumps back up 98px from the options to the remove link. I kept that deliberately. The alternative — amount before fulfilment in DOM — matches visual order exactly but puts the destructive control in the middle of the sequence, so a keyboard user setting fulfilment tabs through "Hapus" on the way. Destructive-last seemed the stronger property, and the order still preserves meaning: identify the item, set quantity, choose fulfilment, then remove. It is a judgement call, not a measurement, so a reviewer should overrule it if they read WCAG 2.4.3 the other way.LineSemantics' remaining should, and their measurement generalised further than the rule they aimed at. They showed `.co-body`'s placement earns nothing: forced to `auto`, every cell box is byte-identical at 320 / 375 / 700 / 701 / 1440. Testing the same way took three of the four with it. Now that the markup order is thumb, body, amount, fulfilment — the order they read in, after the DOM flip earlier in this branch — auto-flow puts the first three in row 1 unaided at both breakpoints. Verified byte-identical cell boxes at 320 / 375 / 700 / 701 / 900 / 1440 with all placement dropped except one. So the whole `@media (min-width: 701px)` placement block plus its mobile counterpart collapse to: .co-fulfil { grid-column: 2 / -1; } That is the only cell auto-flow genuinely cannot express, and `2 / -1` reads as "indented to the body, out to the end" at two tracks or three without knowing which. The track itself stays scoped to `min-width: 701px` because it must not reach the mobile branch. `.co-body` is gone from the markup too, so the app's second cell is unclassed like the `coLineMarkup` cell it mirrors. Their argument for cutting rather than hoisting is the one I'd already written into the cascade fix: remove the obligation instead of adding a thing to remember. The explicit-at-desktop, auto-at-mobile split was two mental models by width, and that asymmetry is what produced the cascade bug in the first place. ## And the breakpoint now tracks the reader's font size Sweeping 57 width x root combinations left one outlier: at a 24px root and exactly 901px the body track is 133px and a product name wraps onto six lines. No overflow, axis shared, order monotonic — but it is the same defect class as the 701px one this branch already fixed, the summary column appearing before there is room for it. `@media (max-width: 900px)` is now `56.25em`. Identical at the default 16px. The mechanism is narrower than "it scales", and I nearly shipped that sentence without checking: a media-query `em` resolves against the browser's INITIAL font size, so `html { font-size }` set inline does not move it — which is why my first sweep saw no change and I had to measure it properly. What it does track is a reader who raises their browser's default font size. Measured with `Page.setFontSizes` at 24px, viewport 901: the em query matches where the px one does not, the row stays single-column, and the line body gets 477px instead of 133px. Page zoom needs no help, since it shrinks the CSS-px viewport and a width query already follows it. So the two font-size defences are complementary rather than redundant: the `8.75rem` track handles content growing inside a fixed row, and the `56.25em` query handles the row itself splitting too early. 504 tests, both typechecks exit 0, gate PASSED, port idempotent, all eight vendored files byte-identical to upstream.