Skip to content

feat(pro): buy-box pricing section — feature list once, term choice as radio rows#195

Merged
kilbot merged 6 commits into
mainfrom
worktree-pro-pricing-buy-box
Jul 2, 2026
Merged

feat(pro): buy-box pricing section — feature list once, term choice as radio rows#195
kilbot merged 6 commits into
mainfrom
worktree-pro-pricing-buy-box

Conversation

@kilbot

@kilbot kilbot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the pricing design picked in #194 (prototype variant H, "the buy box"). The /pro pricing section becomes a product page rather than a pricing page:

  • Feature list appears exactly once — icons + real descriptions (from the existing pro.features messages), replacing both the old duplicate per-plan checklists and the separate features section. Owner rule from prototype: /pro pricing + checkout variants (do not merge) #194: never repeat the feature checklist per plan.
  • Sticky buy box — yearly vs lifetime as radio rows whose only content is price + term facts; one CTA that follows the selection (same interaction pattern as the chosen checkout design).
  • Static-first rendering/pro is now ◐ partially prerendered: hero, features and FAQ are static HTML; only the buy box suspends (Medusa prices + checkout experiment cookie). This closes the original complaint about the whole pricing blocks skeletoning.
  • Trust facts at the decision point, all sourced from live pages: 14-day money-back guarantee (linked to /refunds), never-auto-renews, yearly-credits-toward-lifetime, 5,000+ active stores, accepted payment methods.

Kept intact: pro_checkout_v1 experiment (CTA label + exp/exp_variant params via buildProCheckoutHref/getProCheckoutCtaLabel), click_start_checkout analytics event with the same properties, JSON-LD product schema, graceful "pricing unavailable" fallback, FAQ.

Removed: PricingCard + test (this page was its only consumer).

Validation

  • tsc --noEmit clean, eslint clean
  • Unit: 134 files / 911 tests passing (new pro-buy-box tests cover default selection, term switching, analytics payload, refunds link)
  • Build: /pro renders as ◐ Partial Prerender (was ƒ-dynamic-on-request for the pricing block)
  • E2E: e2e/pro.spec.ts updated for the buy box — 7/7 passing against the mocked Medusa backend (chromium)
  • Visually verified in the browser with mocked catalog prices ($129/$399)

Notes

Summary by CodeRabbit

  • New Features
    • Added a Pro purchase box with accessible Yearly and Lifetime term selection, including plan labels, pricing suffixes, and a dynamic checkout CTA.
    • Added localized buy-box messaging across supported languages.
    • Updated the Pro page layout to present features separately from the on-demand purchase box.
  • Bug Fixes
    • Updated end-to-end checks to validate the Pro buy box UI (including term selection, unavailable-state messaging, and CTA query parameters) instead of pricing cards.
    • Refined assertions around load/visibility timing to make availability edge cases more reliable.

…rows

