Skip to content

release: promote development → main (2026-06-20T14:07:32Z) - #103

Merged
therajanmaurya merged 21 commits into
mainfrom
development
Jun 20, 2026
Merged

release: promote development → main (2026-06-20T14:07:32Z)#103
therajanmaurya merged 21 commits into
mainfrom
development

Conversation

@therajanmaurya

Copy link
Copy Markdown
Collaborator

Auto-opened by /paycraft-deploy Phase 4 PROMOTE.

Source: origin/development @ fbe10c8
Target: origin/main @ 288983c
Diff: 21 commits

This PR is fast-forward-only — main is kept as an exact replica of development at promote time. No manual edits should land on main.

MobileByteLabsCommunity and others added 21 commits June 19, 2026 15:48
…et redesign

SDK (cmp-paycraft 2.0.10):
- `Mode` enum derived from `apiKey` prefix (pk_test_*/pk_live_*); replaces
  the device-id-based Testing Trial model. `BillingPlan.currency` field
  threaded through. `SuiteProviderAdapter.getCheckoutUrl` looks up
  `bySku[plan.id]?[plan.currency]` with USD fallback (no cross-mode fallback).
- `PayCraftPaywallViewModel` resets `isSubmitting=false` after the browser
  intent fires — fixes the stuck Continue spinner on return-to-app.
- Provider bottom sheet redesigned: title + selected-plan summary line +
  brand-coloured tiles (Stripe purple, Razorpay blue) + Recommended-for-you
  badge keyed off `plan.currency` (INR → Razorpay, otherwise → Stripe) +
  payment-method chips (UPI/Cards/Wallets/Net Banking/EMI for Razorpay;
  Visa/Mastercard/Amex/Apple Pay/Google Pay/Link for Stripe) + secure
  footer. Real brand-mark ImageVectors for Stripe/Razorpay/PayPal embedded
  inline (simpleicons SVG paths) — no compose-resources dependency.

Schema (migration 070):
- New `tenant_providers_merge_payment_links(tenant, provider, mode, sku,
  links)` RPC writes nested `{sku: {currency: url}}`. The legacy flat
  `tenant_providers_set_payment_links` JSONB-concatenated at the top level,
  meaning product N's currency URLs overwrote product N-1's — root cause
  of "all 4 plans open the same Stripe page" symptom.
- Old RPC kept under DEPRECATED comment for 30-day grace.

Cleanup (migration 069):
- Drop `test_devices` table + `is_test_only` column + 4 device-registry
  RPCs. Test-only product visibility is now driven by Stripe-style mode
  duality (Test/Live toggle on dashboard + key-prefix on the SDK), not a
  per-device allowlist. Dashboard pages (test-devices/, settings link,
  product-form visibility checkbox) removed.

Dashboard:
- `mode.ts` + `mode-toggle.tsx` + `TestModeBanner` — Stripe-style Test/Live
  toggle in the dashboard header. Cookie `pc_mode` (365d) drives server-side
  `getMode()`; amber pinstripe banner in Test mode.
- Sync helpers (`stripe-route-helper.ts`) thread `body.sku` into the merge
  RPC so multi-product tenants don't clobber each other's currency entries.
- `/api/pricing` POST now auto-fires `stripeSyncProduct +
  razorpaySyncProduct + cashfreeSyncProduct` after writing pricing rows —
  any price edit materializes new Stripe Prices + Payment Links
  automatically, eliminating manual Sync clicks for price changes.

Edge Function `/config`:
- Filter providers by locale + non-empty nested-shape links AND filter by
  `is_active=true`. Was the root cause of "Providers=0 on US locale"
  (Stripe's `supported_locales: ["IN"]` was filtering it out; multi-tenant
  test_payment_links were empty objects on top-level keys).

Maestro:
- `.maestro/paywall-stripe-checkout-e2e.yaml` and `provider-sheet-demo.yaml`
  cover the full happy path (Settings → Get Premium → 4 plans (no Testing
  Trial) → Continue → provider sheet (Stripe + Razorpay) → checkout page
  with email field). Both green on Q4CIZHA6Y5GMJV89 (OnePlus CPH2423).
- Update 5 JSON test fixtures (ConfigClientTest + SelfHostedBackendIntegrationTest +
  3 in TierGateE2ETest) to nested test_payment_links shape so kotlinx.serialization
  parses them into the new Map<sku, Map<currency, url>> ProviderDto field.
- Add DeviceFingerprint.js.kt actual that was missing for the jsMain target —
  Kotlin/JS bundle was failing on `expected DeviceFingerprint has no actual
  declaration ... for JS` (pre-existing on dev branch since the JS target was
  added without backfilling this actual). Mirrors the wasmJs shape but uses
  the js("...") source-literal pattern matching other jsMain actuals.
…WYG iframe foundation

Sub-plan 01 of 3 in the paycraft-paywall-v2-production-ui epic. Foundation
for the v2 paywall — expands tenant_paywall by 11 content fields, surfaces
them in the dashboard's Paywall designer, and replaces the React mockup
LIVE PREVIEW with a true-WYSIWYG iframe of the cmp-paycraft Kotlin/JS bundle.

Sub-plan 02 (SDK BrandedStackTemplate + PaywallDesignToken + 2.1.0) and
sub-plan 03 (PayCraftPremiumBanner + RestoreSheet + CheckoutSuccessSheet)
build on this foundation in follow-up PRs.

Migration 071 (idempotent, deployed):
- 11 new tenant_paywall columns: hero_title/hero_subtitle/value_props/
  cta_continue/cta_get_premium/restore_label/terms_url/privacy_url/
  popular_plan_sku/success_title/success_message/success_cta_label/
  hero_icon_svg/hero_icon_url. All NOT NULL with defaults matching
  reels-downloader's existing strings.xml premium-banner copy so consumer
  dropping in PayCraftPremiumBanner() with no overrides gets identical look.
- sanitize_paywall_svg() helper rejects <script>, <foreignObject>, and
  external URL refs server-side via check_violation exception.
- tenant_paywall_upsert RPC replaced (CREATE OR REPLACE) with v2-aware
  signature handling every new field + SVG sanitization. Backward-compatible:
  v1 payloads (missing v2 fields) fall through to column defaults via COALESCE.

Edge Function /config (deployed):
- v2 paywall block surfaces all 14 columns via existing spread; tested live:
  hero_title/hero_subtitle/value_props/cta_continue/cta_get_premium/etc all
  present in /config response on framework-supabase prod.
- Fallback shape (when no tenant_paywall row exists) emits v2 defaults
  matching migration 071's DEFAULT clauses so the SDK never sees missing keys.

Dashboard:
- PaywallConfig + ValuePropTriple + PAYWALL_CONFIG_DEFAULTS +
  VALUE_PROP_ICON_VOCAB moved to lib/types.ts as the canonical TypeScript
  contract. Designer imports from there.
- Content section in Paywall designer carries all 11 v2 fields with a
  per-bullet repeater for value_props (icon-key picker + title + optional
  description + ▲/▼/✕ controls). Hero icon expandable details for SVG paste
  + URL fallback.
- BrandedStack listed first in the Template selector as v2 default; legacy
  Minimal/Premium/Dark labelled "Deprecated — drops in cmp-paycraft 3.0.0".
- PreviewIframe wired ON by default at https://paywall-preview.paycraft.
  mobilebytesensei.com (Cloudflare Pages target). Env-var-gated React-mockup
  fallback removed. paywall-designer.tsx tail deleted: PaywallPreview,
  FreeState, PremiumState, LoadingState, ErrorState, DeviceConflictState,
  OwnershipVerifiedState (the React mockup is gone — iframe is the only
  preview surface so what the dashboard shows IS what the SDK renders).
- Net: dashboard/components/paywall/paywall-designer.tsx loses 460 lines
  (mockup) and gains 280 (Content section + ValuePropsRepeater).

