Skip to content

Prevent pricing CTA loading layout shifts - #8806

Merged
lawrencecchen merged 5 commits into
mainfrom
task-pricing-cta-loading-layout
Jul 24, 2026
Merged

lawrencecchen merged 5 commits into
mainfrom
task-pricing-cta-loading-layout

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • keep checkout labels in layout while their loading spinner is shown
  • center the spinner over full-size and compact pricing CTAs
  • cover the loading markup with a focused regression test

Testing

  • cd web && bun test tests/checkout-navigation.test.tsx tests/pricing-page.test.tsx tests/app-pricing-page.test.tsx
  • cd web && bun run typecheck
  • cd web && bunx eslint app/components/checkout-navigation.tsx app/[locale]/components/pro-cta-link.tsx tests/checkout-navigation.test.tsx
  • browser geometry check: full-size and compact buttons retained their exact dimensions with zero spinner center offset

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes layout shifts in pricing CTAs during checkout by keeping the label in flow and overlaying a centered spinner. Works for both full-size and compact buttons, with tests for pending and idle spinner presence.

  • Bug Fixes
    • Added CheckoutPendingContent to keep labels invisible and center the spinner; CTA containers are now relative.
    • Updated CheckoutButton and ProCtaLink to use it; added web/tests/checkout-navigation.test.tsx to assert label retention, centering classes, and spinner <svg> presence when pending, and absence when idle.

Written for commit 31a2015. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved checkout pending/loading behavior by overlaying a centered spinner while keeping the button’s label markup present (hidden during loading to avoid flicker).
    • Ensured pending checkout actions continue to block interactions and report busy state consistently.
  • Tests
    • Added automated server-rendering assertions for the pending vs. idle checkout navigation/button HTML, including preserved label text and correct spinner centering classes.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Checkout loading content is centralized in CheckoutPendingContent, adopted by CheckoutButton and the Pro CTA, and covered by server-rendered tests for pending and idle visibility and spinner positioning.

Changes

Checkout pending UI

Layer / File(s) Summary
Reusable pending content component
web/app/components/checkout-navigation.tsx, web/tests/checkout-navigation.test.tsx
CheckoutPendingContent overlays a centered spinner while preserving the label with invisible styling; CheckoutButton uses it, and tests verify pending and idle rendered classes and label content.
Pro CTA integration
web/app/[locale]/components/pro-cta-link.tsx
The Pro CTA uses CheckoutPendingContent for pending rendering and adds relative positioning to its anchor.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProCtaLink
  participant CheckoutPendingContent
  participant CheckoutSpinner
  ProCtaLink->>CheckoutPendingContent: pass pending state and CTA children
  CheckoutPendingContent->>CheckoutSpinner: render centered spinner while pending
  CheckoutPendingContent-->>ProCtaLink: preserve label with pending visibility styling
