Skip to content

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
mainfrom
fix/dead-pricing-links
Open

fix(#560): the checkout path — three dead buy buttons, contradictory copy, and a form that did not look like the app#624
TortoiseWolfe wants to merge 2 commits into
mainfrom
fix/dead-pricing-links

Conversation

@TortoiseWolfe

@TortoiseWolfe TortoiseWolfe commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Three things the owner found walking the live storefront, all on the checkout path.

1. Three buy buttons went nowhere

/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 is three of eight
buy buttons, and every subscription product.

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 kept its working-looking button indefinitely.

They cannot simply be activated. The database forbids it:

products_recurring_provider_check
CHECK (type='one_time' OR NOT active OR stripe_price_id IS NOT NULL OR paypal_plan_id IS NOT NULL)

All three carry neither id, so UPDATE products SET active=true is 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:

before after
label→input gap 0px — bare <div>, no spacing utility at all 24px (gap-2 row from SignInForm.tsx:278-298)
input bare .input — no border, no min-h-11 input-bordered min-h-11 (44px)
container floated on the page background sh-plate, as /sign-in does (sign-in/page.tsx:135)
submit btn btn-primary sh-btn sh-btn-primary

The missing 44px touch target was on the one form in the product that takes money, and
invisible to mobile-touch-targets.spec.ts because that spec measures buttons and links.
btn-primary also reads as disabled on scripthammer-dark (SignInForm.tsx:386-391).

Deliberately not hand-setting a box-shadow — globals.css:523-551 already gives
.input/.textarea the --sh-groove recess under the house themes. Labels use
label-text so they inherit the repo-wide contrast fix at globals.css:61-85 instead
of DaisyUI's muted 5.86:1.

The guard

tests/e2e/commerce/pricing-links.spec.ts walks every checkout link a customer can
click and fails if one dead-ends. Black-box — no database credentials, no knowledge of
active flags — so it also catches causes nobody predicted: a deleted row, a renamed
SKU, 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 not
lower it (#396).

Verification performed

Rendered the real signed-in checkout against the live catalog and measured:
label→input 0px → 24px, input height → 44px, border 0 → 1px, sh-plate shadow
present, submit carries sh-btn sh-btn-primary, every label fits one line, stale copy
gone. 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 the
prefix, so a relative-goto run measures a 404 page. What was confirmed locally is the
rendered HTML: exactly 5 sku hrefs remain, all five active=true.

Refs #560

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
@TortoiseWolfe TortoiseWolfe changed the title fix(#560): stop advertising three packages that cannot be bought fix(#560): the checkout path — three dead buy buttons, contradictory copy, and a form that did not look like the app Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants