fix(#560): the checkout path — three dead buy buttons, contradictory copy, and a form that did not look like the app - #624
Open
TortoiseWolfe wants to merge 2 commits into
Open
fix(#560): the checkout path — three dead buy buttons, contradictory copy, and a form that did not look like the app#624TortoiseWolfe wants to merge 2 commits into
TortoiseWolfe wants to merge 2 commits into
Conversation
Reported from production: /checkout?sku=svc-care and svc-care-pro dead-end on "That package is not available". prd-foundry does too — nobody had clicked it. That was three of the eight buy buttons on /pricing, and every subscription. The cause is a split brain. pricing/page.tsx is a hand-maintained literal array; /checkout resolves the SKU against `products` and refuses `active = false`. Nothing connected them, so a deactivated product went on being advertised with a working-looking button indefinitely. Those three are inactive DELIBERATELY. A recurring SKU needs a plan registered at the payment provider, and the database enforces it — products_recurring_provider_check requires an active recurring row to carry a stripe_price_id or paypal_plan_id, and all three carry neither. They cannot be activated until those plans exist; doing so would move the failure from an honest "not available" shown BEFORE the buyer invests anything, to a declined charge AFTER they filled the intake form and entered a card. So they keep their cards (demand signal) and lose their buttons: a non-interactive "Coming soon", not a disabled control, because a disabled button still invites the click that discovers the dead end. Dashed border rather than dimmed text — muting the label is the obvious move and would drop it under the 7:1 the a11y gate enforces (globals.css:61-85 documents the same trap for DaisyUI's .label). The page stays backend-free on purpose. It is a static export, and a build-time catalog fetch would both bake staleness and fail the build whenever Supabase is paused — which has happened repeatedly here. The real guard is tests/e2e/commerce/pricing-links.spec.ts: it walks every checkout link a customer can click and fails if one dead-ends. Black-box, needs no database credentials, and catches causes nobody has predicted — a deleted row, a renamed SKU, a typo'd href. Verified against LIVE production, where it fails and names exactly svc-care, svc-care-pro and prd-foundry. Its coverage floor already earned its place: the first run found 0 links (the selector missed `trailingSlash`) and the floor turned a vacuous green into a failure. Do not lower it (#396). Refs #560
…tradicting itself Two things the owner found on the live checkout. **The copy contradicted the page before it.** The signed-in branch still read "No account needed. Terms are shown before payment." #613 replaced guest checkout with a required account and updated the copy on the GATE, but not here — so a buyer who had just been forced to create an account was then told they did not need one. **It did not look like the app.** Three concrete defects, not taste: - `Field` wrapped label and input in a bare `<div>` with NO spacing utility between them. DaisyUI's `.label` ships only its own padding, so there was nothing to separate them at all — reported as "labels too close to inputs". Now the two-column row from SignInForm.tsx:278-298, whose `gap-2` IS that missing space. `items-start` rather than SignInForm's `items-center`, because this form has hints, errors and a textarea that must align under the input column. - Inputs emitted bare `.input` — no `-bordered`, and no `min-h-11`. The 44px touch target was missing on the one form in the product that takes money, and invisible to mobile-touch-targets.spec.ts, which measures buttons and links. - The form floated directly on the page background. /sign-in and /reset-password both sit on `sh-plate` (sign-in/page.tsx:135); this now does too, which is the single biggest reason the screen read as a different product. Submit moves from `btn btn-primary` to `sh-btn sh-btn-primary`. Not cosmetic: btn-primary reads as DISABLED on scripthammer-dark (SignInForm.tsx:386-391). No box-shadow is set by hand — globals.css:523-551 already gives .input and .textarea the --sh-groove recess under the house themes, and adding one would double it. Labels use `label-text` so they inherit the repo-wide contrast correction at globals.css:61-85 rather than DaisyUI's muted 5.86:1. Verified by rendering the real signed-in page against the live catalog and measuring: label→input gap 0px → 24px, input height → 44px, border 0 → 1px, panel shadow present, submit carries sh-btn sh-btn-primary, and every label now fits one line. Refs #560
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.
Three things the owner found walking the live storefront, all on the checkout path.
1. Three buy buttons went nowhere
/checkout?sku=svc-careandsvc-care-prodead-end on "That package is notavailable".
prd-foundrydoes too — nobody had clicked it. That is three of eightbuy buttons, and every subscription product.
Split brain.
pricing/page.tsxis a hand-maintained literal array;/checkoutresolves the SKU against
productsand refusesactive = false. Nothing connectedthem, so a deactivated product kept its working-looking button indefinitely.
They cannot simply be activated. The database forbids it:
All three carry neither id, so
UPDATE products SET active=trueis rejected.Activating by dropping the constraint would move the failure from an honest "not
available" shown before the buyer invests anything, to a declined charge after they
filled the intake form and entered a card. Selling these needs real plans created in
Stripe and PayPal first — owner-side work, tracked separately.
So they keep their cards and lose their buttons: a non-interactive "Coming soon",
not a disabled control, because a disabled button still invites the click that finds
the dead end.
2. The checkout contradicted the page before it
The signed-in branch still read "No account needed. Terms are shown before payment."
#613 replaced guest checkout with a required account and updated the copy on the gate
but not here — so a buyer who had just been made to create an account was told they
did not need one.
3. It did not look like the rest of the app
Three concrete defects, not taste:
<div>, no spacing utility at allgap-2row fromSignInForm.tsx:278-298).input— no border, nomin-h-11input-bordered min-h-11(44px)sh-plate, as/sign-indoes (sign-in/page.tsx:135)btn btn-primarysh-btn sh-btn-primaryThe missing 44px touch target was on the one form in the product that takes money, and
invisible to
mobile-touch-targets.spec.tsbecause that spec measures buttons and links.btn-primaryalso reads as disabled on scripthammer-dark (SignInForm.tsx:386-391).Deliberately not hand-setting a box-shadow —
globals.css:523-551already gives.input/.textareathe--sh-grooverecess under the house themes. Labels uselabel-textso they inherit the repo-wide contrast fix atglobals.css:61-85insteadof DaisyUI's muted 5.86:1.
The guard
tests/e2e/commerce/pricing-links.spec.tswalks every checkout link a customer canclick and fails if one dead-ends. Black-box — no database credentials, no knowledge of
activeflags — so it also catches causes nobody predicted: a deleted row, a renamedSKU, a typo'd href.
Verified against LIVE production, where it correctly fails and names exactly
svc-care, svc-care-pro, prd-foundry.Its coverage floor already earned its keep: the first run found 0 links (my selector
missed
trailingSlash) and the floor turned a vacuous green into a failure. Do notlower it (#396).
Verification performed
Rendered the real signed-in checkout against the live catalog and measured:
label→input
0px → 24px, input height→ 44px, border0 → 1px,sh-plateshadowpresent, submit carries
sh-btn sh-btn-primary, every label fits one line, stale copygone. Type-check, lint and the 11 IntakeForm unit + a11y tests pass.
Still owed: the pricing-links spec's green case is confirmed post-deploy — locally
the dev server carries a basePath and
new URL('/pricing', '…/ScriptHammer')drops theprefix, so a relative-goto run measures a 404 page. What was confirmed locally is the
rendered HTML: exactly 5
skuhrefs remain, all fiveactive=true.Refs #560