Loading

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Cmux Expensive Synchronous Load ❓ Inconclusive placeholder pending investigation
✅ Passed checks (24 passed)
Check name Status Explanation
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.
Cmux Swift Actor Isolation ✅ Passed The PR diff only touches web/tests files; no Swift declarations or actor-isolation-sensitive code changed.
Cmux Swift Blocking Runtime ✅ Passed No Swift files or Swift blocking/timing primitives were introduced; the diff is limited to web TSX/test changes.
Cmux Browser Automation Off-Main ✅ Passed PR tip only changes web/tests/checkout-navigation.test.tsx; no browser.* socket commands, mainActor routing, or policy files were modified.
Cmux Cache Substitution Correctness ✅ Passed Only transient CTA loading UI changed; no persistence/history/snapshot path or cached-vs-fresh substitution appears in the diff, so the cache rule doesn't apply.
Cmux No Hacky Sleeps ✅ Passed No fixed sleeps, timers, polling, or wall-clock waits appear in the changed runtime code; the only actual diff is a test assertion update.
Cmux Algorithmic Complexity ✅ Passed Changes only wrap CTA text/spinner in small UI components; no collection scans, sorts, filters, or batch rescans in production paths.
Cmux Swift Concurrency ✅ Passed HEAD changes only web/tests/checkout-navigation.test.tsx; no Swift files or concurrency patterns were introduced, so the Swift modernization check is not applicable.
Cmux Swift @Concurrent ✅ Passed No Swift files are changed in this PR; only a TSX test file was modified, so the Swift @concurrent rule is not applicable.
Cmux Swift Package Boundaries ✅ Passed PR range changes only TS/TSX web files; no Swift files, so the Swift package boundary rule is not implicated.
Cmux Swiftpm Lockfiles ✅ Passed Diff only changes web TS/TSX files; no SwiftPM/Xcode/.gitignore/workflow/dependency files are touched.
Cmux Swift Logging ✅ Passed No Swift files changed; the PR only touches TS/TSX checkout UI and test code, so the Swift logging rule is not applicable.
Cmux User-Facing Error Privacy ✅ Passed The patch only changes checkout loading UI and tests; it adds no user-facing errors, alerts, or recovery copy exposing sensitive implementation details.
Cmux Full Internationalization ✅ Passed No new production user-facing text was added; the only English literals are in tests, which the rule allows.
Cmux Swiftui State Layout ✅ Passed PASS: the PR diff only touches web TSX files, with no SwiftUI views, state objects, GeometryReader, or render-time state writes covered by the rule.
Cmux Architecture Rethink ✅ Passed PR only changes TSX/test checkout UI code; no Swift files or Swift architectural anti-patterns are present, so the check is not applicable.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: the PR diff only touches web TS/TSX files and adds a test; no Swift/NSWindow/WindowGroup changes, so the auxiliary-window shortcut rule is not applicable.
Cmux Source Artifacts ✅ Passed Only hand-written TSX source and a focused test changed; no logs, screenshots, temp dirs, caches, or generated artifact paths appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PR only changes web TypeScript files and a test; no Swift production Sources/ files or test/debug seams appear in the diff.
Cmux No Ambient Global State ✅ Passed PR touches only web TS/TSX files; no production Swift changes or new ambient globals were introduced.
Title check ✅ Passed The title clearly matches the main change: preventing checkout CTA loading layout shifts.
Description check ✅ Passed It covers the required Summary and Testing sections and matches the template, though Demo Video and Checklist sections are missing.
✨ 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 task-pricing-cta-loading-layout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes layout shifts on pricing CTAs during checkout loading by keeping the button label in the DOM (visually hidden via visibility:hidden) while overlaying a centered spinner, rather than swapping the label out entirely.

  • Introduces CheckoutPendingContent, a small wrapper that renders children in an invisible span (preserving layout dimensions) and overlays CheckoutSpinner absolutely-centered when pending=true; both CheckoutButton and ProCtaLink adopt it and gain a relative class on their anchor.
  • CheckoutSpinner retains aria-hidden="true" and the parent anchors keep aria-busy={pending}, so the accessibility contract is unchanged.
  • A new checkout-navigation.test.tsx covers both the loading markup (invisible label + centered SVG spinner) and idle markup (visible label, no spinner).

Confidence Score: 5/5

Safe to merge — the change is a contained rendering fix with no behavioral side-effects.

The change wraps children in a lightweight span and adds absolute positioning for the spinner overlay. The parent anchors already carry aria-busy, the spinner keeps aria-hidden, and pointerEvents: none while pending is unchanged. Both affected call sites are updated consistently, and the new tests confirm the markup contract for both states.

No files require special attention.

Important Files Changed

Filename Overview
web/app/components/checkout-navigation.tsx Adds CheckoutPendingContent that keeps children in layout with visibility:hidden and overlays a centered spinner; updates CheckoutButton to use it with a relative parent class.
web/app/[locale]/components/pro-cta-link.tsx Adopts CheckoutPendingContent in place of the direct CheckoutSpinner swap; adds relative to the anchor class to anchor the spinner overlay.
web/tests/checkout-navigation.test.tsx New test file covering both the pending=true (invisible label + spinner) and pending=false (visible label, no spinner) states of CheckoutPendingContent.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["CheckoutButton / ProCtaLink\n(class: pricingActionClassName + relative)"] --> B["CheckoutPendingContent\n{ pending, children }"]
    B --> C{pending?}
    C -- true --> D["span.invisible{children}\n(keeps layout, hides visually)"]
    C -- true --> E["span.absolute.inset-0.flex.items-center.justify-center\n  CheckoutSpinner aria-hidden"]
    C -- false --> F["span{children}\n(visible, no class)"]
    A -- aria-busy={pending} --> G["Screen reader: element is loading"]
Loading

Reviews (3): Last reviewed commit: "test pricing checkout spinner presence" | Re-trigger Greptile

Comment on lines +6 to +18
describe("checkout loading content", () => {
test("keeps the label in layout and centers the pending spinner", () => {
const html = renderToStaticMarkup(
<CheckoutPendingContent pending>Get Teams</CheckoutPendingContent>,
);

expect(html).toContain('class="invisible"');
expect(html).toContain(
'class="absolute inset-0 flex items-center justify-center"',
);
expect(html).toContain("Get Teams");
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The test only exercises pending=true. A second case for pending={false} would guard against regressions where the spinner leaks into the non-loading state (e.g., if the conditional is accidentally inverted) or the label is accidentally hidden when the button is idle.

Suggested change
describe("checkout loading content", () => {
test("keeps the label in layout and centers the pending spinner", () => {
const html = renderToStaticMarkup(
<CheckoutPendingContent pending>Get Teams</CheckoutPendingContent>,
);
expect(html).toContain('class="invisible"');
expect(html).toContain(
'class="absolute inset-0 flex items-center justify-center"',
);
expect(html).toContain("Get Teams");
});
});
describe("checkout loading content", () => {
test("keeps the label in layout and centers the pending spinner", () => {
const html = renderToStaticMarkup(
<CheckoutPendingContent pending>Get Teams</CheckoutPendingContent>,
);
expect(html).toContain('class="invisible"');
expect(html).toContain(
'class="absolute inset-0 flex items-center justify-center"',
);
expect(html).toContain("Get Teams");
});
test("shows the label and no spinner when not pending", () => {
const html = renderToStaticMarkup(
<CheckoutPendingContent pending={false}>Get Teams</CheckoutPendingContent>,
);
expect(html).not.toContain("invisible");
expect(html).not.toContain("absolute inset-0");
expect(html).toContain("Get Teams");
});
});

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added the complementary pending={false} regression case in c217e75. It verifies the idle label stays visible and no spinner overlay renders.

— Claude Code

@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@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

🤖 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 `@web/tests/checkout-navigation.test.tsx`:
- Around line 26-28: Update the checkout navigation assertions in the relevant
test cases to check for the spinner’s stable marker or element directly: assert
it is absent in idle output and present in pending output. Retain the existing
positioning-class assertions and the “Get Teams” content check.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 3511c874-a9ef-421f-b37c-94fdec57cd4b

📥 Commits

Reviewing files that changed from the base of the PR and between d04b845 and c217e75.

📒 Files selected for processing (1)
  • web/tests/checkout-navigation.test.tsx

Comment thread web/tests/checkout-navigation.test.tsx
@lawrencecchen
lawrencecchen merged commit fc0a930 into main Jul 24, 2026
10 checks passed
@lawrencecchen
lawrencecchen deleted the task-pricing-cta-loading-layout branch July 24, 2026 04:52
@coderabbitai coderabbitai Bot mentioned this pull request Jul 30, 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.

1 participant