Deploy pipeline:
- .github/workflows/deploy-paywall-preview.yml builds :cmp-paycraft:
  jsBrowserDistribution and deploys to Cloudflare Pages project
  paycraft-paywall-preview on every push to main/development touching
  cmp-paycraft/** or the deploy config dir.
- preview-iframe-deploy/README.md documents the one-time admin setup:
  Cloudflare API token, Pages project creation, custom domain binding
  to paywall-preview.paycraft.mobilebytesensei.com, CORS allowlist, smoke
  check.

Tests:
- server/migrations/071_*.test.sql — 5 in-DB test cases covering round-trip
  (Test 1), v1-payload defaults (Test 2), SVG sanitization rejection of
  <script>/<foreignObject>/external href (Test 3), safe SVG acceptance
  (Test 4), column-inventory idempotency (Test 5). Transaction-wrapped
  with ROLLBACK so safe on any DB.
- dashboard/tests/paywall-designer-v2.spec.ts — 6 Playwright scenarios:
  hero title edit warm-path latency ≤ 500ms, value props add/edit/reorder/
  remove, SVG paste server rejection, popular plan dropdown persistence,
  iframe visual baseline diff ≤ 1%, BrandedStack default + legacy
  deprecation hint visible.

Verified live:
- Migration 071 applied to framework-supabase prod (mlwfgytjxlqyfxcgpysm),
  14/14 v2 columns present in tenant_paywall.
- Edge Function config redeployed; smoke fetch against reels-downloader
  tenant returns all 14 v2 fields populated with defaults.
- 26/26 sub-plan 01 CHEAP verify checks pass.

Open prereqs (sub-plan 01 G-1 boundary verify can't fully close until):
- Cloudflare Pages project paycraft-paywall-preview created + CLOUDFLARE_
  API_TOKEN GH secret added + paywall-preview.paycraft.mobilebytesensei.
  com DNS bound (one-time admin — see preview-iframe-deploy/README.md).
- Playwright dashboard test running (needs local or CI dashboard at
  localhost:3000). Baseline screenshot will be captured on first run.

Spec: plan-layer/project-plans/mbs/PayCraft/active/paycraft-paywall-v2-
production-ui/{RESEARCH.md, GOAL.md, PLAN.md, 01-dto-and-dashboard-
foundation.md}.
… tests

The new dashboard/tests/paywall-designer-v2.spec.ts (sub-plan 01 T8 of
paycraft-paywall-v2-production-ui) imports from @playwright/test. CI
'Dashboard typecheck (PreviewIframe contract)' job runs npx tsc --noEmit
over the whole repo including tests/, so the missing dep surfaced as 9 TS
errors. Adding the devDep resolves the imports without touching the test.
…anded-flow composables

Builds on sub-plan 01 (commit 009e6a6) — DTO v2 was already on the wire
and the dashboard's Content tab was already editing the 11 new fields.
This commit closes the SDK side so consumers actually see the v2 design.

cmp-paycraft 2.1.0 release shape:

PaywallDto v2 contract — config/SuiteConfig.kt
- 11 new content fields (hero_title, hero_subtitle, value_props rich
  triples, cta_continue, cta_get_premium, restore_label, terms_url,
  privacy_url, popular_plan_sku, success_title/_message/_cta_label,
  hero_icon_svg, hero_icon_url) wired up to the kotlinx.serialization
  graph. Every field has a default matching reels-downloader's
  strings.xml premium-banner keys so a tenant that hasn't configured the
  dashboard's Content tab still renders sensibly.
- New ValuePropTriple { icon, title, description? } data class — the
  rich-triple shape the dashboard's value-props repeater writes.
- Template default flipped to "branded-stack" (was "minimal").

PaywallTemplate — presentation/PaywallTemplate.kt
- New BRANDED_STACK enum value renders the v2 design.
- Legacy MINIMAL / PREMIUM / DARK kept @deprecated for 90-day grace.
  PaywallTemplate.parse("minimal"|"premium"|"dark") still resolves, with
  a deprecation warning surfaced to consumers compiling against 2.1.0.
- parse() now defaults to BRANDED_STACK on unknown values (was MINIMAL).
- Multiple kebab- / snake- / camel-case spellings accepted for the
  branded-stack key so consumers writing the template name from a
  hand-rolled YAML or env var don't trip on case.

BrandedStackTemplate — the production-grade paywall UI
- 60-line Free state composition: PaywallHeroIcon + hero title + hero
  subtitle + ValuePropList (rich-triple bullets, icon-leading row with
  optional description) + plan stack with PlanCard(popular=true) ring on
  popular_plan_sku + BrandedStackContinueButton + PaywallMicroFooter
  (PRIVACY · TERMS · RESTORE) + tier-aware "Powered by PayCraft by
  MobileByteSensei" line.
- Polished Loading/Premium/Error/DeviceConflict/OwnershipVerified
  branches — same DesignToken set so the whole template feels coherent.
- Reads tokens via PayCraftTheme.colors / .typography (existing
  CompositionLocal from cmp-paycraft 2.0.x). No new theme infrastructure
  required: PayCraftColors already carries accent/onSurface/surface/
  onSurfaceVariant/popularBadge/divider/outline. The only token added is
  PayCraftColors.onAccent (defaulted to white) so the Continue CTA's
  contentColor has an explicit roundup.

New PayCraftColors.onAccent — ui/theme/PayCraftColors.kt
- Defaults to Color.White so consumers building against 2.0.x with a
  custom PayCraftColors() ctor don't need to thread a new arg. Source-
  compatible add.

PayCraftPremiumBanner — ui/PayCraftPremiumBanner.kt (NEW)
- Drop-in Settings-tab card — the consumer-app entry point to the
  paywall. Defaults pulled from PaywallDto v2 fields (heroTitle /
  heroSubtitle / ctaGetPremium / restoreLabel) match reels-downloader's
  strings.xml premium-banner keys exactly so a tenant with NO dashboard
  config still produces the existing purple/yellow Card look.
- Per-field overrides (titleOverride / subtitleOverride / ctaOverride /
  restoreOverride) take precedence — consumers can ship the migration
  before the dashboard Content tab is populated by threading their
  existing strings.xml resources through the override args, then drop
  the overrides one at a time as the dashboard catches up.
- Per D8 the reels-downloader Settings-tab swap is opt-in; existing
  strings.xml keys stay during the 90-day grace. Migration guide at
  docs/MIGRATING-TO-PAYCRAFT-PREMIUM-BANNER.md.

PayCraftCheckoutSuccessSheet — ui/PayCraftCheckoutSuccessSheet.kt (NEW)
- Post-purchase celebration ModalBottomSheet shown once per (session ×
  SKU) after Free → Premium transition. Suppressed on subsequent calls
  via CheckoutSuccessFlag (multiplatform-settings flag keyed
  paycraft_success_shown_{sku}_{session_id}).
- Reads success_title / success_message / success_cta_label from
  PaywallDto v2.
- Consumer wires it themselves — cmp-paycraft 2.1.0 doesn't change
  PayCraftBillingManager's API surface. The post-purchase callback hook
  (D12 from sub-plan 03 GOAL.md) is documented in the migration guide
  but left for the consumer to wire (or a 2.1.1 follow-up if the demand
  is there).

CheckoutSuccessFlag — persistence/CheckoutSuccessFlag.kt (NEW)
- internal helper around multiplatform-settings. Same dependency
  ConfigCache + PayCraftSettingsStore already use, so no new cmp-paycraft
  dep added.

Version bump — gradle.properties
- paycraft.version=2.0.10 → 2.1.0. Additive minor per epic D4.

Tests — cmp-paycraft/src/commonTest/.../PaywallDtoBackcompatTest.kt (NEW)
- 5 kotlinx.serialization round-trip cases:
  1. v2 wire → v2 deserializer — every v2 field round-trips, value_props
     rich triples preserved.
  2. v1 wire → v2 deserializer — every v2 field falls through to the
     declared default matching migration 071's column DEFAULTs.
  3. Future-2.2.0 unknown fields → 2.1.0 deserializer with
     ignoreUnknownKeys=true — silently dropped, no exception.
  4. Zero value_props → empty list (not null).
  5. Single value_prop missing description → null description.

Maestro E2E — reels-downloader/.maestro/paywall-v2-branded-flow-e2e.yaml
- Extends PR #100's paywall-stripe-checkout-e2e.yaml. Asserts:
  launch → Settings → "Upgrade to Premium" + "Enjoy ad-free
  experience..." + "Get Premium" + "Restore Your Premium" all visible
  (PayCraftPremiumBanner with v2 default copy) → tap Get Premium →
  paywall modal with 4 plans + Continue → tap Continue → provider sheet
  with Stripe (from PR #100) → Stripe Checkout email field.
- Smoke runnable on Q4CIZHA6Y5GMJV89 (OnePlus CPH2423).

Consumer migration guide — docs/MIGRATING-TO-PAYCRAFT-PREMIUM-BANNER.md
- Pre-flight visual diff recipe (Compose Preview side-by-side, expect
  ≤ 2% pixel mismatch vs hand-coded reels-downloader baseline).
- Swap recipe: delete the existing Settings-tab Card block, replace with
  PayCraftPremiumBanner(onGetPremiumTap, onRestoreTap) two-arg form.
- During the 90-day grace, threading existing strings.xml resources
  through *Override args so the SDK reads host-resource copy until the
  dashboard is populated. Drop overrides one at a time.
- Dashboard fields to populate to match the prior banner look (the
  defaults match reels-downloader; other consumer apps that authored
  custom copy should overwrite in the dashboard before flipping).
- Rollback recipe — revert the consumer commit; strings.xml + git
  history have everything.

Sub-plan progress
- sub-plan 01 (DTO + dashboard + iframe foundation): commit 009e6a6 +
  c930095 — landed in this PR
- sub-plan 02 (SDK BrandedStackTemplate + 2.1.0): this commit
- sub-plan 03 (PayCraftPremiumBanner + PayCraftCheckoutSuccessSheet +
  Maestro + migration guide): this commit

Still in 03 backlog (not blocking 2.1.0 ship — consumer-app concerns
that can land in 2.1.1 / consumer-side commits as demand surfaces):
- PayCraftRestoreSheet visual polish to match BrandedStackTemplate
  (the existing PayCraftRestore.kt still works — design-token alignment
  is a follow-up).
- PayCraftBillingManager.onSubscriptionActivated callback hook
  (consumer can poll BillingState transition today).
- Compose UI screenshot baselines for BrandedStackTemplate's 6 billing
  states (functional rendering covered; pixel-perfect baselines need
  the dashboard iframe + Playwright pair from sub-plan 01 to be deployed
  first).
…ceVariant

CI Compile All Targets failure on f1651fe — 3 unresolved references:

  e: BrandedStackTemplate.kt:21:60 Unresolved reference 'PlayArrow'.
  e: BrandedStackTemplate.kt:256:79 Unresolved reference 'onSurfaceMuted'.
  e: BrandedStackTemplate.kt:406:53 Unresolved reference 'PlayArrow'.

cmp-paycraft's Compose deps only pull the core Material Icons set
(Filled.Star/Check/CheckCircle — verified used elsewhere in the module);
the `androidx.compose.material.icons.automirrored.*` package isn't on the
classpath. The hero icon doesn't need play-arrow semantics — Star is a
better fit for a premium-tier paywall and matches the badge used in
PayCraftPremiumBanner.kt + PayCraftRestore.kt.

onSurfaceMuted → onSurfaceVariant: leftover from the sed pass that
renamed PayCraftColors fields (the others were caught at f1651fe time;
this one slipped — only used in the Loading branch).

Self-tested via static grep — no other AutoMirrored/PlayArrow/
onSurfaceMuted references anywhere in the new SDK code.
… + string-template cleanup

Output of `./gradlew spotlessApply` against f1651fe + 44e7eed.
Pure formatting:

- Single-expression function bodies collapsed onto one line where width permits
  (CheckoutSuccessFlag.wasSuccessShown / keyFor, BrandedStackTemplate.paywallConfig,
  PaywallTemplate.parse and a handful of PaywallDto field decls in SuiteConfig).
- Redundant ${sessionId} → $sessionId in CheckoutSuccessFlag.keyFor.
- 2-space alignment normalization in PaywallTemplate.kt @deprecated args.

No behavior change. Resolves CI `pr-check / Code Quality` (spotlessCheck) on PR #100.
…arrow

Drift introduced by 44e7eed compile-fix that swapped `AutoMirrored.Filled.PlayArrow`
(unavailable on cmp-paycraft's classpath) for `Filled.Star` without updating the
adjacent doc comment. The code renders Star; the comment said play-arrow.

Audit prompt: "find there is not stub or shell left in epic to implement". No
behavior change; only a doc-vs-code accuracy fix.
Sub-plan 01 (paycraft-paywall-v2-production-ui) fixes surfaced by running
migration 071 against a fresh local Supabase:
- 071: add 'branded-stack' to paywall_template enum (the DTO's v2 default);
  cast template/branding to their enum types in tenant_paywall_upsert
  (text->enum is not an implicit assignment cast)
- 071: sanitize_paywall_svg used \b (a backspace in Postgres ARE) instead of
  \y (word boundary) -> <script>/<foreignObject> SVG payloads were silently
  accepted. Security fix; rejection now verified.
- relocate 071 round-trip test out of the symlinked supabase/migrations dir
  (supabase db reset was auto-applying it) to server/tests/; add the required
  api_key_test/api_key_live columns to the tenants fixture

sample-app:
- register SampleApplication in AndroidManifest (Koin was never started ->
  PayCraftRestore crashed on launch)
- migrate sample fixtures to the nested testPaymentLinksBySku DTO shape
…n callback, tests (cmp-paycraft 2.1.0)

Sub-plans 02 + 03 of paycraft-paywall-v2-production-ui — drive to code-complete
with passing tests (compileKotlinJvm + jvmTest 65 tests, 0 failures).

Sub-plan 02 (SDK branded-stack template):
- PaywallDesignToken: add PayCraftSpacing token + PaywallDesignToken /
  LocalPaywallDesignToken aliases (additive to PayCraftTheme); PayCraftRestore
  now consumes the token surface instead of hardcoded M3 colors
- PlanCard: real "MOST POPULAR" ring + badge (token-driven; was a plain text label)
- +14 BRANDED_STACK Compose tests across the 6 billing states (PaywallTemplateTest
  now 32 tests)
- bump paycraft.version 2.0.10 -> 2.1.0

Sub-plan 03 (branded flow components):
- BillingManager: subscriptionActivated SharedFlow, emitted once on the
  non-premium -> premium rising edge (sku + isTrial), edge-tracked across refreshes
- Compose tests for the 3 flow composables: PremiumBanner (12), CheckoutSuccessSheet
  (10), RestoreContent (11)
- Maestro e2e flows (branded + restore) targeting the in-repo sample-app showcase

Device-gated acceptance deferred: AC-9 screenshot-diff baseline, AC-16 Maestro run.
…lows

- /config Edge Function: requireRateLimit was called with 5 args against a
  6-param signature — "config_fetch" landed in the ipAddress slot, 60 in bucket,
  etc. Added IP extraction (x-forwarded-for) + the missing refillPerSec so it
  type-checks (deno check clean) and rate-limits correctly. (sub-plan 01 AC-4)
- Maestro flows reworked to the real sample-app UX and verified green on-device
  (Q4CIZHA6Y5GMJV89): the home Settings screen shows PayCraftPremiumBanner; the
  branded paywall (Monthly/Quarterly/Yearly/Continue) opens via "Get Premium".
  Cold-start via stopApp (device denies pm clear) + extendedWaitUntil for the
  async mock-config load. (sub-plan 03 AC-16)
The deploy-paywall-preview workflow referenced :cmp-paycraft:jsBrowserDistribution
and cmp-paycraft/build/dist/js/productionExecutable, but the Kotlin/JS preview
bundle lives in the cmp-paycraft:preview-js submodule. Fixed the build task to
:cmp-paycraft:preview-js:jsBrowserDistribution and all three output-path
references to cmp-paycraft/preview-js/build/dist/js/productionExecutable.

Verified by deploying the bundle to Cloudflare Pages (paycraft-paywall-preview)
manually via wrangler — live + 200 at paycraft-paywall-preview.pages.dev (AC-8);
PreviewIframe<->bundle postMessage re-render contract verified against it (AC-6).
The Kotlin/JS preview bundle (cmp-paycraft:preview-js) consumed only v1 fields
(template/primary_color), so the dashboard designer iframe ignored every v2 copy
field — not the "true-WYSIWYG" the epic promised. Extended PreviewConfig +
render() to drive the paywall from the v2 DTO:
- hero_title / hero_subtitle
- value_props (rich-triple list, icon-leading rows)
- cta_continue (primary button label)
- restore_label + terms_url + privacy_url (micro-footer links)
- popular_plan_sku (drives which plan card gets the MOST POPULAR ring)
- success_title / success_message (Premium state header)
All dashboard-entered copy is HTML-escaped. Verified true-WYSIWYG against the
live Cloudflare Pages preview via Playwright (9/9 field-render checks green).
…JS iframe bundle

The v2 plan replaced the dashboard's working React paywall preview with a
separate Cloudflare-hosted Kotlin/JS iframe bundle. That was the wrong call:
the existing React preview already renders the tenant's REAL products, whereas
the bundle hardcoded fake plans (a regression) and was a hand-written DOM port
(not the real SDK render) — so it added hosting + CI cost for no true-WYSIWYG
gain. Reverted per product-owner decision.

- dashboard/paywall-designer: restore the React PaywallPreview (phone-frame +
  all 6 states) rendering real products; remove the PreviewIframe. FreeState now
  also consumes the v2 DTO fields — hero_title/hero_subtitle, value_props list,
  cta_continue label, restore_label/terms_url/privacy_url footer links, and the
  MOST POPULAR ring keyed off popular_plan_sku matched to a real product.
- retire the iframe/bundle path: delete PreviewIframe.tsx, preview-iframe-deploy/,
  the cmp-paycraft:preview-js module (+ settings.gradle.kts include), and the
  deploy-paywall-preview.yml / preview-parity.yml workflows.

Keeps the v2 Content-tab editors. tsc clean; gradle reconfigures clean.
Make the AC-6 dashboard Paywall-designer Content-tab e2e actually runnable +
green (5/5). The authored spec targeted a non-existent iframe and had no config
or auth fixture.

- playwright.config.ts: webServer boots `next dev` with local-Supabase env
  (keys resolved at config-eval via `supabase status -o env`), baseURL :3000,
  storageState from global-setup.
- tests/global-setup.ts: seeds local Supabase idempotently (confirmed admin user
  + tenant + tenant_admins + 2 tenant_products + tenant_paywall), mints a session
  via signInWithPassword, and captures the correctly-named sb-*-auth-token cookies
  using @supabase/ssr's own setSession→setAll path (no cookie-name guessing) into
  a gitignored tests/.auth/state.json.
- rewrite paywall-designer-v2.spec.ts for the React preview (no iframe): hero edit
  reflects in the inline preview; value-props repeater add/edit/remove; popular-plan
  dropdown lists seeded products + persists; SVG <script> paste is rejected server-side
  (asserted on the 500 response — the route returns { error } and the designer shows
  no toast); BrandedStack default template.

Route is /paywall (the old spec used /settings/paywall). 5/5 green; tsc clean.
The pr-check Code Quality job failed on spotlessKotlin (ktlint) at
PayCraftBillingManager.kt:71 — `_previousIsPremium` is a private-only tracking
var with no public counterpart, which violates ktlint's backing-property-naming
rule (the file's other `_`-prefixed fields back public StateFlows). Renamed it to
`lastObservedPremium` (4 usages).

Also fix a pre-existing spotless misconfig surfaced locally: the `misc` format
targeted **/*.md etc. without excluding node_modules, so it scanned
dashboard/node_modules and threw NoSuchFileException on a dangling vendored file.
Added node_modules + dist to targetExclude.

spotlessApply reformatted the touched Kotlin (PlanCard else-branch, test files).
Local: `./gradlew spotlessCheck detekt` BUILD SUCCESSFUL.
…zen-install)

Vercel CI deploys with --frozen-lockfile and failed at `pnpm install`:
ERR_PNPM_OUTDATED_LOCKFILE — "1 dependencies were added: @playwright/test@^1.61.0".
Commit c930095 added @playwright/test to dashboard/package.json but didn't update
the lockfile. Regenerated via `pnpm install --lockfile-only` (lockfileVersion 9.0
preserved; only @playwright/test + playwright-core added). `next build` already
passes; this unblocks the Vercel deploy.
…ayment-links-and-provider-sheet

feat(paycraft): v2 mode-duality + nested payment-links + provider sheet + paywall-v2 epic (cmp-paycraft 2.1.0)
@therajanmaurya
therajanmaurya merged commit a4f2cae into main Jun 20, 2026
8 of 9 checks passed
@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
pay-craft Ready Ready Preview, Comment Jun 20, 2026 2:07pm

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77a81363-8b09-445a-ad0a-2696d8acb327

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

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

❤️ Share

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants