Skip to content

feat: paykit v2#195

Merged
maxktz merged 83 commits into
mainfrom
feat/polar-remove
Jun 6, 2026
Merged

feat: paykit v2#195
maxktz merged 83 commits into
mainfrom
feat/polar-remove

Conversation

@maxktz

@maxktz maxktz commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • remove Polar and standalone provider packages, moving Stripe support into PayKit core
  • switch createPayKit configuration to required built-in Stripe options
  • simplify demo app config, scripts, routes, docs, and e2e harness for Stripe-only usage
  • add Stripe-only schema migration and update logic touching provider identifiers

Verification

  • pnpm --filter paykitjs typecheck
  • pnpm --filter e2e typecheck
  • pnpm test:unit -- packages/paykit/src/api/tests/methods.test.ts
  • focused Stripe e2e reruns for the previously failing specs
  • full Stripe e2e suite: 19 passed / 19 files, 21 passed / 21 tests
  • git diff --check

Summary by CodeRabbit

Release Notes

  • New Features

    • Consolidated PayKit to focus exclusively on Stripe with simplified configuration syntax
  • Documentation

    • Restructured guides with Stripe-focused examples; removed provider-specific documentation pages
    • Updated CLI commands and installation instructions
  • Style

    • Refreshed UI components with updated icon library and theme tokens
    • Added blog and sponsor pages to marketing site

@vercel

vercel Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
paykit Ready Ready Preview, Comment Jun 6, 2026 6:54am

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges.

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Convert PayKit to Stripe-only: configure via options, replace provider JSON with Stripe columns, migrate schema, update core/services/webhooks/CLI, unify demo to a single paykit route/client, adjust e2e harness/tests, and rewrite docs and site content accordingly.

Changes

Stripe-only refactor and ecosystem updates

Layer / File(s) Summary
Stripe-only end-to-end refactor
packages/paykit/src/**/*, apps/demo/**/*, e2e/**/*, apps/web/**/*, README.md, scripts/*, packages/polar/*, packages/stripe/*
Implements Stripe options and adapter, migrates DB and services to Stripe columns, updates CLI/listen/push/status, unifies demo routes/client, removes Polar/adapter packages, updates e2e harness/tests and documentation/site to Stripe-first.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • getpaykit/paykit#173 — Touches the same webhook contract/flow; this PR replaces allowStaleSignatures with allowUnsignedPayload across API/provider.
  • getpaykit/paykit#121 — Modifies the same copy-markdown button logic now further expanded in this PR.
  • getpaykit/paykit#168 — Overlaps demo billing UI/routes that this PR unifies into a single PayKit tab and route.

Poem

A rabbit taps its keys with glee,
One stripe of moonlight, billing free;
Webhooks hum, the plans align,
Migrations march in tidy line.
Farewell, the crowded warren’s sprawl—
One path hops swift, and serves us all. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/polar-remove

@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: 6

Caution

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

⚠️ Outside diff range comments (6)
apps/web/src/components/sections/readme-code-content.ts (1)

28-40: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stripe wiring update LGTM, but the example callback uses await without async.

Lines 36-38 use await sendEmail(...) inside a non-async arrow, which is a syntax error for anyone copying this hero snippet.

📝 Proposed fix
   on: {
-    "subscription.activated": ({ customer, plan }) => {
+    "subscription.activated": async ({ customer, plan }) => {
       await sendEmail(customer.email, "Welcome to Pro!")
     },
   }
🤖 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 `@apps/web/src/components/sections/readme-code-content.ts` around lines 28 -
40, The event handler for "subscription.activated" in the createPayKit call uses
await inside a non-async arrow function causing a syntax error; update the
handler for that event (the "subscription.activated" callback used in
paykit/createPayKit) to be async (e.g., make the arrow async: async ({ customer,
plan }) => { await sendEmail(...) }) or remove the await and return the promise
from sendEmail, ensuring sendEmail is invoked correctly.
packages/paykit/src/core/create-paykit.ts (1)

68-90: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Attach a rejection handler to the cached context promise to prevent unhandledRejection.

initContext() can reject (e.g., pending migrations). In createPayKit, contextPromise = initContext(options) is created eagerly and passed into getApi, but wrapMethods only awaits the ctx promise inside each method wrapper (and handler()/$context do the same), so no immediate .catch/await is attached at context creation time. This can allow Node to emit unhandledRejection before any caller invokes a method/handler.

Add a rejection handler right after setting contextPromise (e.g., contextPromise.catch(() => {}), or equivalent) so the rejection is handled without swallowing the error.

🤖 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 `@packages/paykit/src/core/create-paykit.ts` around lines 68 - 90, The cached
context promise created in createPayKit (contextPromise assigned via initContext
in getContext) can reject and cause unhandledRejection because no handler is
attached immediately; after you set contextPromise (contextPromise ??=
initContext(options)) attach a rejection handler (e.g., contextPromise.catch(()
=> {})) so the rejection is handled right away while preserving the existing
behavior where wrappers (handler/$context/wrapped API methods produced by
getApi/wrapMethods) still await the promise; ensure you add this line in the
getContext scope right after the assignment to contextPromise.
packages/paykit/src/cli/commands/status.ts (1)

80-86: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent section label: error path still says Provider.

Success output (Lines 150, 156) was renamed to Stripe, but this failure block still prints Provider/Fix provider issues. Align for consistency.

Proposed fix
-    p.log.error(`Provider\n  ${picocolors.red("✖")} ${providerResult.account.message}`);
-    p.outro("Fix provider issues before continuing");
+    p.log.error(`Stripe\n  ${picocolors.red("✖")} ${providerResult.account.message}`);
+    p.outro("Fix Stripe issues before continuing");
🤖 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 `@packages/paykit/src/cli/commands/status.ts` around lines 80 - 86, The failure
block prints "Provider" and "Fix provider issues" but the success messages were
renamed to "Stripe"; update the error section to match by replacing the
"Provider" label and the outro text accordingly. Locate the block that checks
providerResult.account.ok (uses s.stop(""), p.log.error, p.outro, await
database.end(), process.exit) and change the displayed label in p.log.error from
"Provider" to "Stripe" and update p.outro to "Fix Stripe issues before
continuing" so the success and failure texts are consistent.
packages/paykit/src/cli/commands/push.ts (1)

49-54: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent section label: error path still says Provider.

The success path (Line 96) and config errors now use the Stripe label, but this failure block still prints Provider. Align it for consistent CLI output.

Proposed fix
-      p.log.error(`Provider\n  ${picocolors.red("✖")} ${providerResult.account.message}`);
+      p.log.error(`Stripe\n  ${picocolors.red("✖")} ${providerResult.account.message}`);
🤖 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 `@packages/paykit/src/cli/commands/push.ts` around lines 49 - 54, The error
block that checks providerResult.account.ok prints the wrong section label
"Provider"; update the log to use the "Stripe" label for consistency with the
success and config error paths by changing the string passed to p.log.error from
`Provider\n  ${picocolors.red("✖")} ...` to `Stripe\n  ${picocolors.red("✖")}
${providerResult.account.message}`, leaving the surrounding calls (s.stop(""),
p.cancel("Push failed"), process.exit(1)) and symbols
(providerResult.account.ok, s.stop, p.log.error, picocolors.red, p.cancel,
process.exit) unchanged.
packages/paykit/src/webhook/webhook.api.ts (1)

17-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Confirm whether the legacy PAYKIT_ALLOW_STALE_SIGNATURES env var is still intended.

The rename from allowStaleSignaturesallowUnsignedPayload carried over the old env var on Line 19 alongside the new PAYKIT_ALLOW_UNSIGNED_PAYLOADS. If this is intentional backward compatibility it should be documented with a short comment; otherwise it's a dangling reference to a removed flag that silently keeps a signature-bypass path alive.

🔧 If backward compat is not intended
   return (
     process.env.PAYKIT_ALLOW_UNSIGNED_PAYLOADS === "1" ||
-    process.env.PAYKIT_ALLOW_STALE_SIGNATURES === "1" ||
     process.env.NODE_ENV === "development" ||
     process.env.NODE_ENV === "test"
   );
🤖 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 `@packages/paykit/src/webhook/webhook.api.ts` around lines 17 - 22, The
conditional that checks environment variables in webhook.api.ts still references
the legacy PAYKIT_ALLOW_STALE_SIGNATURES alongside
PAYKIT_ALLOW_UNSIGNED_PAYLOADS; either remove the legacy reference or explicitly
document it: if backward compatibility is intended, add a short inline comment
next to the return expression explaining that PAYKIT_ALLOW_STALE_SIGNATURES is
kept for legacy support and should be removed in a future major; if not
intended, delete the PAYKIT_ALLOW_STALE_SIGNATURES check from the return
expression so only PAYKIT_ALLOW_UNSIGNED_PAYLOADS (and NODE_ENV checks) control
unsigned-stub behavior.
packages/paykit/src/stripe/stripe-provider.ts (1)

943-955: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move the signature guard behind allowUnsignedPayload.

allowUnsignedPayload is checked only after PROVIDER_SIGNATURE_MISSING can already be thrown, so the new flag never works for unsigned deliveries. packages/paykit/src/cli/commands/listen.ts now relies on this path for direct tunnel replays.

Suggested fix
     async handleWebhook(data) {
-      const headerKey = Object.keys(data.headers).find(
-        (k) => k.toLowerCase() === "stripe-signature",
-      );
-      const signature = headerKey ? data.headers[headerKey] : undefined;
-      if (!signature) {
-        throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_SIGNATURE_MISSING);
-      }
-
-      const event = data.allowUnsignedPayload
-        ? (JSON.parse(data.body) as StripeSdk.Event)
-        : await client.webhooks.constructEventAsync(data.body, signature, options.webhookSecret);
+      const event = data.allowUnsignedPayload
+        ? (JSON.parse(data.body) as StripeSdk.Event)
+        : await (async () => {
+            const headerKey = Object.keys(data.headers).find(
+              (k) => k.toLowerCase() === "stripe-signature",
+            );
+            const signature = headerKey ? data.headers[headerKey] : undefined;
+            if (!signature) {
+              throw PayKitError.from(
+                "BAD_REQUEST",
+                PAYKIT_ERROR_CODES.PROVIDER_SIGNATURE_MISSING,
+              );
+            }
+            return client.webhooks.constructEventAsync(
+              data.body,
+              signature,
+              options.webhookSecret,
+            );
+          })();
       return [
🤖 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 `@packages/paykit/src/stripe/stripe-provider.ts` around lines 943 - 955, In
handleWebhook, the signature missing guard runs before allowUnsignedPayload is
checked so unsigned deliveries are blocked; change the flow in the handleWebhook
method to first branch on data.allowUnsignedPayload and, if true, parse JSON
from data.body into a StripeSdk.Event, otherwise perform the existing signature
lookup (headerKey/signature) and throw the PayKitError with
PAYKIT_ERROR_CODES.PROVIDER_SIGNATURE_MISSING if no signature, then call
client.webhooks.constructEventAsync(...) with data.body, signature and
options.webhookSecret; keep all existing variable names (headerKey, signature,
data.allowUnsignedPayload, PAYKIT_ERROR_CODES.PROVIDER_SIGNATURE_MISSING,
client.webhooks.constructEventAsync) and return the event as before.
🧹 Nitpick comments (3)
e2e/cli/push.test.ts (1)

85-93: 💤 Low value

Nit: providerRows name is stale.

The rows no longer come from a provider field; consider proRows for clarity.

🤖 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/cli/push.test.ts` around lines 85 - 93, Rename the stale variable
providerRows to proRows in the select block and update its use when extracting
the first row into proProduct; specifically update the identifier providerRows
in the lines using ctx.database.select(...).from(product)... to proRows and
adjust any subsequent references (e.g., the assignment to proProduct) so the new
name is used consistently.
packages/paykit/src/cli/utils/shared.ts (1)

181-186: ⚖️ Poor tradeoff

checkActiveSubscriptionsOnOtherProvider is now a no-op.

It ignores both params and always returns [], yet callers in push.ts/status.ts still compute providerId = "stripe" and await it as a preflight check. In a Stripe-only world this cross-provider check is dead. Consider removing the function and its call sites in a follow-up to avoid misleading preflight semantics.

🤖 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 `@packages/paykit/src/cli/utils/shared.ts` around lines 181 - 186, The function
checkActiveSubscriptionsOnOtherProvider is a no-op and causes misleading
preflight behavior; remove the function and its call sites instead of leaving it
returning [] — locate references to checkActiveSubscriptionsOnOtherProvider in
push.ts and status.ts (where providerId is computed as "stripe"), delete the
await calls and any related handling, and remove the import/export of
checkActiveSubscriptionsOnOtherProvider from
packages/paykit/src/cli/utils/shared.ts; ensure any leftover variables (e.g.,
providerId or returned values) are cleaned up or adjusted and run tests to
confirm no unused-import/variable errors.
packages/paykit/src/cli/commands/init.ts (1)

551-552: Open TODO for paykitjs listen.

The forwarded webhook command is hardcoded to the Stripe CLI with a TODO to swap in paykitjs listen.

Want me to open an issue to track replacing this with paykitjs listen?

🤖 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 `@packages/paykit/src/cli/commands/init.ts` around lines 551 - 552, Replace the
hardcoded webhookCommand string with a small helper that prefers the new paykit
CLI: implement a getWebhookListenCommand() (and optional detectPaykitCli()) and
assign webhookCommand = getWebhookListenCommand(port) so it returns "paykitjs
listen --forward-to localhost:3000/paykit/webhook" when the paykit CLI is
available and falls back to "stripe listen --forward-to ..." otherwise; update
the TODO by removing the hardcoded string and ensuring the helper is referenced
from init.ts (webhookCommand) and covered by a simple unit test for both code
paths.
🤖 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 `@e2e/test-utils/harness/stripe.ts`:
- Around line 91-94: The current use of cardPaymentButton.evaluate(...click())
bypasses Playwright actionability checks; keep the existing count() conditional
but replace the evaluate call with an actionable locator click: use
cardPaymentButton.first().waitFor({ state: "visible" }) (or waitFor({ state:
"enabled" }) as appropriate) and then await cardPaymentButton.first().click() so
the click uses Playwright's built-in waits and retries; update the code in the
block referencing the cardPaymentButton locator accordingly.

In `@packages/paykit/src/cli/commands/listen.ts`:
- Around line 94-106: The function getEnvStripeOptions currently returns a fake
"whsec_placeholder" when no webhook secret env var exists; change it to
explicitly represent "unknown" by returning webhookSecret as undefined (and
update the function signature to allow webhookSecret?: string | undefined)
instead of synthesizing a value, and then ensure callers (e.g.,
ensureTunnelWebhook in stripe-provider.ts and any code that prints
options.webhookSecret) treat undefined as "secret unknown" and avoid
echoing/saving a placeholder.
- Around line 682-692: The current catch around getPayKitConfig() treats every
error as "no config" and falls back to envs; change it to only fall back when
the error is a genuine "config not found" case and rethrow all other errors
(syntax/validation/missing env) so they surface. Implement a check (e.g., add or
use an isConfigNotFound(error) helper or test error.name/message/code) inside
the catch for getPayKitConfig(...) — if isConfigNotFound(error) is true then
call loadDotEnv(params.cwd) and stripeOptions = getEnvStripeOptions(), otherwise
throw the error (respecting params.configPath || params.requireConfig logic).
Ensure references: getPayKitConfig, params.configPath, params.requireConfig,
loadDotEnv, getEnvStripeOptions, basePath, stripeOptions.

In `@packages/paykit/src/database/migrations/0001_stripe_only_schema.sql`:
- Around line 65-70: The migration adds a new column paykit_payment_method.brand
but never backfills it from the existing JSON provider_data, so dropping
provider_data will lose historical brand values; update the migration (around
ALTER TABLE "paykit_payment_method" ADD COLUMN "brand" text and the related
blocks at the other ranges) to run an UPDATE that sets brand =
provider_data->>'brand' for all rows (and ensure NULL-safe handling), verify the
backfill completes before any DROP COLUMN "provider_data" statements, and apply
the same backfill logic for the other affected migration sections (lines
referenced 89-104 and 147-148) so historical brand data is preserved.
- Around line 55-60: The migration currently adds stripe_frozen_time as type
timestamp which drops the Z/UTC offset; change the ALTER TABLE that adds
stripe_frozen_time on paykit_customer to use timestamptz instead of timestamp,
and update any related backfill/CAST logic (the backfill that converts
ISO-8601/Z values into stripe_frozen_time) to cast to timestamptz so the
original instant is preserved when reading/writing from node-postgres; ensure
the column name stripe_frozen_time and the ALTER TABLE statement are the only
places modified.

In `@packages/paykit/src/payment-method/payment-method.service.ts`:
- Around line 49-54: The current lookup in
database.query.paymentMethod.findFirst excludes soft-deleted rows
(isNull(paymentMethod.deletedAt)), so after deletePaymentMethodByProviderId a
later upsert creates a duplicate; change the upsert flow to include soft-deleted
rows by removing the isNull filter (or explicitly searching both deleted and
non-deleted) when querying by stripePaymentMethodId
(paymentMethod.stripePaymentMethodId) and if a row is found with deletedAt set,
perform an update to clear deletedAt and refresh the record fields instead of
inserting a new row; keep the existing update path that clears deletedAt intact
and ensure deletePaymentMethodByProviderId still sets deletedAt only.

---

Outside diff comments:
In `@apps/web/src/components/sections/readme-code-content.ts`:
- Around line 28-40: The event handler for "subscription.activated" in the
createPayKit call uses await inside a non-async arrow function causing a syntax
error; update the handler for that event (the "subscription.activated" callback
used in paykit/createPayKit) to be async (e.g., make the arrow async: async ({
customer, plan }) => { await sendEmail(...) }) or remove the await and return
the promise from sendEmail, ensuring sendEmail is invoked correctly.

In `@packages/paykit/src/cli/commands/push.ts`:
- Around line 49-54: The error block that checks providerResult.account.ok
prints the wrong section label "Provider"; update the log to use the "Stripe"
label for consistency with the success and config error paths by changing the
string passed to p.log.error from `Provider\n  ${picocolors.red("✖")} ...` to
`Stripe\n  ${picocolors.red("✖")} ${providerResult.account.message}`, leaving
the surrounding calls (s.stop(""), p.cancel("Push failed"), process.exit(1)) and
symbols (providerResult.account.ok, s.stop, p.log.error, picocolors.red,
p.cancel, process.exit) unchanged.

In `@packages/paykit/src/cli/commands/status.ts`:
- Around line 80-86: The failure block prints "Provider" and "Fix provider
issues" but the success messages were renamed to "Stripe"; update the error
section to match by replacing the "Provider" label and the outro text
accordingly. Locate the block that checks providerResult.account.ok (uses
s.stop(""), p.log.error, p.outro, await database.end(), process.exit) and change
the displayed label in p.log.error from "Provider" to "Stripe" and update
p.outro to "Fix Stripe issues before continuing" so the success and failure
texts are consistent.

In `@packages/paykit/src/core/create-paykit.ts`:
- Around line 68-90: The cached context promise created in createPayKit
(contextPromise assigned via initContext in getContext) can reject and cause
unhandledRejection because no handler is attached immediately; after you set
contextPromise (contextPromise ??= initContext(options)) attach a rejection
handler (e.g., contextPromise.catch(() => {})) so the rejection is handled right
away while preserving the existing behavior where wrappers
(handler/$context/wrapped API methods produced by getApi/wrapMethods) still
await the promise; ensure you add this line in the getContext scope right after
the assignment to contextPromise.

In `@packages/paykit/src/stripe/stripe-provider.ts`:
- Around line 943-955: In handleWebhook, the signature missing guard runs before
allowUnsignedPayload is checked so unsigned deliveries are blocked; change the
flow in the handleWebhook method to first branch on data.allowUnsignedPayload
and, if true, parse JSON from data.body into a StripeSdk.Event, otherwise
perform the existing signature lookup (headerKey/signature) and throw the
PayKitError with PAYKIT_ERROR_CODES.PROVIDER_SIGNATURE_MISSING if no signature,
then call client.webhooks.constructEventAsync(...) with data.body, signature and
options.webhookSecret; keep all existing variable names (headerKey, signature,
data.allowUnsignedPayload, PAYKIT_ERROR_CODES.PROVIDER_SIGNATURE_MISSING,
client.webhooks.constructEventAsync) and return the event as before.

In `@packages/paykit/src/webhook/webhook.api.ts`:
- Around line 17-22: The conditional that checks environment variables in
webhook.api.ts still references the legacy PAYKIT_ALLOW_STALE_SIGNATURES
alongside PAYKIT_ALLOW_UNSIGNED_PAYLOADS; either remove the legacy reference or
explicitly document it: if backward compatibility is intended, add a short
inline comment next to the return expression explaining that
PAYKIT_ALLOW_STALE_SIGNATURES is kept for legacy support and should be removed
in a future major; if not intended, delete the PAYKIT_ALLOW_STALE_SIGNATURES
check from the return expression so only PAYKIT_ALLOW_UNSIGNED_PAYLOADS (and
NODE_ENV checks) control unsigned-stub behavior.

---

Nitpick comments:
In `@e2e/cli/push.test.ts`:
- Around line 85-93: Rename the stale variable providerRows to proRows in the
select block and update its use when extracting the first row into proProduct;
specifically update the identifier providerRows in the lines using
ctx.database.select(...).from(product)... to proRows and adjust any subsequent
references (e.g., the assignment to proProduct) so the new name is used
consistently.

In `@packages/paykit/src/cli/commands/init.ts`:
- Around line 551-552: Replace the hardcoded webhookCommand string with a small
helper that prefers the new paykit CLI: implement a getWebhookListenCommand()
(and optional detectPaykitCli()) and assign webhookCommand =
getWebhookListenCommand(port) so it returns "paykitjs listen --forward-to
localhost:3000/paykit/webhook" when the paykit CLI is available and falls back
to "stripe listen --forward-to ..." otherwise; update the TODO by removing the
hardcoded string and ensuring the helper is referenced from init.ts
(webhookCommand) and covered by a simple unit test for both code paths.

In `@packages/paykit/src/cli/utils/shared.ts`:
- Around line 181-186: The function checkActiveSubscriptionsOnOtherProvider is a
no-op and causes misleading preflight behavior; remove the function and its call
sites instead of leaving it returning [] — locate references to
checkActiveSubscriptionsOnOtherProvider in push.ts and status.ts (where
providerId is computed as "stripe"), delete the await calls and any related
handling, and remove the import/export of
checkActiveSubscriptionsOnOtherProvider from
packages/paykit/src/cli/utils/shared.ts; ensure any leftover variables (e.g.,
providerId or returned values) are cleaned up or adjusted and run tests to
confirm no unused-import/variable errors.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1059bfa6-d80b-4f37-9f95-137993970930

📥 Commits

Reviewing files that changed from the base of the PR and between 6dac3b5 and 2a18aed.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (103)
  • README.md
  • apps/demo/drizzle.config.ts
  • apps/demo/next.config.js
  • apps/demo/package.json
  • apps/demo/paykit.config.ts
  • apps/demo/paykit.polar.config.ts
  • apps/demo/paykit.stripe.config.ts
  • apps/demo/scripts/push-sandbox.ts
  • apps/demo/scripts/sandbox.ts
  • apps/demo/src/app/_components/checkout-page-content.tsx
  • apps/demo/src/app/_components/features-panel.tsx
  • apps/demo/src/app/_components/subscribe-panel.tsx
  • apps/demo/src/app/paykit-polar/[[...slug]]/route.ts
  • apps/demo/src/app/paykit-stripe/[[...slug]]/route.ts
  • apps/demo/src/app/paykit/[[...slug]]/route.ts
  • apps/demo/src/env.js
  • apps/demo/src/lib/paykit-client.ts
  • apps/demo/src/lib/paykit-scenarios.ts
  • apps/demo/src/lib/paykit.ts
  • apps/demo/src/lib/paykit/polar.ts
  • apps/demo/src/lib/paykit/stripe.ts
  • apps/demo/src/lib/scenario-config.ts
  • apps/demo/src/server/api/root.ts
  • apps/demo/src/server/api/routers/paykit-route.ts
  • apps/demo/src/server/db.ts
  • apps/web/content/docs/concepts/cli.mdx
  • apps/web/content/docs/concepts/payment-providers.mdx
  • apps/web/content/docs/get-started/index.mdx
  • apps/web/content/docs/get-started/installation.mdx
  • apps/web/content/docs/providers/creem.mdx
  • apps/web/content/docs/providers/lemonsqueezy.mdx
  • apps/web/content/docs/providers/meta.json
  • apps/web/content/docs/providers/paddle.mdx
  • apps/web/content/docs/providers/paypal.mdx
  • apps/web/content/docs/providers/polar.mdx
  • apps/web/content/docs/providers/stripe.mdx
  • apps/web/src/components/docs/docs-icons.tsx
  • apps/web/src/components/docs/features.tsx
  • apps/web/src/components/sections/demo/demo-types.tsx
  • apps/web/src/components/sections/features-section.tsx
  • apps/web/src/components/sections/readme-code-content.ts
  • e2e/cli/init.test.ts
  • e2e/cli/push.test.ts
  • e2e/cli/setup.ts
  • e2e/cli/status.test.ts
  • e2e/core/subscribe/cancel-end-of-cycle.test.ts
  • e2e/core/subscribe/renewal.test.ts
  • e2e/core/webhook/duplicate-webhook.test.ts
  • e2e/core/webhook/subscription-deleted.test.ts
  • e2e/package.json
  • e2e/test-utils/env.ts
  • e2e/test-utils/harness/index.ts
  • e2e/test-utils/harness/polar.ts
  • e2e/test-utils/harness/stripe.ts
  • e2e/test-utils/harness/types.ts
  • e2e/test-utils/setup.ts
  • e2e/vitest.config.ts
  • packages/paykit/package.json
  • packages/paykit/src/api/__tests__/define-route.test.ts
  • packages/paykit/src/api/__tests__/methods.test.ts
  • packages/paykit/src/api/methods.ts
  • packages/paykit/src/cli/commands/init.ts
  • packages/paykit/src/cli/commands/listen.ts
  • packages/paykit/src/cli/commands/push.ts
  • packages/paykit/src/cli/commands/status.ts
  • packages/paykit/src/cli/utils/get-config.ts
  • packages/paykit/src/cli/utils/shared.ts
  • packages/paykit/src/core/__tests__/context.test.ts
  • packages/paykit/src/core/__tests__/create-paykit.test.ts
  • packages/paykit/src/core/context.ts
  • packages/paykit/src/core/create-paykit.ts
  • packages/paykit/src/customer/__tests__/customer.service.test.ts
  • packages/paykit/src/customer/customer.service.ts
  • packages/paykit/src/database/migrations/0001_stripe_only_schema.sql
  • packages/paykit/src/database/migrations/meta/0001_snapshot.json
  • packages/paykit/src/database/migrations/meta/_journal.json
  • packages/paykit/src/database/schema.ts
  • packages/paykit/src/index.ts
  • packages/paykit/src/invoice/invoice.service.ts
  • packages/paykit/src/payment-method/payment-method.service.ts
  • packages/paykit/src/payment/payment.service.ts
  • packages/paykit/src/product/product.service.ts
  • packages/paykit/src/providers/provider.ts
  • packages/paykit/src/stripe/stripe-provider.ts
  • packages/paykit/src/subscription/subscription.service.ts
  • packages/paykit/src/testing/testing.service.ts
  • packages/paykit/src/types/instance.ts
  • packages/paykit/src/types/options.ts
  • packages/paykit/src/utilities/dependencies/paykit-package-list.ts
  • packages/paykit/src/webhook/webhook.api.ts
  • packages/paykit/src/webhook/webhook.service.ts
  • packages/polar/package.json
  • packages/polar/src/index.ts
  • packages/polar/src/polar-provider.ts
  • packages/polar/tsconfig.json
  • packages/polar/tsdown.config.ts
  • packages/stripe/package.json
  • packages/stripe/src/__tests__/stripe-provider.test.ts
  • packages/stripe/src/__tests__/stripe.test.ts
  • packages/stripe/src/index.ts
  • packages/stripe/tsconfig.json
  • packages/stripe/tsdown.config.ts
  • scripts/publish-dist.mjs
💤 Files with no reviewable changes (27)
  • apps/demo/src/lib/paykit/stripe.ts
  • packages/stripe/tsconfig.json
  • packages/stripe/src/tests/stripe.test.ts
  • apps/web/content/docs/providers/lemonsqueezy.mdx
  • apps/web/content/docs/providers/creem.mdx
  • apps/web/content/docs/providers/paypal.mdx
  • packages/stripe/src/index.ts
  • apps/demo/src/lib/paykit/polar.ts
  • apps/web/content/docs/providers/polar.mdx
  • apps/demo/paykit.stripe.config.ts
  • apps/demo/src/lib/paykit-scenarios.ts
  • packages/stripe/package.json
  • packages/polar/tsconfig.json
  • apps/web/content/docs/providers/paddle.mdx
  • e2e/test-utils/harness/polar.ts
  • packages/polar/tsdown.config.ts
  • packages/stripe/tsdown.config.ts
  • packages/paykit/src/testing/testing.service.ts
  • packages/stripe/src/tests/stripe-provider.test.ts
  • packages/polar/src/polar-provider.ts
  • e2e/package.json
  • apps/demo/src/app/paykit-stripe/[[...slug]]/route.ts
  • apps/demo/src/app/paykit-polar/[[...slug]]/route.ts
  • packages/polar/package.json
  • packages/polar/src/index.ts
  • apps/demo/paykit.polar.config.ts
  • apps/demo/src/lib/scenario-config.ts

Comment thread e2e/test-utils/harness/stripe.ts
Comment thread packages/paykit/src/cli/commands/listen.ts Outdated
Comment thread packages/paykit/src/cli/commands/listen.ts
Comment thread packages/paykit/src/database/migrations/0001_stripe_only_schema.sql
Comment thread packages/paykit/src/database/migrations/0001_stripe_only_schema.sql
Comment thread packages/paykit/src/payment-method/payment-method.service.ts

@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 (3)
packages/paykit/src/database/schema.ts (1)

231-248: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Schema defines stripeEventId as nullable but migration enforces NOT NULL.

The schema at line 235 defines stripeEventId without .notNull(), but the migration (line 158) adds ALTER TABLE ... ALTER COLUMN "stripe_event_id" SET NOT NULL. This mismatch means the Drizzle schema won't reflect the actual database constraint, potentially causing type-level confusion.

🔧 Proposed fix to align schema with migration
   webhookEvent = pgTable(
   "webhook_event",
   {
     id: text("id").primaryKey(),
-    stripeEventId: text("stripe_event_id").notNull(),
+    stripeEventId: text("stripe_event_id").notNull(),
     type: text("type").notNull(),

Wait, looking again at line 235, it already shows stripeEventId: text("stripe_event_id").notNull() - let me re-check... Actually line 235 shows just text("stripe_event_id") without .notNull(). The fix should be:

-    stripeEventId: text("stripe_event_id"),
+    stripeEventId: text("stripe_event_id").notNull(),
🤖 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 `@packages/paykit/src/database/schema.ts` around lines 231 - 248, The Drizzle
schema for the webhook_event table has stripeEventId defined without .notNull(),
but the migration enforces NOT NULL; update the webhookEvent table definition by
adding .notNull() to the stripeEventId column (symbol: stripeEventId in the
webhookEvent pgTable) so the schema matches the migration and the generated
types reflect the non-null constraint.
apps/web/content/docs/concepts/cli.mdx (1)

27-27: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the multi-provider reference.

The parenthetical "(or whichever provider you're using)" is inconsistent with the Stripe-only migration. This line should reference Stripe specifically to match the rest of the documentation.

📝 Proposed fix
-2. Syncs your plan definitions to the database and your payment provider, creating or updating products and prices in Stripe (or whichever provider you're using)
+2. Syncs your plan definitions to the database and Stripe, creating or updating products and prices
🤖 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 `@apps/web/content/docs/concepts/cli.mdx` at line 27, Update the sentence
fragment "Syncs your plan definitions to the database and your payment provider,
creating or updating products and prices in Stripe (or whichever provider you're
using)" to remove the multi‑provider parenthetical and explicitly reference
Stripe (e.g., end with "creating or updating products and prices in Stripe").
Locate the sentence in the CLI docs content (the line starting "Syncs your plan
definitions...") and replace the parenthetical "(or whichever provider you're
using)" with a Stripe‑specific phrasing so the text consistently references
Stripe only.
packages/paykit/src/cli/commands/status.ts (1)

48-68: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate the config here before using Stripe/database fields.

pushAction already calls assertValidPayKitOptions(...), but statusAction now reads config.options.stripe and opens the database without the same schema check. With the new Stripe-only contract, malformed config will fall through to createPool() / checkProvider() and show a generic runtime failure instead of a config error.

🤖 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 `@packages/paykit/src/cli/commands/status.ts` around lines 48 - 68, The status
flow currently reads config.options.stripe and calls createPool/checkProvider
without validating the config schema; call the existing validation function
assertValidPayKitOptions (or equivalent) on config.options near the start of
statusAction (before referencing config.options.stripe or creating the DB pool)
and handle validation failures by logging the config error and exiting, so
malformed configs produce a clear config error instead of runtime failures in
createPool/checkProvider.
🧹 Nitpick comments (2)
packages/paykit/src/stripe/stripe-provider.ts (1)

1066-1066: 💤 Low value

Empty string default for webhookSecret may cause confusing runtime failures.

Passing an empty string as webhookSecret to createStripeProvider will cause constructEventAsync to fail with a cryptic Stripe SDK error during webhook verification (when allowUnsignedPayload is false). This scenario can occur when the CLI's listen command creates an adapter without a pre-existing webhook secret.

Consider either:

  1. Throwing early if webhookSecret is required but missing
  2. Documenting that the empty string case is only valid when allowUnsignedPayload will always be true
🤖 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 `@packages/paykit/src/stripe/stripe-provider.ts` at line 1066, The current
wrapper returns createStripeProvider(client, { ...options, webhookSecret:
options.webhookSecret ?? "" }) which injects an empty string and leads to
cryptic failures in Stripe.constructEventAsync; remove the empty-string default
and instead pass webhookSecret only if present (e.g., leave it undefined when
missing) and add validation inside createStripeProvider to throw a clear error
when webhookSecret is missing while allowUnsignedPayload is false (referencing
createStripeProvider, webhookSecret, allowUnsignedPayload, and
constructEventAsync).
apps/web/src/components/docs/docs-icons.tsx (1)

125-195: 💤 Low value

Consider removing unused provider icon definitions.

The providerPageIcons object still contains SVG definitions for polar, paypal, lemonsqueezy, paddle, and creem, but only stripe is in the enabledProviders set. Since Polar is being removed from the project entirely and the other providers aren't currently supported, these icon definitions are unused code.

🧹 Optional cleanup to remove unused icons
 const providerPageIcons = {
   stripe: (
     <svg
       xmlns="http://www.w3.org/2000/svg"
       width="15"
       height="15"
       viewBox="0 0 24 24"
       className="docs-category-icon size-3! shrink-0 text-current"
     >
       <path
         fill="currentColor"
         d="M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409c0-.831.683-1.305 1.901-1.305c2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0C9.667 0 7.589.654 6.104 1.872C4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219c2.585.92 3.445 1.574 3.445 2.583c0 .98-.84 1.545-2.354 1.545c-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813c1.664-1.305 2.525-3.236 2.525-5.732c0-4.128-2.524-5.851-6.594-7.305z"
       />
     </svg>
   ),
-  paypal: (
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      width="15"
-      height="15"
-      viewBox="0 0 154.728 190.5"
-      className="docs-category-icon size-3! shrink-0 text-current"
-    >
-      <g transform="translate(898.192 276.071)">
-        <path
-          d="M-837.663-237.968a5.49 5.49 0 0 0-5.423 4.633l-9.013 57.15-8.281 52.514-.005.044.01-.044 8.281-52.514c.421-2.669 2.719-4.633 5.42-4.633h26.404c26.573 0 49.127-19.387 53.246-45.658.314-1.996.482-3.973.52-5.924v-.003h-.003c-6.753-3.543-14.683-5.565-23.372-5.565z"
-          fill="currentColor"
-        />
-        <path
-          d="M-766.506-232.402c-.037 1.951-.207 3.93-.52 5.926-4.119 26.271-26.673 45.658-53.246 45.658h-26.404c-2.701 0-4.999 1.964-5.42 4.633l-8.281 52.514-5.197 32.947a4.46 4.46 0 0 0 4.405 5.153h28.66a5.49 5.49 0 0 0 5.423-4.633l7.55-47.881c.423-2.669 2.722-4.636 5.423-4.636h16.876c26.573 0 49.124-19.386 53.243-45.655 2.924-18.649-6.46-35.614-22.511-44.026z"
-          fill="currentColor"
-        />
-        <path
-          d="M-870.225-276.071a5.49 5.49 0 0 0-5.423 4.636l-22.489 142.608a4.46 4.46 0 0 0 4.405 5.156h33.351l8.281-52.514 9.013-57.15a5.49 5.49 0 0 1 5.423-4.633h47.782c8.691 0 16.621 2.025 23.375 5.563.46-23.917-19.275-43.666-46.412-43.666z"
-          fill="currentColor"
-        />
-      </g>
-    </svg>
-  ),
-  polar: (
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      width="15"
-      height="15"
-      viewBox="-0.5 -0.5 16 16"
-      fill="none"
-      className="docs-category-icon size-3! shrink-0 text-current"
-    >
-      <path
-        d="M7.5 14.337c-3.776 0-6.837-3.061-6.837-6.837C.663 3.724 3.724.663 7.5.663c3.776 0 6.837 3.061 6.837 6.837 0 3.776-3.061 6.837-6.837 6.837Z"
-        stroke="currentColor"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="1"
-      />
-      <path
-        d="M7.5 14.337c-1.51 0-2.735-3.061-2.735-6.837C4.765 3.724 5.99.663 7.5.663c1.51 0 2.735 3.061 2.735 6.837 0 3.776-1.225 6.837-2.735 6.837Z"
-        stroke="currentColor"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="1"
-      />
-      <path
-        d="M5.449 13.654c-2.051-.684-2.735-3.685-2.735-5.812 0-2.127 1.026-4.786 3.419-6.495"
-        stroke="currentColor"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="1"
-      />
-      <path
-        d="M9.551 1.346c2.051.684 2.735 3.685 2.735 5.812 0 2.127-1.026 4.786-3.419 6.495"
-        stroke="currentColor"
-        strokeLinecap="round"
-        strokeLinejoin="round"
-        strokeWidth="1"
-      />
-    </svg>
-  ),
-  lemonsqueezy: (
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      width="15"
-      height="15"
-      viewBox="0 0 24 24"
-      className="docs-category-icon size-3! shrink-0 text-current"
-    >
-      <path
-        fill="currentColor"
-        d="m7.4916 10.835 2.3748-6.5114a3.1497 3.1497 0 0 0-.065-2.3418C9.0315.183 6.9427-.398 5.2928.265 3.643.929 2.71 2.4348 3.512 4.3046l2.8197 6.5615c.219.509.97.489 1.16-.03m1.6798 1.0969 6.5334-2.7758c2.1699-.9219 2.7218-3.6907 1.022-5.2905l-.068-.063c-1.6669-1.5469-4.4217-1.002-5.3706 1.0359L8.3566 11.135c-.234.503.295 1.0199.8159.7979m.373.87 6.6454-2.5119c2.2078-.8349 4.6206.745 4.5886 3.0398l-.002.09c-.048 2.2358-2.3938 3.7376-4.5536 2.9467l-6.6724-2.4418a.595.595 0 0 1-.006-1.1229m-.386 1.9269 6.4375 2.9767a3.2997 3.2997 0 0 1 1.6658 1.6989c.769 1.7998-.283 3.6396-1.9328 4.3016-1.6499.662-3.4097.235-4.2097-1.6359l-2.8027-6.5694c-.217-.509.328-1.009.8419-.772"
-      />
-    </svg>
-  ),
-  paddle: (
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      width="15"
-      height="15"
-      viewBox="0 0 160 201"
-      className="docs-category-icon size-3! shrink-0 text-current"
-    >
-      <path
-        fill="currentColor"
-        fillRule="evenodd"
-        clipRule="evenodd"
-        d="M32.154 10.694V21.113l34.575.751c33.315.724 34.94.966 44.623 6.66 32.453 19.077 29.294 71.09-5.193 85.472-5.992 2.5-15.922 3.3-40.958 3.3H32.154v41.49 41.49h11.702 11.702v-30.632-30.632l26.064-.855c22.107-.726 27.679-1.634 36.703-5.991 15.017-7.254 29.536-22.83 35.153-37.716 9.43-24.991 2.627-55.38-16.353-73.051C118.533 4.09 105.065.275 62.559.275H32.154v10.42zm0 26.143c0 1.387-1.915 6.477-4.255 11.311-4.213 8.704-16.779 18.085-24.226 18.085-2.015 0-3.434 1.757-3.434 4.255 0 2.87 1.42 4.255 4.362 4.255 11.183 0 23.508 11.673 26.394 24.999 2.155 9.95 5.853 8.496 10.734-4.219 4.59-11.957 14.662-20.78 23.719-20.78 3.585 0 5.004-1.207 5.004-4.256 0-2.498-1.42-4.255-3.434-4.255-7.447 0-20.013-9.381-24.226-18.085-2.34-4.834-4.255-9.923-4.255-11.31 0-1.386-1.436-2.52-3.191-2.52s-3.192 1.134-3.192 2.52z"
-      />
-    </svg>
-  ),
-  creem: <CreemIcon className="docs-category-icon size-3! shrink-0 text-current" />,
 } as const;
🤖 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 `@apps/web/src/components/docs/docs-icons.tsx` around lines 125 - 195,
providerPageIcons contains unused SVG entries (polar, paypal, lemonsqueezy,
paddle, creem) while only stripe is in enabledProviders; remove the dead icon
definitions or reduce providerPageIcons to only include providers present in
enabledProviders (e.g., keep the stripe entry and delete
polar/paypal/lemonsqueezy/paddle/creem) and ensure any imports/components
referenced (like CreemIcon) are also removed if no longer used; update any
consumers that iterate providerPageIcons to handle the reduced set.
🤖 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 `@packages/paykit/src/cli/commands/status.ts`:
- Around line 113-115: The code after createPool(...) can throw (e.g., in
loadProductDiffs or checkProviderCustomers) without calling database.end(), so
wrap the entire post-createPool section in a try/finally that always calls
database.end(); specifically, around the block that calls loadProductDiffs(...),
checkProviderCustomers(...), and mutates preflightErrors, ensure a try { /*
existing logic */ } finally { await database.end(); } so the pg pool is always
closed even on errors.