Implements the winning prototype (PR #194 variant H). The /pro pricing
section becomes a product page: the Pro feature list renders once
(icons + descriptions from pro.features messages) beside a sticky buy
box where yearly vs lifetime is price + term facts only — the feature
checklist never repeats per plan.

Static-first: the page is now partially prerendered — hero, features
and FAQ are static HTML; only the buy box suspends (Medusa prices +
checkout experiment from cookies). Buy box keeps the pro_checkout_v1
experiment (CTA label + exp params) and the click_start_checkout event,
and adds trust facts sourced from live pages: 14-day refund (linked to
/refunds), never-auto-renews, yearly-credits-toward-lifetime, 5,000+
stores.

PricingCard and its test are deleted (only consumer was this page).
The old standalone features section is subsumed by the new layout.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6396a41a-7618-4586-8eeb-6f1310f2253b

📥 Commits

Reviewing files that changed from the base of the PR and between f135279 and 63eefae.

📒 Files selected for processing (19)
  • e2e/pro.spec.ts
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/nl.json
  • messages/pt.json
  • messages/zh.json
  • src/app/[locale]/(main)/pro/page.test.tsx
  • src/app/[locale]/(main)/pro/page.tsx
  • src/components/pro/pro-buy-box-options.test.ts
  • src/components/pro/pro-buy-box-options.ts
  • src/components/pro/pro-buy-box.test.tsx
  • src/components/pro/pro-buy-box.tsx
  • src/lib/pro-offer-catalog.test.ts
  • src/lib/pro-offer-catalog.ts
✅ Files skipped from review due to trivial changes (4)
  • messages/zh.json
  • messages/ko.json
  • messages/en.json
  • messages/fr.json
🚧 Files skipped from review as they are similar to previous changes (13)
  • messages/es.json
  • messages/ja.json
  • messages/de.json
  • messages/nl.json
  • messages/pt.json
  • src/components/pro/pro-buy-box-options.test.ts
  • src/components/pro/pro-buy-box.test.tsx
  • src/components/pro/pro-buy-box-options.ts
  • src/components/pro/pro-buy-box.tsx
  • messages/it.json
  • src/app/[locale]/(main)/pro/page.tsx
  • src/lib/pro-offer-catalog.ts
  • e2e/pro.spec.ts

📝 Walkthrough

Walkthrough

The Pro page now renders a localized buy box with yearly and lifetime options, static feature rows, and updated checkout tracking. The offer catalog shape is simplified, tests were updated for the new flow, and locale files add buy-box copy.

Changes

Pro Buy Box Feature

Layer / File(s) Summary
Buy box option building
src/components/pro/pro-buy-box-options.ts, src/components/pro/pro-buy-box-options.test.ts
Defines buy-box option data, computes lifetime CTA copy from offer pricing, and maps offers into checkout options with experiment metadata.
ProBuyBox component and tests
src/components/pro/pro-buy-box.tsx, src/components/pro/pro-buy-box.test.tsx
Implements the client-side option selector, keyboard navigation, checkout CTA tracking, and coverage for selection and analytics behavior.
Pro feature list
src/components/pro/pro-features.tsx
Exports the Pro feature key mapping, feature row type, and static feature list renderer used on the Pro page.
Pro page wiring and tests
src/app/[locale]/(main)/pro/page.tsx, src/app/[locale]/(main)/pro/page.test.tsx, e2e/pro.spec.ts
Replaces the pricing section with the suspended buy box, renders translated features statically, and updates page and e2e assertions for the new UI.
Localized buy box copy
messages/de.json, messages/en.json, messages/es.json, messages/fr.json, messages/it.json, messages/ja.json, messages/ko.json, messages/nl.json, messages/pt.json, messages/zh.json
Adds buy-box headings, option copy, guarantee text, payment labels, proof text, and unavailable-state messaging across the locale files.
Pro offer catalog model
src/lib/pro-offer-catalog.ts, src/lib/pro-offer-catalog.test.ts
Simplifies the Pro offer copy mapping to schema name only and updates catalog output/test expectations for the reduced offer shape.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • wcpos/wcpos-com#181: Shares the Pro offer-catalog and checkout helper surface used by the new buy box flow.
  • wcpos/wcpos-com#185: Also changes src/app/[locale]/(main)/pro/page.tsx and its layout/tests.
  • wcpos/wcpos-com#188: Uses the same checkout query-parameter flow that the buy box now generates and tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main change: the Pro page buy-box pricing section with a single feature list and radio-style term selection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-pro-pricing-buy-box

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

Replaces the two-card pricing layout with a single "buy box" — a sticky radio-row term selector beside a unified feature list — and moves /pro to Partial Prerender so only the Medusa-backed buy box suspends while hero, features, and FAQ render as static HTML.

  • ProBuyBox (new client component) holds the yearly/lifetime radio rows, a single CTA, and trust facts; pricing-card.tsx and its tests are deleted as no longer needed.
  • ProFeatureList (new server component) renders the feature list exactly once next to the buy box, driven by the existing pro.features i18n messages.
  • BuyBoxSection in page.tsx maps Medusa offers to ProBuyBoxOption using a BUY_BOX_COPY constant; the experiment variant is resolved from the cookie in BuyBoxWithExperiment and passed down as a cache key.

Confidence Score: 4/5

Safe to merge; the page restructure is well-tested and the static/dynamic split is correctly implemented.

The buy-box copy (plan titles, subtitles, CTA notes) is hard-coded English while everything else on the page flows through next-intl, which is an inconsistency on a locale-routed page. The custom radio group also sets role="radio" on button elements without the expected arrow-key keyboard navigation, which diverges from the ARIA radiogroup spec. Neither issue breaks the page for English-language users, but both are worth resolving before expanding to additional locales or auditing for accessibility.

src/app/[locale]/(main)/pro/page.tsx (BUY_BOX_COPY), src/components/pro/pro-buy-box.tsx (radiogroup keyboard handling)

Important Files Changed

Filename Overview
src/components/pro/pro-buy-box.tsx New client component implementing the buy-box UI with radio-row term selection; custom ARIA radiogroup uses button tab stops instead of arrow-key navigation per the ARIA spec
src/app/[locale]/(main)/pro/page.tsx Page refactored to PPR with features+buy-box co-located in one section; BUY_BOX_COPY is hard-coded English while surrounding content uses t() for localization
src/components/pro/pro-features.tsx New statically-rendered feature list component; clean icon-keyed mapping, receives translated strings from parent
src/components/pro/pro-buy-box.test.tsx New unit tests covering default selection, term switching, analytics payload, and refund-link presence; good coverage of the stateful interactions
src/app/[locale]/(main)/pro/page.test.tsx Page test updated for the new section structure; correctly asserts the skeleton renders in place of the suspended buy box
e2e/pro.spec.ts E2E spec updated from pricing-card locators to buy-box and radio-based locators; role-based selectors are more robust than testid-only queries
src/components/pro/pricing-card.tsx Deleted — only consumer was /pro page, replaced by ProBuyBox
src/components/pro/pricing-card.test.tsx Deleted alongside its component; equivalent coverage now lives in pro-buy-box.test.tsx

Fix All in Claude Code Fix All in Codex

Reviews (1): Last reviewed commit: "feat(pro): buy-box pricing — feature lis..." | Re-trigger Greptile

Comment thread src/app/[locale]/(main)/pro/page.tsx Outdated
Comment thread src/components/pro/pro-buy-box.tsx Outdated
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚀 Preview: https://wcpos-710lj0hdm-wcpos.vercel.app

@wcpos-bot

wcpos-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Triage before fixes (fresh unresolved review-thread inventory):

# Source File Class Category Decision Outcome
1 Greptile page.tsx:70 Logic Buy-box option copy bypasses t() and remains English on locale routes Fix Move plan title/subtitle/badge/suffix/note copy through t() in BuyBoxSection; commit pending
2 Greptile pro-buy-box.tsx:98 Mechanical Custom radiogroup lacks roving tab index and arrow-key navigation Fix Add keyboard handling and selected-only tabIndex=0; commit pending

Skipped threads: none. CI scope: no failing checks at inventory time; Deploy Preview is still in progress.

@wcpos-bot

wcpos-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Round 3 triage before fixes (fresh unresolved review-thread inventory):

# Source File Class Category Decision Outcome
1 Greptile page.tsx:70 Logic Buy-box option copy bypasses t() and remains English on locale routes Fix Move plan title/subtitle/badge/suffix/note copy through t() in BuyBoxSection; commit pending
2 Greptile pro-buy-box.tsx:98 Mechanical Custom radiogroup lacks roving tab index and arrow-key navigation Fix Add keyboard handling and selected-only tabIndex=0; commit pending

Skipped threads: none.

CI scope: gh pr checks currently reports all checks passing; branch protection remains blocked until required review/merge conditions are satisfied.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kilbot has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@wcpos-bot

wcpos-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Post-fix triage (fresh unresolved review-thread inventory after push/resolve: 0):

Thread File Issue Decision Commit
Greptile i18n page.tsx Buy-box option copy bypassed t() and rendered English on locale routes. Fixed: option copy now resolves through pro.buyBox messages, with entries added for all existing locale files. 570adbc
Greptile radiogroup pro-buy-box.tsx Custom radio rows lacked roving tab index and ArrowUp/ArrowDown behavior. Fixed: only the selected row is tabbable, arrow keys move selection and focus, covered by a unit test. 5704352

Skipped threads: none.

Validation:

  • pnpm install --frozen-lockfile passed.
  • pnpm lint passed with existing warnings in src/services/core/business/electron-service.ts.
  • pnpm exec tsc --noEmit passed.
  • pnpm test:unit passed: 134 files / 913 tests.
  • pnpm test:e2e --project=chromium was attempted twice locally but blocked by the container environment: first the Chromium browser was missing, then after pnpm exec playwright install chromium, Chromium could not launch because libglib-2.0.so.0 is missing. pnpm exec playwright install-deps chromium failed because this session cannot switch to root (su: Authentication failure).

CI scope: checks are currently running on pushed head 5704352; no local code failure was found in lint, typecheck, or unit tests.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚀 Preview: https://wcpos-6x8pdghcd-wcpos.vercel.app

Review of PR #195 (8 finder angles + adversarial verification) surfaced
9 findings; this applies them:

- a11y (CONFIRMED): real radio keyboard model — roving tabindex plus
  arrow-key navigation with wrap-around and focus tracking.
- stale-selection crash (PLAUSIBLE): current falls back to options[0]
  when a revalidated payload drops the selected plan.
- price-ratio copy (CONFIRMED): 'About N years of Yearly' is now derived
  from live catalog prices (with a ratio-free fallback), never hardcoded.
- split-brain plan copy (CONFIRMED): OFFER_COPY shrinks to schema.org
  names; ProOffer drops the six orphaned presentation fields; buy-box
  copy moves to the pro.buyBox message namespace (keys in all 10
  locales, matching the pro.* convention).
- i18n (CONFIRMED): every buy-box string (incl. the radiogroup
  aria-label and the unavailable state) now routes through messages.
- primitives (CONFIRMED): Card(elevated) for box/skeleton/fallback,
  EmptyState for the unavailable state, TextLink for the refunds link,
  SectionHeading(align=left) for the features heading.
- redundant experiment channel (PLAUSIBLE): analytics eventProperties
  are built server-side next to the checkout href in a new pure
  buildProBuyBoxOptions; the client component no longer knows about
  experiments.
- coverage gap (CONFIRMED): buildProBuyBoxOptions unit tests cover the
  offers→options mapping, years derivation, and experiment wiring;
  buy-box tests add keyboard-model and stale-selection cases.
- catalog cache is now keyed by the catalog alone (one entry shared by
  buy box + JSON-LD instead of one per experiment variant).

Remaining CONFIRMED finding (pre-existing on main, out of scope):
transient Medusa failures cache the unavailable fallback for ~15-60min.
@kilbot

kilbot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Post-creation review complete (8 finder angles → adversarial verification → fixes applied in the latest commit):

Confirmed & fixed:

  • a11y: the radio term selector now implements the full WAI-ARIA keyboard model — roving tabindex (single tab stop) + Arrow Up/Down/Left/Right with wrap-around and focus tracking. (Integrates with the bot's keyboard commit; adds Left/Right + the group-level handler.)
  • Stale-selection crash: options.find(...)! could throw to the route error boundary if a revalidated payload dropped the selected plan; now falls back to the first option (covered by a test).
  • Hardcoded price ratio: "About 3 years of Yearly" is now derived from live catalog prices ({years} interpolation in all 10 locales, ratio-free fallback when the claim isn't a selling point).
  • Split-brain plan copy: OFFER_COPY shrinks to schema.org names; ProOffer drops six orphaned presentation fields (features/featured/title/description/priceSuffix/badgeLabel); buy-box copy lives in the pro.buyBox message namespace only.
  • i18n: every buy-box string (heading, aria-label, guarantee, payments, proof, unavailable state) now routes through messages — merged with the bot's translations and extended to the remaining keys in the same voice, all 10 locales.
  • Primitives: Card(elevated) for box/skeleton/fallback, EmptyState for the unavailable state, TextLink for the refunds link, SectionHeading(align=left) for the features heading.
  • Experiment channel: analytics eventProperties are built server-side next to the checkout href in buildProBuyBoxOptions (pure, unit-tested — also closes the offers→options coverage gap); the client component no longer knows about experiments.
  • Cache shape: one cached catalog entry shared by buy box + JSON-LD (previously one per experiment arm).

Confirmed, pre-existing, deferred (identical on main): a transient Medusa outage at revalidation caches the "pricing unavailable" fallback for ~15–60 min (getProducts swallows fetch errors into []). Worth a follow-up: throw on the buy-box path or use a short error-path cacheLife.

Refuted in verification (for the record): pre-hydration wrong-plan CTA (selection UI and href freeze together; React replays the click); integration-spec CTA label mismatch (variant resolution is consent-gated — deterministic control in e2e).

Note: the bot's through-the-page buy-box test was superseded by direct buildProBuyBoxOptions unit tests (its fixtures relied on the now-removed ProOffer fields); its translations are fully preserved and extended.

Validation after fixes: tsc/eslint clean, 916 unit tests (135 files), production build /pro still ◐ partial prerender, e2e pro spec 7/7 against mocked Medusa.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kilbot has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/[locale]/(main)/pro/page.tsx (2)

101-110: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback "unavailable" copy isn't translated.

t is already in scope in BuyBoxSection, but the empty-offers fallback text ("Pricing information is currently unavailable...") is a hardcoded English string, unlike the rest of the buy-box copy which goes through getTranslations.

🩹 Proposed fix
   if (offers.length === 0) {
     return (
       <div className="rounded-2xl border bg-card p-6 text-center">
         <p className="text-muted-foreground">
-          Pricing information is currently unavailable. Please try again
-          later.
+          {t('buyBox.unavailable')}
         </p>
       </div>
     )
   }

(requires adding a buyBox.unavailable key to the message files.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`[locale]/(main)/pro/page.tsx around lines 101 - 110, The
empty-offers fallback in BuyBoxSection is hardcoded English instead of using the
existing translation flow. Update the offers.length === 0 branch in pro/page.tsx
to render the unavailable copy via the scoped t from getTranslations, and add
the corresponding buyBox.unavailable key to the message files so the fallback is
localized like the rest of the buy-box content.

94-110: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid caching the empty-catalog fallback for the full products lifetime
src/services/core/external/medusa-client.ts:getProducts() swallows Medusa failures and returns [], so BuyBoxSection can cache the "Pricing information is currently unavailable" state under 'use cache' + cacheTag('products'). A transient upstream outage would then keep serving the fallback until the next tag revalidation. Make empty results bypass this cache path or throw so the page doesn’t cache outage state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`[locale]/(main)/pro/page.tsx around lines 94 - 110, The cached
fallback in pro/page.tsx is currently allowing an empty offer catalog from
getProOfferCatalog() to be stored under the products cache. Update the
BuyBoxSection/pro page caching path so empty offers do not go through the 'use
cache' products lifetime cache: either bypass caching when offers.length is 0 or
have getProducts()/getProOfferCatalog() surface the Medusa failure instead of
returning []. Keep the fix anchored around getProOfferCatalog(), getProducts(),
and the cacheTag('products') usage so transient outages do not get cached as the
unavailable-pricing state.
🧹 Nitpick comments (2)
messages/en.json (1)

72-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Whitespace-dependent priceSuffix is fragile for i18n.

lifetime.priceSuffix embeds a leading space (" once") that only works because ProBuyBox concatenates priceText and priceSuffix adjacently without an explicit separator. This is invisible in the JSON and easy for a translator/tooling pass to trim, silently breaking spacing across all locales at once. yearly.priceSuffix avoids this by using a /-prefixed style instead, so the convention isn't even consistent within this same object.

Consider handling the visual spacing in the component (e.g., a gap/margin utility or an explicit space rendered in JSX) rather than relying on leading whitespace baked into translated strings.

Example component-side fix
-<span className="text-xl font-bold">{option.priceText}</span>
-<span className="text-sm text-muted-foreground">
-  {option.priceSuffix}
-</span>
+<span className="text-xl font-bold">{option.priceText}</span>
+<span className="text-sm text-muted-foreground ml-1">
+  {option.priceSuffix}
+</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@messages/en.json` around lines 72 - 77, The `lifetime.priceSuffix` string is
relying on a leading space for layout, which is fragile for translations and
inconsistent with the `yearly.priceSuffix` convention. Update `ProBuyBox` to
handle spacing between `priceText` and `priceSuffix` in the component itself,
and remove the whitespace dependency from the `lifetime` entry in
`messages/en.json` so the localized string is just the suffix text.
e2e/pro.spec.ts (1)

55-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid hard-coded waitForTimeout.

page.waitForTimeout(2000) after waitForLoadState('networkidle') is a flaky-test anti-pattern that adds fixed latency to every run without guaranteeing readiness. The subsequent getByRole('radio', ...) assertions already auto-wait/retry, so the explicit timeout is redundant and can be dropped.

Suggested fix
     test('displays yearly and lifetime term options', async ({ page }) => {
       await skipIfPricingUnavailable(page)
       await page.waitForLoadState('networkidle')
-      await page.waitForTimeout(2000)
       await expect(page.getByRole('radio', { name: /Yearly/ })).toBeVisible()
       await expect(page.getByRole('radio', { name: /Lifetime/ })).toBeVisible()
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/pro.spec.ts` around lines 55 - 61, Remove the hard-coded delay in the
pricing terms test and rely on Playwright’s built-in waiting instead. In the
`displays yearly and lifetime term options` test, drop the
`page.waitForTimeout(2000)` call after `page.waitForLoadState('networkidle')`;
the `expect(page.getByRole('radio', ...)).toBeVisible()` assertions already
auto-wait. Keep the rest of the flow in `skipIfPricingUnavailable` and the radio
checks unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/pro/pro-buy-box.tsx`:
- Line 79: The row click handler in ProBuyBox is only updating selection state
and not moving focus, which breaks Safari mouse-to-keyboard continuity. Update
the button/list item click path in pro-buy-box.tsx so the same selected option
is also focused by calling the relevant ref’s focus() after setSelected,
matching the behavior already used by moveSelection and keeping the roving
tabIndex in sync. Apply this to the onClick handlers for the option rows that
use optionRefs.current and setSelected(option.planId).
- Around line 53-58: The static BuyBoxSection copy is hardcoded in English,
including the header, subtitle, radiogroup aria-label, and trust-facts block, so
non-English locales miss critical localized content. Move these strings out of
the client component and source them from the existing getTranslations flow in
BuyBoxSection, then pass them into pro-buy-box.tsx as props (or pre-rendered
content) using the existing buyBox.* translation keys and any new keys needed
for the trust block and aria-label. Update the BuyBoxSection and BuyBox-related
component props so the localizable text is resolved server-side instead of
embedded directly in the ProBuyBox component.

---

Outside diff comments:
In `@src/app/`[locale]/(main)/pro/page.tsx:
- Around line 101-110: The empty-offers fallback in BuyBoxSection is hardcoded
English instead of using the existing translation flow. Update the offers.length
=== 0 branch in pro/page.tsx to render the unavailable copy via the scoped t
from getTranslations, and add the corresponding buyBox.unavailable key to the
message files so the fallback is localized like the rest of the buy-box content.
- Around line 94-110: The cached fallback in pro/page.tsx is currently allowing
an empty offer catalog from getProOfferCatalog() to be stored under the products
cache. Update the BuyBoxSection/pro page caching path so empty offers do not go
through the 'use cache' products lifetime cache: either bypass caching when
offers.length is 0 or have getProducts()/getProOfferCatalog() surface the Medusa
failure instead of returning []. Keep the fix anchored around
getProOfferCatalog(), getProducts(), and the cacheTag('products') usage so
transient outages do not get cached as the unavailable-pricing state.

---

Nitpick comments:
In `@e2e/pro.spec.ts`:
- Around line 55-61: Remove the hard-coded delay in the pricing terms test and
rely on Playwright’s built-in waiting instead. In the `displays yearly and
lifetime term options` test, drop the `page.waitForTimeout(2000)` call after
`page.waitForLoadState('networkidle')`; the `expect(page.getByRole('radio',
...)).toBeVisible()` assertions already auto-wait. Keep the rest of the flow in
`skipIfPricingUnavailable` and the radio checks unchanged.

In `@messages/en.json`:
- Around line 72-77: The `lifetime.priceSuffix` string is relying on a leading
space for layout, which is fragile for translations and inconsistent with the
`yearly.priceSuffix` convention. Update `ProBuyBox` to handle spacing between
`priceText` and `priceSuffix` in the component itself, and remove the whitespace
dependency from the `lifetime` entry in `messages/en.json` so the localized
string is just the suffix text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ce8665a-7968-428e-9b1a-f74e5500f5cd

📥 Commits

Reviewing files that changed from the base of the PR and between ae25b46 and 5704352.

📒 Files selected for processing (18)
  • e2e/pro.spec.ts
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/nl.json
  • messages/pt.json
  • messages/zh.json
  • src/app/[locale]/(main)/pro/page.test.tsx
  • src/app/[locale]/(main)/pro/page.tsx
  • src/components/pro/pricing-card.test.tsx
  • src/components/pro/pricing-card.tsx
  • src/components/pro/pro-buy-box.test.tsx
  • src/components/pro/pro-buy-box.tsx
  • src/components/pro/pro-features.tsx
💤 Files with no reviewable changes (2)
  • src/components/pro/pricing-card.test.tsx
  • src/components/pro/pricing-card.tsx

Comment thread src/components/pro/pro-buy-box.tsx Outdated
Comment thread src/components/pro/pro-buy-box.tsx Outdated
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚀 Preview: https://wcpos-fcr1jn69o-wcpos.vercel.app

@wcpos-bot

wcpos-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Round 3 follow-up triage (fresh unresolved review-thread inventory after remote updates):

# Source File Class Category Decision Outcome
1 CodeRabbit pro-buy-box.tsx:79 Mechanical Click selection updates roving radio state but does not explicitly focus the clicked option for Safari Fix Call the existing option ref focus path from the row click handler; commit pending

Skipped threads: none.

CI scope: current PR checks are green for Test, E2E, CodeQL and CodeRabbit; Deploy Preview is still in progress.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kilbot has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@wcpos-bot

wcpos-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Round 3 final triage after fixes (fresh unresolved review-thread inventory: 0):

Thread File Issue Decision Commit
Greptile page.tsx Buy-box option copy bypassed t() on locale routes Fixed 570adbc / 654cfe5
Greptile pro-buy-box.tsx Custom radiogroup needed roving tab index and arrow-key navigation Fixed 5704352 / 654cfe5
CodeRabbit pro-buy-box.tsx Safari click path needed to focus the selected radio so roving tabindex and focus stay aligned Fixed and resolved f135279

Skipped threads: none.

Thread Reason for skipping
None All actionable review threads are fixed/resolved.

CI scope: local pnpm install --frozen-lockfile, pnpm lint, pnpm exec tsc --noEmit, and pnpm test:unit completed; local Chromium e2e is blocked by missing system library libglib-2.0.so.0. GitHub Actions are green for Test, E2E, CodeQL, and Deploy Preview after f135279; CodeRabbit remains pending. Review-thread scope is complete: unresolved inline thread count is 0.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚀 Preview: https://wcpos-qa93fmn89-wcpos.vercel.app

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/components/pro/pro-buy-box-options.ts (2)

23-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

eventProperties: Record<string, unknown> loses the known shape.

The actual payload is always { experiment, variant, product, plan }. Typing it precisely would give compile-time protection against a future field being dropped or misnamed at either the builder or the (client) consumer.

Also applies to: 65-70

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/pro/pro-buy-box-options.ts` around lines 23 - 24, The
eventProperties field is too loosely typed and hides the known payload shape.
Update the type in pro-buy-box-options so the builder and client consumer both
use the exact object shape with experiment, variant, product, and plan instead
of Record<string, unknown>. Also apply the same precise typing in the related
builder/consumer code around the referenced section so future field drops or
renames are caught at compile time.

31-45: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Lifetime-years derivation looks correct.

Logic correctly guards against a missing yearly/lifetime offer and division by zero, and the ≥2 threshold avoids making a weak "years of yearly" claim. Matches the documented intent in the header comment (never hardcode the ratio).

One edge case worth noting: Math.round will round e.g. a 1.6× ratio up to years: 2, slightly overstating the value claim right at the boundary. Given catalog prices are static and controlled, this is unlikely to bite in practice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/pro/pro-buy-box-options.ts` around lines 31 - 45, The lifetime
CTA year count in lifetimeCtaNote can be overstated because Math.round may
promote a non-integer ratio (for example, 1.6) to 2 and trigger the stronger
copy. Update the years derivation in lifetimeCtaNote to avoid rounding up—prefer
a conservative calculation such as flooring or otherwise requiring a true
whole-number ratio—so the t('buyBox.lifetime.ctaNote', { years }) path only uses
accurate years values.
src/components/pro/pro-buy-box.tsx (1)

39-44: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Minor: options[0] assumes a non-empty array.

useState<PlanId>(options[0].planId) and the current fallback both dereference options[0] unconditionally. Today the only caller (BuyBoxWithExperiment in page.tsx) guards against offers.length === 0 before rendering ProBuyBox, so this isn't currently reachable, but nothing in ProBuyBoxProps enforces a non-empty options array, so a future caller/refactor could reintroduce a crash.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/pro/pro-buy-box.tsx` around lines 39 - 44, `ProBuyBox` assumes
`options[0]` always exists when initializing `selected` and computing `current`,
which can crash if an empty options array is ever passed. Update `ProBuyBox` to
avoid unconditional indexing by adding a non-empty guard or a safe fallback
before using `options[0]`, and ensure the logic around `useState`, `current`,
and any dependent rendering handles the empty-array case explicitly. Refer to
`ProBuyBox` and `ProBuyBoxProps` when making the fix so the component is robust
even if future callers skip the current `offers.length === 0` guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/`[locale]/(main)/pro/page.tsx:
- Around line 110-116: The primary CTA text is still hardcoded in English via
getProCheckoutCtaLabel(experimentVariant), which bypasses localization on the
Pro page. Update the CTA label source in pro-offer-catalog.ts to use translation
keys resolved through the existing translate/t flow, similar to
buildProBuyBoxOptions, and thread the localized value back into the ProBuyBox
ctaLabel prop instead of returning literal English strings.

---

Nitpick comments:
In `@src/components/pro/pro-buy-box-options.ts`:
- Around line 23-24: The eventProperties field is too loosely typed and hides
the known payload shape. Update the type in pro-buy-box-options so the builder
and client consumer both use the exact object shape with experiment, variant,
product, and plan instead of Record<string, unknown>. Also apply the same
precise typing in the related builder/consumer code around the referenced
section so future field drops or renames are caught at compile time.
- Around line 31-45: The lifetime CTA year count in lifetimeCtaNote can be
overstated because Math.round may promote a non-integer ratio (for example, 1.6)
to 2 and trigger the stronger copy. Update the years derivation in
lifetimeCtaNote to avoid rounding up—prefer a conservative calculation such as
flooring or otherwise requiring a true whole-number ratio—so the
t('buyBox.lifetime.ctaNote', { years }) path only uses accurate years values.

In `@src/components/pro/pro-buy-box.tsx`:
- Around line 39-44: `ProBuyBox` assumes `options[0]` always exists when
initializing `selected` and computing `current`, which can crash if an empty
options array is ever passed. Update `ProBuyBox` to avoid unconditional indexing
by adding a non-empty guard or a safe fallback before using `options[0]`, and
ensure the logic around `useState`, `current`, and any dependent rendering
handles the empty-array case explicitly. Refer to `ProBuyBox` and
`ProBuyBoxProps` when making the fix so the component is robust even if future
callers skip the current `offers.length === 0` guard.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a1d940f-0242-49d8-9e35-b782f3b07612

📥 Commits

Reviewing files that changed from the base of the PR and between 5704352 and f135279.

📒 Files selected for processing (19)
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/nl.json
  • messages/pt.json
  • messages/zh.json
  • src/app/[locale]/(main)/pro/page.test.tsx
  • src/app/[locale]/(main)/pro/page.tsx
  • src/components/pro/pro-buy-box-options.test.ts
  • src/components/pro/pro-buy-box-options.ts
  • src/components/pro/pro-buy-box.test.tsx
  • src/components/pro/pro-buy-box.tsx
  • src/components/pro/pro-features.tsx
  • src/lib/pro-offer-catalog.test.ts
  • src/lib/pro-offer-catalog.ts
✅ Files skipped from review due to trivial changes (5)
  • messages/en.json
  • messages/de.json
  • messages/es.json
  • messages/ja.json
  • messages/nl.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/components/pro/pro-features.tsx
  • messages/fr.json
  • messages/ko.json
  • messages/it.json
  • messages/zh.json
  • messages/pt.json
  • src/app/[locale]/(main)/pro/page.test.tsx

Comment thread src/app/[locale]/(main)/pro/page.tsx
@wcpos-bot

wcpos-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Round 4 triage before fixes (fresh unresolved review-thread inventory):

# Source File Class Category Decision Outcome
1 CodeRabbit page.tsx:116 Logic Primary CTA label bypasses localization via getProCheckoutCtaLabel() returning hardcoded English Fix Resolve CTA label through pro.buyBox translation keys; commit pending
2 CodeRabbit review body pro-buy-box-options.ts:23 Mechanical eventProperties is typed as Record<string, unknown> instead of its known analytics payload shape Fix Add a precise payload type shared by the option builder and consumer; commit pending
3 CodeRabbit review body pro-buy-box-options.ts:31 Logic Lifetime years copy can overstate by rounding a fractional price ratio up Fix Use a conservative floor-based year count before selecting the stronger copy; commit pending
4 CodeRabbit review body pro-buy-box.tsx:39 Logic ProBuyBox dereferences options[0] before guarding against an empty options array Fix Add an explicit empty-options branch before state initialization and dependent rendering; commit pending

Skipped threads: none.

CI scope: gh pr checks reports Test, E2E, CodeQL, Deploy Preview, and CodeRabbit passing; auto-merge/deploy production are skipped by policy.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kilbot has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@wcpos-bot

wcpos-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Round 4 final triage after fixes (fresh unresolved review-thread inventory: 0):

Thread File Issue Decision Commit
CodeRabbit CTA localization page.tsx Primary buy-box CTA label bypassed localization via hardcoded English literals. Fixed and resolved: CTA label now resolves through pro.buyBox translation keys. 63eefae
CodeRabbit review body pro-buy-box-options.ts eventProperties used a loose Record<string, unknown> instead of the known checkout analytics payload shape. Fixed: added a precise ProBuyBoxEventProperties type. 63eefae
CodeRabbit review body pro-buy-box-options.ts Lifetime CTA year count could overstate fractional price ratios by rounding up. Fixed: changed the derivation to floor the ratio and added coverage for the fractional case. 63eefae
CodeRabbit review body pro-buy-box.tsx ProBuyBox could dereference options[0] if a future caller passed an empty options list. Fixed: added a safe initial plan and explicit empty-options render branch with a unit test. 63eefae
CodeRabbit review body pro-buy-box.tsx Lifetime priceSuffix depended on leading whitespace in translated strings. Fixed: spacing now lives in the component and locale suffix strings no longer carry leading spaces. 63eefae
CodeRabbit review body pro.spec.ts Pro E2E tests used fixed waitForTimeout(2000) waits after readiness checks. Fixed: removed the fixed waits and kept Playwright auto-waiting assertions. 63eefae

Skipped threads:

Thread Reason for skipping
None Fresh unresolved review-thread inventory is empty; all addressed inline review threads are fixed/resolved.

Validation:

  • pnpm install --frozen-lockfile passed.
  • pnpm lint passed with existing warnings in src/services/core/business/electron-service.ts.
  • pnpm exec tsc --noEmit passed.
  • pnpm test:unit passed: 135 files / 917 tests.
  • pnpm test:e2e --project=chromium was attempted locally and blocked by the container: Chromium cannot launch because libglib-2.0.so.0 is missing. pnpm exec playwright install-deps chromium also failed because the container cannot switch to root (su: Authentication failure).

CI scope: checks for pushed head 63eefae are currently pending (Test, CodeQL Analyze, CodeRabbit). Review-thread scope is complete: unresolved inline thread count is 0.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚀 Preview: https://wcpos-3x7yj5vbd-wcpos.vercel.app

@kilbot kilbot merged commit f7db95f into main Jul 2, 2026
9 checks passed
@kilbot kilbot deleted the worktree-pro-pricing-buy-box branch July 2, 2026 12:06
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.

1 participant