In `@packages/paykit/src/stripe/stripe-provider.ts`:
- Around line 953-960: When allowUnsignedPayload is true the code calls
JSON.parse(data.body) without validation; wrap that parse in a try-catch and
validate the resulting object shape before treating it as a StripeSdk.Event.
Specifically, in the block that sets event when data.allowUnsignedPayload is
true, catch JSON.parse errors and throw a PayKitError (e.g., BAD_REQUEST with a
PROVIDER_INVALID_PAYLOAD or similar code) and perform basic structural checks
(presence of required keys like "type" and "id" on the parsed object) before
casting to StripeSdk.Event; keep the existing signed path using
client.webhooks.constructEventAsync unchanged.

---

Outside diff comments:
In `@apps/web/content/docs/concepts/cli.mdx`:
- Line 27: Update the sentence fragment "Syncs your plan definitions to the
database and your payment provider, creating or updating products and prices in
Stripe (or whichever provider you're using)" to remove the multi‑provider
parenthetical and explicitly reference Stripe (e.g., end with "creating or
updating products and prices in Stripe"). Locate the sentence in the CLI docs
content (the line starting "Syncs your plan definitions...") and replace the
parenthetical "(or whichever provider you're using)" with a Stripe‑specific
phrasing so the text consistently references Stripe only.

In `@packages/paykit/src/cli/commands/status.ts`:
- Around line 48-68: The status flow currently reads config.options.stripe and
calls createPool/checkProvider without validating the config schema; call the
existing validation function assertValidPayKitOptions (or equivalent) on
config.options near the start of statusAction (before referencing
config.options.stripe or creating the DB pool) and handle validation failures by
logging the config error and exiting, so malformed configs produce a clear
config error instead of runtime failures in createPool/checkProvider.

In `@packages/paykit/src/database/schema.ts`:
- Around line 231-248: The Drizzle schema for the webhook_event table has
stripeEventId defined without .notNull(), but the migration enforces NOT NULL;
update the webhookEvent table definition by adding .notNull() to the
stripeEventId column (symbol: stripeEventId in the webhookEvent pgTable) so the
schema matches the migration and the generated types reflect the non-null
constraint.

---

Nitpick comments:
In `@apps/web/src/components/docs/docs-icons.tsx`:
- Around line 125-195: providerPageIcons contains unused SVG entries (polar,
paypal, lemonsqueezy, paddle, creem) while only stripe is in enabledProviders;
remove the dead icon definitions or reduce providerPageIcons to only include
providers present in enabledProviders (e.g., keep the stripe entry and delete
polar/paypal/lemonsqueezy/paddle/creem) and ensure any imports/components
referenced (like CreemIcon) are also removed if no longer used; update any
consumers that iterate providerPageIcons to handle the reduced set.

In `@packages/paykit/src/stripe/stripe-provider.ts`:
- Line 1066: The current wrapper returns createStripeProvider(client, {
...options, webhookSecret: options.webhookSecret ?? "" }) which injects an empty
string and leads to cryptic failures in Stripe.constructEventAsync; remove the
empty-string default and instead pass webhookSecret only if present (e.g., leave
it undefined when missing) and add validation inside createStripeProvider to
throw a clear error when webhookSecret is missing while allowUnsignedPayload is
false (referencing createStripeProvider, webhookSecret, allowUnsignedPayload,
and constructEventAsync).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ea71ac5a-720a-412d-bdab-f1b69a2c6b18

📥 Commits

Reviewing files that changed from the base of the PR and between 2a18aed and 8cf1953.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (104)
  • README.md
  • apps/demo/drizzle.config.ts
  • apps/demo/next.config.js
  • apps/demo/package.json
  • apps/demo/paykit.config.ts
  • apps/demo/paykit.polar.config.ts
  • apps/demo/paykit.stripe.config.ts
  • apps/demo/scripts/push-sandbox.ts
  • apps/demo/scripts/sandbox.ts
  • apps/demo/src/app/_components/checkout-page-content.tsx
  • apps/demo/src/app/_components/features-panel.tsx
  • apps/demo/src/app/_components/subscribe-panel.tsx
  • apps/demo/src/app/paykit-polar/[[...slug]]/route.ts
  • apps/demo/src/app/paykit-stripe/[[...slug]]/route.ts
  • apps/demo/src/app/paykit/[[...slug]]/route.ts
  • apps/demo/src/env.js
  • apps/demo/src/lib/paykit-client.ts
  • apps/demo/src/lib/paykit-scenarios.ts
  • apps/demo/src/lib/paykit.ts
  • apps/demo/src/lib/paykit/polar.ts
  • apps/demo/src/lib/paykit/stripe.ts
  • apps/demo/src/lib/scenario-config.ts
  • apps/demo/src/server/api/root.ts
  • apps/demo/src/server/api/routers/paykit-route.ts
  • apps/demo/src/server/db.ts
  • apps/web/content/docs/concepts/cli.mdx
  • apps/web/content/docs/concepts/payment-providers.mdx
  • apps/web/content/docs/get-started/index.mdx
  • apps/web/content/docs/get-started/installation.mdx
  • apps/web/content/docs/providers/creem.mdx
  • apps/web/content/docs/providers/lemonsqueezy.mdx
  • apps/web/content/docs/providers/meta.json
  • apps/web/content/docs/providers/paddle.mdx
  • apps/web/content/docs/providers/paypal.mdx
  • apps/web/content/docs/providers/polar.mdx
  • apps/web/content/docs/providers/stripe.mdx
  • apps/web/src/components/docs/docs-icons.tsx
  • apps/web/src/components/docs/features.tsx
  • apps/web/src/components/sections/demo/demo-types.tsx
  • apps/web/src/components/sections/features-section.tsx
  • apps/web/src/components/sections/readme-code-content.ts
  • e2e/cli/init.test.ts
  • e2e/cli/push.test.ts
  • e2e/cli/setup.ts
  • e2e/cli/status.test.ts
  • e2e/core/subscribe/cancel-end-of-cycle.test.ts
  • e2e/core/subscribe/renewal.test.ts
  • e2e/core/webhook/duplicate-webhook.test.ts
  • e2e/core/webhook/subscription-deleted.test.ts
  • e2e/package.json
  • e2e/test-utils/env.ts
  • e2e/test-utils/harness/index.ts
  • e2e/test-utils/harness/polar.ts
  • e2e/test-utils/harness/stripe.ts
  • e2e/test-utils/harness/types.ts
  • e2e/test-utils/setup.ts
  • e2e/vitest.config.ts
  • packages/paykit/package.json
  • packages/paykit/src/api/__tests__/define-route.test.ts
  • packages/paykit/src/api/__tests__/methods.test.ts
  • packages/paykit/src/api/methods.ts
  • packages/paykit/src/cli/__tests__/init.test.ts
  • packages/paykit/src/cli/commands/init.ts
  • packages/paykit/src/cli/commands/listen.ts
  • packages/paykit/src/cli/commands/push.ts
  • packages/paykit/src/cli/commands/status.ts
  • packages/paykit/src/cli/utils/get-config.ts
  • packages/paykit/src/cli/utils/shared.ts
  • packages/paykit/src/core/__tests__/context.test.ts
  • packages/paykit/src/core/__tests__/create-paykit.test.ts
  • packages/paykit/src/core/context.ts
  • packages/paykit/src/core/create-paykit.ts
  • packages/paykit/src/customer/__tests__/customer.service.test.ts
  • packages/paykit/src/customer/customer.service.ts
  • packages/paykit/src/database/migrations/0001_stripe_only_schema.sql
  • packages/paykit/src/database/migrations/meta/0001_snapshot.json
  • packages/paykit/src/database/migrations/meta/_journal.json
  • packages/paykit/src/database/schema.ts
  • packages/paykit/src/index.ts
  • packages/paykit/src/invoice/invoice.service.ts
  • packages/paykit/src/payment-method/payment-method.service.ts
  • packages/paykit/src/payment/payment.service.ts
  • packages/paykit/src/product/product.service.ts
  • packages/paykit/src/providers/provider.ts
  • packages/paykit/src/stripe/stripe-provider.ts
  • packages/paykit/src/subscription/subscription.service.ts
  • packages/paykit/src/testing/testing.service.ts
  • packages/paykit/src/types/instance.ts
  • packages/paykit/src/types/options.ts
  • packages/paykit/src/utilities/dependencies/paykit-package-list.ts
  • packages/paykit/src/webhook/webhook.api.ts
  • packages/paykit/src/webhook/webhook.service.ts
  • packages/polar/package.json
  • packages/polar/src/index.ts
  • packages/polar/src/polar-provider.ts
  • packages/polar/tsconfig.json
  • packages/polar/tsdown.config.ts
  • packages/stripe/package.json
  • packages/stripe/src/__tests__/stripe-provider.test.ts
  • packages/stripe/src/__tests__/stripe.test.ts
  • packages/stripe/src/index.ts
  • packages/stripe/tsconfig.json
  • packages/stripe/tsdown.config.ts
  • scripts/publish-dist.mjs
💤 Files with no reviewable changes (27)
  • e2e/package.json
  • packages/polar/tsconfig.json
  • apps/web/content/docs/providers/paypal.mdx
  • apps/web/content/docs/providers/paddle.mdx
  • apps/demo/paykit.polar.config.ts
  • apps/web/content/docs/providers/creem.mdx
  • apps/web/content/docs/providers/polar.mdx
  • packages/stripe/package.json
  • apps/demo/src/lib/paykit-scenarios.ts
  • apps/web/content/docs/providers/lemonsqueezy.mdx
  • packages/stripe/src/tests/stripe.test.ts
  • packages/stripe/tsconfig.json
  • packages/stripe/src/tests/stripe-provider.test.ts
  • apps/demo/src/lib/paykit/stripe.ts
  • apps/demo/src/lib/paykit/polar.ts
  • packages/polar/tsdown.config.ts
  • packages/polar/package.json
  • packages/polar/src/index.ts
  • apps/demo/src/lib/scenario-config.ts
  • apps/demo/src/app/paykit-polar/[[...slug]]/route.ts
  • packages/paykit/src/testing/testing.service.ts
  • packages/stripe/src/index.ts
  • apps/demo/src/app/paykit-stripe/[[...slug]]/route.ts
  • packages/stripe/tsdown.config.ts
  • e2e/test-utils/harness/polar.ts
  • packages/polar/src/polar-provider.ts
  • apps/demo/paykit.stripe.config.ts
✅ Files skipped from review due to trivial changes (12)
  • apps/web/content/docs/providers/meta.json
  • packages/paykit/src/cli/tests/init.test.ts
  • apps/web/src/components/sections/features-section.tsx
  • apps/web/content/docs/get-started/index.mdx
  • apps/web/src/components/sections/demo/demo-types.tsx
  • packages/paykit/src/database/migrations/meta/_journal.json
  • apps/web/content/docs/providers/stripe.mdx
  • packages/paykit/src/cli/utils/get-config.ts
  • apps/web/content/docs/concepts/payment-providers.mdx
  • apps/demo/next.config.js
  • packages/paykit/src/database/migrations/meta/0001_snapshot.json
  • README.md
🚧 Files skipped from review as they are similar to previous changes (54)
  • packages/paykit/src/core/tests/create-paykit.test.ts
  • e2e/cli/status.test.ts
  • apps/demo/drizzle.config.ts
  • scripts/publish-dist.mjs
  • packages/paykit/src/utilities/dependencies/paykit-package-list.ts
  • packages/paykit/src/api/tests/define-route.test.ts
  • e2e/core/subscribe/cancel-end-of-cycle.test.ts
  • e2e/core/subscribe/renewal.test.ts
  • apps/web/src/components/docs/features.tsx
  • e2e/core/webhook/duplicate-webhook.test.ts
  • packages/paykit/src/invoice/invoice.service.ts
  • packages/paykit/src/api/tests/methods.test.ts
  • e2e/cli/push.test.ts
  • apps/demo/src/app/paykit/[[...slug]]/route.ts
  • apps/demo/src/lib/paykit-client.ts
  • apps/demo/scripts/sandbox.ts
  • apps/web/src/components/sections/readme-code-content.ts
  • packages/paykit/src/types/options.ts
  • apps/demo/paykit.config.ts
  • packages/paykit/src/index.ts
  • e2e/cli/init.test.ts
  • e2e/core/webhook/subscription-deleted.test.ts
  • apps/demo/src/server/api/root.ts
  • packages/paykit/src/core/context.ts
  • apps/web/content/docs/get-started/installation.mdx
  • packages/paykit/package.json
  • e2e/test-utils/harness/index.ts
  • apps/demo/src/server/db.ts
  • apps/demo/scripts/push-sandbox.ts
  • e2e/test-utils/harness/types.ts
  • packages/paykit/src/core/tests/context.test.ts
  • e2e/test-utils/harness/stripe.ts
  • packages/paykit/src/providers/provider.ts
  • apps/demo/src/server/api/routers/paykit-route.ts
  • packages/paykit/src/customer/customer.service.ts
  • packages/paykit/src/payment/payment.service.ts
  • e2e/vitest.config.ts
  • apps/demo/src/env.js
  • e2e/test-utils/env.ts
  • e2e/test-utils/setup.ts
  • packages/paykit/src/cli/utils/shared.ts
  • apps/demo/src/app/_components/subscribe-panel.tsx
  • packages/paykit/src/types/instance.ts
  • packages/paykit/src/product/product.service.ts
  • packages/paykit/src/api/methods.ts
  • e2e/cli/setup.ts
  • apps/demo/src/lib/paykit.ts
  • packages/paykit/src/core/create-paykit.ts
  • packages/paykit/src/cli/commands/listen.ts
  • packages/paykit/src/webhook/webhook.api.ts
  • packages/paykit/src/payment-method/payment-method.service.ts
  • packages/paykit/src/webhook/webhook.service.ts
  • packages/paykit/src/cli/commands/init.ts
  • packages/paykit/src/subscription/subscription.service.ts

Comment thread packages/paykit/src/cli/commands/status.ts Outdated
Comment thread packages/paykit/src/stripe/stripe-provider.ts
@maxktz maxktz closed this Jun 2, 2026
@maxktz maxktz deleted the feat/polar-remove branch June 2, 2026 16:09
@maxktz maxktz restored the feat/polar-remove branch June 2, 2026 16:09
@maxktz maxktz reopened this Jun 2, 2026
@maxktz maxktz changed the title Remove Polar and make PayKit Stripe-only feat: paykit v2 Jun 6, 2026
@maxktz

maxktz commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

@vercel deploy

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/components/layout/navigation-bar.tsx (1)

186-197: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the desktop “links” dropdown keyboard-operable.

Line 189 renders a button, but visibility only changes on hover (Lines 186-187). Keyboard users can’t reliably open/access dropdown items.

Suggested fix
                   <motion.div
@@
                     className="relative"
                     onMouseEnter={openLinks}
                     onMouseLeave={closeLinks}
+                    onFocus={openLinks}
+                    onBlur={(e) => {
+                      if (!e.currentTarget.contains(e.relatedTarget as Node | null)) closeLinks();
+                    }}
                   >
                     <button
                       type="button"
+                      aria-haspopup="menu"
+                      aria-expanded={linksOpen}
+                      aria-controls="desktop-links-menu"
+                      onClick={() => setLinksOpen((prev) => !prev)}
                       className={`${tabBase} gap-1 ${linksOpen ? "text-foreground/70" : tabInactive}`}
                     >
@@
                         <motion.div
+                          id="desktop-links-menu"
+                          role="menu"

Also applies to: 199-227

🤖 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 `@apps/web/src/components/layout/navigation-bar.tsx` around lines 186 - 197,
The "links" dropdown is only hover-controlled; make it keyboard-operable by
wiring focus and key events into the same state handlers: call openLinks onFocus
and closeLinks onBlur (in addition to onMouseEnter/onMouseLeave), add an
onKeyDown handler on the button that toggles or opens the menu for Enter/Space
and closes on Escape, and ensure the button includes accessible attributes like
aria-expanded={linksOpen} and aria-controls pointing to the dropdown list id;
update logic in the component using openLinks, closeLinks and linksOpen (and the
button rendering around RiArrowDownSLine) so keyboard users can open, navigate,
and close the dropdown.
🧹 Nitpick comments (13)
apps/web/src/app/(marketing)/blog/page.tsx (3)

8-14: 💤 Low value

Consider adding JSDoc for exported metadata.

Per coding guidelines, JSDoc should be added on user-facing API. This metadata object is exported and defines the page's SEO properties.

📝 Suggested JSDoc
+/** Metadata for the blog placeholder page. */
 export const metadata: Metadata = {
🤖 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 `@apps/web/src/app/`(marketing)/blog/page.tsx around lines 8 - 14, Add a JSDoc
comment above the exported metadata object to document the user-facing SEO
properties; annotate what the exported symbol "metadata: Metadata" represents
(page title, description, alternates/canonical URL) and any expectations for
values/format. Place the JSDoc directly above the export of metadata in page.tsx
(the exported constant "metadata") so IDEs and docs pick it up, using standard
JSDoc tags like `@type` and a short description.

Source: Coding guidelines


1-1: ⚡ Quick win

Use import type for type-only imports.

Per coding guidelines, type-only imports should use the import type syntax.

♻️ Refactor to use type import
-import type { Metadata } from "next";
+import type { Metadata } from "next";

Wait, that's the same. Let me check... actually line 1 shows import type { Metadata } which is already correct. Let me reconsider.

Actually looking at line 1: import type { Metadata } from "next"; - this is already using import type. This is correct. No issue here.

🤖 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 `@apps/web/src/app/`(marketing)/blog/page.tsx at line 1, The import already
uses type-only syntax so no change is needed; leave the existing import type {
Metadata } from "next" in page.tsx as-is and do not modify it.

Source: Coding guidelines


16-42: 💤 Low value

Consider adding JSDoc for the page component.

Per coding guidelines, user-facing functions (including page components) should include JSDoc documentation.

📝 Suggested JSDoc
+/** Blog placeholder page with "coming soon" message. */
 export default function BlogPage() {
🤖 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 `@apps/web/src/app/`(marketing)/blog/page.tsx around lines 16 - 42, Add a JSDoc
block above the BlogPage component to document the page per coding guidelines:
include a one-line description (e.g., "Marketing blog landing page
placeholder"), mention that it takes no props (or list props if added later),
and add an `@returns` tag like "`@returns` JSX.Element" so tooling and readers know
the component's intent and return type; place the comment immediately above the
export default function BlogPage() declaration.

Source: Coding guidelines

apps/web/src/components/ui/sonner.tsx (2)

13-43: 💤 Low value

Consider adding JSDoc for the Toaster component.

Per coding guidelines, UI components used in many places should include JSDoc documentation.

📝 Suggested JSDoc
+/** Toast notification component wrapper for Sonner with theme integration. */
 const Toaster = ({ ...props }: ToasterProps) => {
🤖 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 `@apps/web/src/components/ui/sonner.tsx` around lines 13 - 43, Add a JSDoc
block above the Toaster component describing its purpose, props, and default
behavior: document the Toaster component (function Toaster), its ToasterProps
parameter (including that it spreads {...props}), the theme defaulting via
useTheme(), and the custom icons/style/toastOptions it applies; briefly mention
expected types (ToasterProps["theme"] etc.) and any side-effects or usage notes
so other developers know how to use and override it.

Source: Coding guidelines


11-11: ⚡ Quick win

Separate type import from value import.

Per coding guidelines, use import type for type-only imports. The ToasterProps type import should be separated from the value import.

♻️ Separate the imports
-import { Toaster as Sonner, type ToasterProps } from "sonner";
+import { Toaster as Sonner } from "sonner";
+import type { ToasterProps } from "sonner";
🤖 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 `@apps/web/src/components/ui/sonner.tsx` at line 11, The import mixes a value
and a type; separate them by importing the value default and the type with an
`import type` to follow guidelines—replace the combined import of `Toaster as
Sonner, type ToasterProps` with two imports: one importing the value `Toaster as
Sonner` from "sonner" and a second `import type { ToasterProps }` from "sonner"
so `Sonner` is a runtime value import and `ToasterProps` is a type-only import.

Source: Coding guidelines

apps/web/src/components/ui/navigation-menu.tsx (1)

7-161: ⚖️ Poor tradeoff

Consider adding JSDoc for exported UI components.

Per coding guidelines, JSDoc should be added on API used in many places. These navigation menu primitives are reusable UI components that would benefit from documentation.

🤖 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 `@apps/web/src/components/ui/navigation-menu.tsx` around lines 7 - 161, Add
JSDoc comments for the exported navigation menu components (NavigationMenu,
NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem,
NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger,
NavigationMenuPositioner and navigationMenuTriggerStyle): for each export, add a
short JSDoc block above its declaration describing its purpose, props (noting
key props like align, side, sideOffset, alignOffset), any important behavior or
className expectations, and usage examples where helpful; ensure the JSDoc uses
`@param/`@returns/@example tags as appropriate and stays consistent with existing
project doc style.

Source: Coding guidelines

apps/web/src/components/docs/toc-footer.tsx (1)

7-43: 💤 Low value

Consider adding JSDoc for the component.

Per coding guidelines, JSDoc should be added on user-facing components. This table-of-contents footer is part of the docs UI.

📝 Suggested JSDoc
+/** Footer component displaying roadmap progress link in the docs TOC. */
 export function TocFooter() {
🤖 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 `@apps/web/src/components/docs/toc-footer.tsx` around lines 7 - 43, Add a JSDoc
block above the TocFooter component declaration describing the component's
purpose (table-of-contents footer used in docs), note that it is user-facing,
document any props (none currently) and returned JSX, and include brief
accessibility or interaction notes (opens roadmap in new tab, shows
progressValue). Reference the TocFooter function in the comment so reviewers can
quickly locate it.

Source: Coding guidelines

apps/web/src/components/web/footer.tsx (1)

7-70: 💤 Low value

Consider adding JSDoc for the Footer component.

Per coding guidelines, user-facing components should include JSDoc documentation. This footer component is part of the main layout.

📝 Suggested JSDoc
+/** Main footer component with copyright, social links, and theme toggle. */
 export default function Footer() {
🤖 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 `@apps/web/src/components/web/footer.tsx` around lines 7 - 70, Add a JSDoc
comment block above the Footer component (export default function Footer)
describing that it renders the site footer with watermark SVG, decorative grid,
copyright/year and social links; include `@component`, note that it accepts no
props, mention accessibility concerns (aria-hidden on decorative elements and
aria-labels on links), and any relevant tags like `@example` or `@returns` for
clarity.

Source: Coding guidelines

apps/web/src/app/not-found.tsx (1)

12-64: 💤 Low value

Consider adding JSDoc for the page component.

Per coding guidelines, user-facing functions should include JSDoc documentation. This 404 page component is user-facing.

📝 Suggested JSDoc
+/** Custom 404 error page displayed when a route is not found. */
 export default function NotFound() {
🤖 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 `@apps/web/src/app/not-found.tsx` around lines 12 - 64, Add a JSDoc block above
the exported NotFound component describing the page (e.g., "404 Not Found page
shown when a route is missing"), note that it returns a JSX.Element and mention
there are no props; place it directly above the function declaration for
NotFound so tools and other devs can pick up the docstring (include a short
one-line description and an `@returns` {JSX.Element} tag).

Source: Coding guidelines

apps/web/src/components/web/brand-menu.tsx (1)

25-87: 💤 Low value

Consider adding JSDoc for the BrandMenu component.

Per coding guidelines, user-facing components should include JSDoc documentation. This brand menu with context menu actions is part of the navigation UI.

📝 Suggested JSDoc
+/**
+ * Brand menu component with context menu for copying logo/wordmark SVG.
+ * `@param` wordmarkBaseClassName - Base class for wordmark sizing (defaults to "h-4")
+ */
 export function BrandMenu({
🤖 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 `@apps/web/src/components/web/brand-menu.tsx` around lines 25 - 87, Add a JSDoc
block above the BrandMenu function describing the component's purpose (a
brand/logo link with a context menu to copy SVG assets), document each prop
(className, linkClassName, wordmarkBaseClassName, wordmarkClassName) with their
types and optional nature, and note any important behavior such as the context
menu actions and the copyAsSvg helper used to copy brandAssets ("Logo" and
"Wordmark") to the clipboard and display toast notifications; reference the
BrandMenu function, the copyAsSvg helper, and the brandAssets keys in the JSDoc.

Source: Coding guidelines

apps/web/src/components/ui/pagination.tsx (1)

7-118: ⚖️ Poor tradeoff

Consider adding JSDoc for exported pagination components.

Per coding guidelines, JSDoc should be added on API used in many places. These pagination primitives are reusable UI components that would benefit from documentation.

🤖 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 `@apps/web/src/components/ui/pagination.tsx` around lines 7 - 118, Add JSDoc
comments above each exported pagination primitive (Pagination,
PaginationContent, PaginationItem, PaginationLink, PaginationPrevious,
PaginationNext, PaginationEllipsis) describing the component purpose, accepted
props and types (mention PaginationLinkProps and its isActive/size/nativeButton
behavior), key accessibility behaviors (aria-label/aria-current usage on
PaginationLink, aria-hidden on PaginationEllipsis), and any important
className/slot data attributes (data-slot values) or default prop values; keep
each block concise (one-line summary + `@param` entries for props and types +
`@returns`) so consumers and IDEs can pick up documentation and prop signatures.

Source: Coding guidelines

apps/web/src/components/layout/section.tsx (1)

8-8: 💤 Low value

Consider removing separator comments per coding guidelines.

Per coding guidelines: "Keep comments rare and about code logic; do not add unnecessary separator comments." The section separator comments (lines 8, 34, 57, 69) could be considered decorative rather than explanatory.

Also applies to: 34-34, 57-57, 69-69

🤖 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 `@apps/web/src/components/layout/section.tsx` at line 8, Remove the decorative
separator comments (for example the line "// ─── Shared section line
─────────────────────────────────────────────" and the similar separators at the
other occurrences) from apps/web/src/components/layout/section.tsx to follow the
guideline of avoiding unnecessary comments; edit the Section component file to
delete those separator comment lines (the ones at the top and the other two
occurrences) so only meaningful, explanatory comments remain.

Source: Coding guidelines

apps/web/src/components/sections/demo/index.tsx (1)

324-324: ⚡ Quick win

Avoid >100% width allocation in the demo row.

lg:w-[73%] + lg:w-[37%] (plus gap) can overflow the container on large screens and cause clipping/horizontal scroll.

Suggested fix
-            className={`${WINDOW_HEIGHT} md:w-1/2 lg:w-[73%]`}
+            className={`${WINDOW_HEIGHT} md:flex-1 lg:flex-[73]`}
...
-            className="h-58 md:h-144 md:w-1/2 lg:w-[37%]"
+            className="h-58 md:h-144 md:flex-1 lg:flex-[37]"

Also applies to: 336-336

🤖 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 `@apps/web/src/components/sections/demo/index.tsx` at line 324, The two JSX
containers using className={`${WINDOW_HEIGHT} md:w-1/2 lg:w-[73%]`} and the
corresponding sibling using `lg:w-[37%]` can exceed 100% width (plus gap) on
large screens; update these className values so their lg: widths plus gap do not
exceed 100% (for example switch to safe fractions like lg:w-2/3 + lg:w-1/3, or
use flex utilities such as lg:flex and lg:flex-[2] / lg:flex-[1] or lg:w-3/5 +
lg:w-2/5) and apply the change to both occurrences referenced in the file to
prevent horizontal overflow and clipping.
🤖 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 `@apps/web/src/components/docs/copy-markdown-button.tsx`:
- Around line 24-47: The component currently reuses the single markdown state
across prop changes, causing stale cached text for new markdownUrl; fix this by
scoping the cache to the URL—either store a map keyed by markdownUrl or simply
reset the markdown cache when markdownUrl changes (e.g. add a useEffect that
calls setMarkdown(undefined) with [markdownUrl] as dependency). Ensure the
onClick closure continues to depend on markdownUrl and setMarkdown so it
reads/writes the cache for the current URL.

In `@apps/web/src/components/docs/docs-mdx-components.tsx`:
- Around line 56-64: getHeadingId currently calls children?.toString() which
collapses rich MDX nodes into "[object Object]" causing bad/duplicate anchors;
replace that with a recursive text-extraction helper (e.g.,
extractTextFromChildren) that traverses React
nodes/arrays/fragments/numbers/strings and concatenates text from
props.children, then run the existing lowercasing/replace/trim/space-to-dash
logic on that extracted text; keep the final fallback to a stable string like
"heading" only when the extracted text is empty. Ensure you update getHeadingId
to call this helper and reference the helper in tests if present.

In `@apps/web/src/components/docs/mdx-text.tsx`:
- Around line 42-45: The Link element in mdx-text.tsx sets target based on
_blank but doesn't set rel, exposing window.opener; update the Link props (the
JSX using Link with href, target={_blank ? "_blank" : "_self"},
className={cn(...)}) to include a rel attribute when _blank is true (e.g.,
rel="noopener noreferrer")—apply this conditionally using the same _blank
boolean so external blank targets get rel="noopener noreferrer" while
internal/_self links omit or use a safe default.

In `@apps/web/src/components/docs/package-command-utils.ts`:
- Around line 31-33: The "run" case currently returns npm-run for manager ===
"npm" and otherwise builds `${manager} ${command.args}`, which yields `bun
<script>` and can invoke Bun subcommands; update the mapping in the case "run"
branch to explicitly use `bun run ${command.args}` when manager === "bun" and
fall back to `${manager} ${command.args}` for other managers (i.e., change the
ternary/branching in the case "run" handling to check manager === "bun" and
return `bun run ...`).

In `@apps/web/src/components/docs/package-command.tsx`:
- Around line 296-299: The CopyButton is only exposed via hover (className uses
group-hover:opacity-100), so keyboard users can focus it while it remains
visually hidden; update the CopyButton container className to also reveal on
keyboard focus (e.g., add focus:opacity-100 and focus-visible:opacity-100 and/or
group-focus:opacity-100) so the button becomes visible when focused, and ensure
the CopyButton itself remains keyboard-focusable (no tabIndex=-1 or aria-hidden)
so its code prop behavior continues to work.

In `@apps/web/src/components/layout/navigation-bar.tsx`:
- Line 232: Fix the invalid Tailwind utility in the NavigationBar component:
locate the JSX element that sets className to "relative z-10 flex items-center
gap-0." (the container div in the navigation-bar component) and remove the
trailing dot so the class becomes "gap-0" (or replace with an appropriate gap
value, e.g., "gap-2") to ensure the Tailwind utility is recognized.

---

Outside diff comments:
In `@apps/web/src/components/layout/navigation-bar.tsx`:
- Around line 186-197: The "links" dropdown is only hover-controlled; make it
keyboard-operable by wiring focus and key events into the same state handlers:
call openLinks onFocus and closeLinks onBlur (in addition to
onMouseEnter/onMouseLeave), add an onKeyDown handler on the button that toggles
or opens the menu for Enter/Space and closes on Escape, and ensure the button
includes accessible attributes like aria-expanded={linksOpen} and aria-controls
pointing to the dropdown list id; update logic in the component using openLinks,
closeLinks and linksOpen (and the button rendering around RiArrowDownSLine) so
keyboard users can open, navigate, and close the dropdown.

---

Nitpick comments:
In `@apps/web/src/app/`(marketing)/blog/page.tsx:
- Around line 8-14: Add a JSDoc comment above the exported metadata object to
document the user-facing SEO properties; annotate what the exported symbol
"metadata: Metadata" represents (page title, description, alternates/canonical
URL) and any expectations for values/format. Place the JSDoc directly above the
export of metadata in page.tsx (the exported constant "metadata") so IDEs and
docs pick it up, using standard JSDoc tags like `@type` and a short description.
- Line 1: The import already uses type-only syntax so no change is needed; leave
the existing import type { Metadata } from "next" in page.tsx as-is and do not
modify it.
- Around line 16-42: Add a JSDoc block above the BlogPage component to document
the page per coding guidelines: include a one-line description (e.g., "Marketing
blog landing page placeholder"), mention that it takes no props (or list props
if added later), and add an `@returns` tag like "`@returns` JSX.Element" so tooling
and readers know the component's intent and return type; place the comment
immediately above the export default function BlogPage() declaration.

In `@apps/web/src/app/not-found.tsx`:
- Around line 12-64: Add a JSDoc block above the exported NotFound component
describing the page (e.g., "404 Not Found page shown when a route is missing"),
note that it returns a JSX.Element and mention there are no props; place it
directly above the function declaration for NotFound so tools and other devs can
pick up the docstring (include a short one-line description and an `@returns`
{JSX.Element} tag).

In `@apps/web/src/components/docs/toc-footer.tsx`:
- Around line 7-43: Add a JSDoc block above the TocFooter component declaration
describing the component's purpose (table-of-contents footer used in docs), note
that it is user-facing, document any props (none currently) and returned JSX,
and include brief accessibility or interaction notes (opens roadmap in new tab,
shows progressValue). Reference the TocFooter function in the comment so
reviewers can quickly locate it.

In `@apps/web/src/components/layout/section.tsx`:
- Line 8: Remove the decorative separator comments (for example the line "// ───
Shared section line ─────────────────────────────────────────────" and the
similar separators at the other occurrences) from
apps/web/src/components/layout/section.tsx to follow the guideline of avoiding
unnecessary comments; edit the Section component file to delete those separator
comment lines (the ones at the top and the other two occurrences) so only
meaningful, explanatory comments remain.

In `@apps/web/src/components/sections/demo/index.tsx`:
- Line 324: The two JSX containers using className={`${WINDOW_HEIGHT} md:w-1/2
lg:w-[73%]`} and the corresponding sibling using `lg:w-[37%]` can exceed 100%
width (plus gap) on large screens; update these className values so their lg:
widths plus gap do not exceed 100% (for example switch to safe fractions like
lg:w-2/3 + lg:w-1/3, or use flex utilities such as lg:flex and lg:flex-[2] /
lg:flex-[1] or lg:w-3/5 + lg:w-2/5) and apply the change to both occurrences
referenced in the file to prevent horizontal overflow and clipping.

In `@apps/web/src/components/ui/navigation-menu.tsx`:
- Around line 7-161: Add JSDoc comments for the exported navigation menu
components (NavigationMenu, NavigationMenuContent, NavigationMenuIndicator,
NavigationMenuItem, NavigationMenuLink, NavigationMenuList,
NavigationMenuTrigger, NavigationMenuPositioner and navigationMenuTriggerStyle):
for each export, add a short JSDoc block above its declaration describing its
purpose, props (noting key props like align, side, sideOffset, alignOffset), any
important behavior or className expectations, and usage examples where helpful;
ensure the JSDoc uses `@param/`@returns/@example tags as appropriate and stays
consistent with existing project doc style.

In `@apps/web/src/components/ui/pagination.tsx`:
- Around line 7-118: Add JSDoc comments above each exported pagination primitive
(Pagination, PaginationContent, PaginationItem, PaginationLink,
PaginationPrevious, PaginationNext, PaginationEllipsis) describing the component
purpose, accepted props and types (mention PaginationLinkProps and its
isActive/size/nativeButton behavior), key accessibility behaviors
(aria-label/aria-current usage on PaginationLink, aria-hidden on
PaginationEllipsis), and any important className/slot data attributes (data-slot
values) or default prop values; keep each block concise (one-line summary +
`@param` entries for props and types + `@returns`) so consumers and IDEs can pick up
documentation and prop signatures.

In `@apps/web/src/components/ui/sonner.tsx`:
- Around line 13-43: Add a JSDoc block above the Toaster component describing
its purpose, props, and default behavior: document the Toaster component
(function Toaster), its ToasterProps parameter (including that it spreads
{...props}), the theme defaulting via useTheme(), and the custom
icons/style/toastOptions it applies; briefly mention expected types
(ToasterProps["theme"] etc.) and any side-effects or usage notes so other
developers know how to use and override it.
- Line 11: The import mixes a value and a type; separate them by importing the
value default and the type with an `import type` to follow guidelines—replace
the combined import of `Toaster as Sonner, type ToasterProps` with two imports:
one importing the value `Toaster as Sonner` from "sonner" and a second `import
type { ToasterProps }` from "sonner" so `Sonner` is a runtime value import and
`ToasterProps` is a type-only import.

In `@apps/web/src/components/web/brand-menu.tsx`:
- Around line 25-87: Add a JSDoc block above the BrandMenu function describing
the component's purpose (a brand/logo link with a context menu to copy SVG
assets), document each prop (className, linkClassName, wordmarkBaseClassName,
wordmarkClassName) with their types and optional nature, and note any important
behavior such as the context menu actions and the copyAsSvg helper used to copy
brandAssets ("Logo" and "Wordmark") to the clipboard and display toast
notifications; reference the BrandMenu function, the copyAsSvg helper, and the
brandAssets keys in the JSDoc.

In `@apps/web/src/components/web/footer.tsx`:
- Around line 7-70: Add a JSDoc comment block above the Footer component (export
default function Footer) describing that it renders the site footer with
watermark SVG, decorative grid, copyright/year and social links; include
`@component`, note that it accepts no props, mention accessibility concerns
(aria-hidden on decorative elements and aria-labels on links), and any relevant
tags like `@example` or `@returns` for clarity.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: add29d97-2e9b-4f2b-965a-727ff3e0b20b

📥 Commits

Reviewing files that changed from the base of the PR and between 7cfdcb9 and 4612be4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (117)
  • AGENTS.md
  • apps/web/content/docs/cli.mdx
  • apps/web/content/docs/client.mdx
  • apps/web/content/docs/concepts/meta.json
  • apps/web/content/docs/concepts/payment-providers.mdx
  • apps/web/content/docs/customers.mdx
  • apps/web/content/docs/dashboard.mdx
  • apps/web/content/docs/database.mdx
  • apps/web/content/docs/entitlements.mdx
  • apps/web/content/docs/flows/meta.json
  • apps/web/content/docs/get-started/meta.json
  • apps/web/content/docs/guides/meta.json
  • apps/web/content/docs/installation.mdx
  • apps/web/content/docs/introduction.mdx
  • apps/web/content/docs/meta.json
  • apps/web/content/docs/metered-usage.mdx
  • apps/web/content/docs/plans-and-features.mdx
  • apps/web/content/docs/plugins.mdx
  • apps/web/content/docs/plugins/meta.json
  • apps/web/content/docs/providers/meta.json
  • apps/web/content/docs/providers/stripe.mdx
  • apps/web/content/docs/quickstart.mdx
  • apps/web/content/docs/skills.mdx
  • apps/web/content/docs/subscription-billing.mdx
  • apps/web/content/docs/subscriptions.mdx
  • apps/web/content/docs/typescript.mdx
  • apps/web/content/docs/webhook-events.mdx
  • apps/web/content/drafts/docs-index.mdx
  • apps/web/mdx-components.tsx
  • apps/web/next.config.js
  • apps/web/package.json
  • apps/web/source.config.ts
  • apps/web/src/app/(marketing)/blog/page.tsx
  • apps/web/src/app/(marketing)/contact/contact-form.tsx
  • apps/web/src/app/(marketing)/donate/page.tsx
  • apps/web/src/app/(marketing)/layout.tsx
  • apps/web/src/app/(marketing)/page.tsx
  • apps/web/src/app/(marketing)/sponsor/page.tsx
  • apps/web/src/app/api/og/[[...slug]]/route.tsx
  • apps/web/src/app/docs/[[...slug]]/page.tsx
  • apps/web/src/app/docs/layout.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/llms.txt/route.ts
  • apps/web/src/app/not-found.tsx
  • apps/web/src/components/command-menu.tsx
  • apps/web/src/components/docs/copy-markdown-button.tsx
  • apps/web/src/components/docs/docs-code-surface.tsx
  • apps/web/src/components/docs/docs-icons.tsx
  • apps/web/src/components/docs/docs-layout.tsx
  • apps/web/src/components/docs/docs-mdx-components.tsx
  • apps/web/src/components/docs/docs-page.tsx
  • apps/web/src/components/docs/features.tsx
  • apps/web/src/components/docs/mdx-tabs.tsx
  • apps/web/src/components/docs/mdx-text.tsx
  • apps/web/src/components/docs/package-command-pre.tsx
  • apps/web/src/components/docs/package-command-utils.ts
  • apps/web/src/components/docs/package-command.tsx
  • apps/web/src/components/docs/package-manager-state.ts
  • apps/web/src/components/docs/react-node-text.ts
  • apps/web/src/components/docs/sidebar-category-accordion.tsx
  • apps/web/src/components/docs/sidebar-collapse-button.tsx
  • apps/web/src/components/docs/toc-footer.tsx
  • apps/web/src/components/icons/index.tsx
  • apps/web/src/components/layout/mini-nav-bar.tsx
  • apps/web/src/components/layout/navigation-bar.tsx
  • apps/web/src/components/layout/section.tsx
  • apps/web/src/components/providers.tsx
  • apps/web/src/components/sections/cta-section.tsx
  • apps/web/src/components/sections/demo/demo-app-window.tsx
  • apps/web/src/components/sections/demo/demo-backend-panel.tsx
  • apps/web/src/components/sections/demo/demo-types.tsx
  • apps/web/src/components/sections/demo/index.tsx
  • apps/web/src/components/sections/features-section.tsx
  • apps/web/src/components/sections/feedback-content.ts
  • apps/web/src/components/sections/feedback-section.tsx
  • apps/web/src/components/sections/footer-section.tsx
  • apps/web/src/components/sections/hero-section.tsx
  • apps/web/src/components/sections/readme-code-content.ts
  • apps/web/src/components/sections/testimonials-section.tsx
  • apps/web/src/components/sidebar-content.tsx
  • apps/web/src/components/theme-switcher.tsx
  • apps/web/src/components/ui/accordion.tsx
  • apps/web/src/components/ui/breadcrumb.tsx
  • apps/web/src/components/ui/button-group.tsx
  • apps/web/src/components/ui/button.tsx
  • apps/web/src/components/ui/calendar.tsx
  • apps/web/src/components/ui/carousel.tsx
  • apps/web/src/components/ui/checkbox.tsx
  • apps/web/src/components/ui/code-block-content.tsx
  • apps/web/src/components/ui/code-block.tsx
  • apps/web/src/components/ui/combobox.tsx
  • apps/web/src/components/ui/command.tsx
  • apps/web/src/components/ui/context-menu.tsx
  • apps/web/src/components/ui/dialog.tsx
  • apps/web/src/components/ui/dropdown-menu.tsx
  • apps/web/src/components/ui/dynamic-code-block.tsx
  • apps/web/src/components/ui/frame-corners.tsx
  • apps/web/src/components/ui/input-otp.tsx
  • apps/web/src/components/ui/menubar.tsx
  • apps/web/src/components/ui/native-select.tsx
  • apps/web/src/components/ui/navigation-menu.tsx
  • apps/web/src/components/ui/pagination.tsx
  • apps/web/src/components/ui/select.tsx
  • apps/web/src/components/ui/sheet.tsx
  • apps/web/src/components/ui/sidebar.tsx
  • apps/web/src/components/ui/sonner.tsx
  • apps/web/src/components/ui/spinner.tsx
  • apps/web/src/components/web/brand-menu.tsx
  • apps/web/src/components/web/footer.tsx
  • apps/web/src/components/web/hero-code-block.tsx
  • apps/web/src/components/web/hero-title.tsx
  • apps/web/src/lib/consts.ts
  • apps/web/src/lib/lucide-react-remix-shim.ts
  • apps/web/src/lib/shiki-themes.ts
  • apps/web/src/lib/shiki-themes/shiki-aura-theme.ts
  • apps/web/src/styles/globals.css
  • dev/DESIGN.md
💤 Files with no reviewable changes (16)
  • apps/web/content/docs/concepts/meta.json
  • apps/web/content/docs/flows/meta.json
  • apps/web/src/components/docs/sidebar-category-accordion.tsx
  • apps/web/content/docs/introduction.mdx
  • apps/web/content/docs/get-started/meta.json
  • apps/web/src/app/(marketing)/donate/page.tsx
  • apps/web/src/lib/consts.ts
  • apps/web/content/docs/guides/meta.json
  • apps/web/src/components/docs/sidebar-collapse-button.tsx
  • apps/web/content/docs/concepts/payment-providers.mdx
  • apps/web/src/components/command-menu.tsx
  • apps/web/content/docs/plugins/meta.json
  • apps/web/src/components/sections/testimonials-section.tsx
  • apps/web/src/components/sections/features-section.tsx
  • apps/web/content/docs/providers/meta.json
  • apps/web/content/docs/providers/stripe.mdx
✅ Files skipped from review due to trivial changes (22)
  • apps/web/content/docs/database.mdx
  • apps/web/src/components/docs/docs-code-surface.tsx
  • apps/web/src/app/llms.txt/route.ts
  • apps/web/src/components/ui/breadcrumb.tsx
  • apps/web/src/components/ui/button.tsx
  • apps/web/src/app/(marketing)/sponsor/page.tsx
  • apps/web/content/drafts/docs-index.mdx
  • apps/web/content/docs/skills.mdx
  • apps/web/src/components/ui/sidebar.tsx
  • apps/web/src/components/ui/sheet.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/content/docs/typescript.mdx
  • apps/web/content/docs/quickstart.mdx
  • apps/web/content/docs/plugins.mdx
  • AGENTS.md
  • apps/web/content/docs/metered-usage.mdx
  • apps/web/src/app/(marketing)/contact/contact-form.tsx
  • dev/DESIGN.md
  • apps/web/src/components/ui/select.tsx
  • apps/web/content/docs/meta.json
  • apps/web/content/docs/client.mdx
  • apps/web/content/docs/subscription-billing.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/components/sections/readme-code-content.ts
  • apps/web/src/components/docs/docs-icons.tsx

Comment thread apps/web/src/components/docs/copy-markdown-button.tsx
Comment thread apps/web/src/components/docs/docs-mdx-components.tsx
Comment thread apps/web/src/components/docs/mdx-text.tsx
Comment thread apps/web/src/components/docs/package-command-utils.ts
Comment thread apps/web/src/components/docs/package-command.tsx
Comment thread apps/web/src/components/layout/navigation-bar.tsx Outdated
@maxktz maxktz merged commit a961954 into main Jun 6, 2026
6 checks passed
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