Skip to content

fix(billing): migrate default packs to the V4 pricing-card schema - #4463

Merged
PierreBrisorgueil merged 3 commits into
masterfrom
fix/4458-packs-v4-schema
Jul 16, 2026
Merged

PierreBrisorgueil merged 3 commits into
masterfrom
fix/4458-packs-v4-schema

Conversation

@PierreBrisorgueil

@PierreBrisorgueil PierreBrisorgueil commented Jul 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • What changed: Migrated the devkit default packs static content from the legacy { packId, label, priceUsd, meterUnits, featureSections } shape to the V4 unified pricing-card schema ({ id, title, subtitle, price:{amount,period}, cta, features, meta:{packId,priceUsd,meterUnits} }) that BillingCardComponent requires. Also adapted billing.subscriptions.component.vue's extrasPacks getter at the consumer boundary so the extras-checkout modal (which still expects the legacy flat shape) maps correctly from the same V4 static-content source, fixed stale docstrings that told downstream projects to edit/override this stack-managed file directly (real contract is config.billing.staticContent, resolved by billing.resolveStaticContent.js), and genericized demo pack copy (scrap runs → compute jobs, autofix sessions → automation runs) plus a misleading "Saves ~10%" claim (large pack is actually ~45% cheaper per-unit) for the public repo.
  • Why: plans were migrated to the V4 schema in 53ec981 but packs were missed. Opening /pricing#units crashed on the unguarded item.price.amount read (packs had no price object), and item.id being undefined (packs used packId) no-opped the CTA.
  • Related issues: Closes 🐛 Migrate default packs to the V4 pricing-card schema #4458

Scope

  • Modules impacted: billing
  • Cross-module impact: none
  • Risk level: low (static demo content + one adapter getter, no backend/contract changes; Stripe-facing packId/priceUsd/meterUnits values preserved unchanged under meta)

Validation

  • npm run lint
  • npm run test:unit
  • npm run build
  • Manual checks done (if applicable)

Tests: new suites render/click against the REAL packs export from billing.static-content.js (not a hand-written V4 mock) in both billing.packs.component.unit.tests.js and billing.subscriptions.component.unit.tests.js — verified red (reproduced the exact /pricing#units crash) against the pre-fix shape, green after. Full suite: 2474 tests green, build green.

Guardrails check

  • No secrets or credentials introduced (.env*, secrets/**, keys, tokens)
  • No risky rename/move of core stack paths
  • Changes remain merge-friendly for downstream projects
  • Tests added or updated when behavior changed

Notes for reviewers

  • Security considerations: none — static demo content only, no auth/data paths touched.
  • Mergeability considerations: billing.static-content.js is stack-managed (wiped by /update-stack --theirs); downstream projects customize via config.billing.staticContent, unaffected by this change.
  • Follow-up tasks (optional): none.

https://claude.ai/code/session_01WfNC8bt1TgL4AsiYgCEGup

Summary by CodeRabbit

  • New Features

    • Updated billing pack content with clearer pricing, descriptions, calls to action, and feature details.
    • Improved compatibility between billing pack displays and extras checkout.
  • Bug Fixes

    • Fixed fallback billing packs so extras checkout consistently receives valid pack IDs, prices, and usage units.
  • Tests

    • Added coverage for rendering real billing packs, displaying prices and IDs, and starting checkout with the correct pack.

Devkit static-content `packs` still shipped the legacy shape
{ packId, label, priceUsd, meterUnits, featureSections } while
BillingCardComponent (used by BillingPacksComponent) requires the V4
unified schema { id, title, subtitle, price:{amount,period}, cta,
features, ... } — `plans` were migrated in 53ec981, packs were not.
Opening /pricing#units crashed on the unguarded `item.price.amount`
read, and `item.id` being undefined (packs used `packId`) no-opped
the CTA.

- Migrate the 3 default packs to the V4 card schema, preserving the
  existing devkit-demo placeholder pricing/quota numbers (id values
  unchanged: demo_small/demo_medium/demo_large). Raw Stripe-facing
  values (priceUsd, meterUnits) move to `meta`, keyed by the same id
  via `meta.packId`, since the extras-checkout modal still needs them.
- billing.subscriptions.component.vue `extrasPacks`: adapt the V4
  static-content fallback to the legacy { packId, label, priceUsd,
  meterUnits } shape BillingExtrasCheckoutModalComponent expects, so
  both the pricing-card render and the extras-checkout modal get their
  required shape from the same static-content source. The backend
  `packsAvailable` fallback path is untouched (already legacy shape).
- Fix stale docstrings (top-of-file, plans, packs) that told
  downstream to replace/override billing.static-content.js directly —
  the real contract is `config.billing.staticContent`, resolved by
  billing.resolveStaticContent.js (e95d944); this file is
  stack-managed and wiped by `/update-stack --theirs`.
- Tests: new suites render/click against the REAL static-content
  `packs` export (not a hand-written V4 mock) in both
  billing.packs.component.unit.tests.js and
  billing.subscriptions.component.unit.tests.js — verified red
  (reproduced the exact crash) against the pre-fix shape, green after.

Closes #4458

Claude-Session: https://claude.ai/code/session_01WfNC8bt1TgL4AsiYgCEGup
Devkit is public OSS; pack feature copy leaked downstream-product
vocabulary ("scrap runs", "autofix sessions"). Replaced with neutral
demo wording (compute jobs / automation runs) and fixed a misleading
"Saves ~10% vs Small Pack" claim (large is actually ~45% cheaper
per-unit) with neutral "Best per-unit value" phrasing.

Claude-Session: https://claude.ai/code/session_01WfNC8bt1TgL4AsiYgCEGup
@PierreBrisorgueil PierreBrisorgueil added Fix billing Phase 3 — Stripe billing integration labels Jul 16, 2026
@PierreBrisorgueil PierreBrisorgueil self-assigned this Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PierreBrisorgueil, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 19ad371a-73dc-4dea-8da2-405c25d92391

📥 Commits

Reviewing files that changed from the base of the PR and between de47ec0 and cef9ab3.

📒 Files selected for processing (1)
  • src/modules/billing/config/billing.static-content.js

Walkthrough

Default billing packs now use the V4 pricing-card schema. Subscription extras checkout adapts those packs to its legacy modal contract, with regression tests covering real static content, card rendering, identifiers, and checkout mapping.

Changes

Billing pack migration

Layer / File(s) Summary
V4 pack schema
src/modules/billing/config/billing.static-content.js
Default packs now expose V4 card fields, flattened features, and aligned metadata; static-content documentation describes stack-managed overrides.
Real pack card validation
src/modules/billing/tests/billing.packs.component.unit.tests.js
Tests render the real packs, validate price.amount and id, and verify CTA checkout uses the pack identifier.
Extras checkout boundary adapter
src/modules/billing/components/billing.subscriptions.component.vue, src/modules/billing/tests/billing.subscriptions.component.unit.tests.js
The fallback converts V4 packs into { packId, label, priceUsd, meterUnits }, with tests verifying values from real static content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: migrating default billing packs to the V4 pricing-card schema.
Description check ✅ Passed The description includes the required summary, scope, validation, guardrails, and reviewer notes in the expected template.
Linked Issues check ✅ Passed The changes satisfy #4458 by migrating packs to V4, adapting the extras modal boundary, updating docs, and adding real-export tests.
Out of Scope Changes check ✅ Passed The extra edits stay within billing static content, docs, copy, and tests, with no clear unrelated changes introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/4458-packs-v4-schema

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.

@codecov

codecov Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.56%. Comparing base (1f7cce7) to head (cef9ab3).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4463   +/-   ##
=======================================
  Coverage   99.56%   99.56%           
=======================================
  Files          35       35           
  Lines        1391     1391           
  Branches      436      436           
=======================================
  Hits         1385     1385           
  Misses          6        6           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@PierreBrisorgueil
PierreBrisorgueil marked this pull request as ready for review July 16, 2026 21:14

@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

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

Inline comments:
In `@src/modules/billing/config/billing.static-content.js`:
- Around line 9-10: Update the documentation comment in
billing.static-content.js to clarify that explicit null overrides are honored
only for display-optional keys. Document that billing.resolveStaticContent.js
replaces null plans, packs, and faqs values with the devkit defaults as a
structural-key exception, while preserving the existing precedence description.
- Line 176: Update the expiration text entries in the billing static content,
including the lines corresponding to “Never expires (24mo)” and the two
additional occurrences, so each states one consistent policy: either credits
expire after 24 months or never expire. Ensure no copy combines “never expires”
with a 24-month limit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 57d47d8c-6001-4531-9e84-d933278c5dfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7cce7 and de47ec0.

📒 Files selected for processing (4)
  • src/modules/billing/components/billing.subscriptions.component.vue
  • src/modules/billing/config/billing.static-content.js
  • src/modules/billing/tests/billing.packs.component.unit.tests.js
  • src/modules/billing/tests/billing.subscriptions.component.unit.tests.js

Comment thread src/modules/billing/config/billing.static-content.js Outdated
Comment thread src/modules/billing/config/billing.static-content.js Outdated
…y copy

Address CodeRabbit PR #4463 review:
- Top-of-file docstring said "project value wins when present, even null"
  unconditionally, but billing.resolveStaticContent.js only honors explicit
  null for display-optional keys (pricingMode/tabs/header/halo) — structural
  keys (plans/packs/faqs) coerce null back to the devkit default since
  consumers .map()/read properties on them unconditionally. Documented the
  exception.
- "Never expires (24mo)" pack feature line contradicted itself (pre-existing
  copy, restructured but not reworded by this PR's V4 migration). Dropped
  "(24mo)" to match the fa-infinity icon's intent.
@PierreBrisorgueil
PierreBrisorgueil merged commit 84789b8 into master Jul 16, 2026
7 checks passed
@PierreBrisorgueil
PierreBrisorgueil deleted the fix/4458-packs-v4-schema branch July 16, 2026 21:37
PierreBrisorgueil pushed a commit that referenced this pull request Aug 1, 2026
# [2.3.0](v2.2.0...v2.3.0) (2026-08-01)

### Bug Fixes

* **auth,organizations:** guard soft-refresh navigation and the signout race ([#4464](#4464)) ([6a2546e](6a2546e)), closes [#4451](#4451) [#4459](#4459)
* **auth,organizations:** soft-refresh (token) at onboarding sites to prevent silent sign-out ([#4431](#4431)) ([5d88766](5d88766))
* **auth,organizations:** surface failures in org-create and email-verification flows ([#4451](#4451)) ([b161df5](b161df5)), closes [#4431](#4431) [#4447](#4447)
* **auth:** surface requestJoin failures in the org-setup alert ([#4389](#4389)) ([#4397](#4397)) ([f92003d](f92003d))
* **auth:** token() soft-refresh also carries pendingRequests ([#4436](#4436)) ([0b40e8c](0b40e8c))
* **billing:** config-source the upgrade-prompt pricing copy ([#4465](#4465)) ([9cb370d](9cb370d)), closes [#4460](#4460)
* **billing:** meter-mode upgrade prompt copy + single-entry route ([#4410](#4410)) ([df396cf](df396cf)), closes [pricing#units](https://github.com/pricing/issues/units) [pricing#units](https://github.com/pricing/issues/units)
* **billing:** migrate default packs to the V4 pricing-card schema ([#4463](#4463)) ([84789b8](84789b8)), closes [pricing#units](https://github.com/pricing/issues/units) [#4458](#4458)
* **core,organizations:** config-gate org-required header + unify first-org redirect ([#4423](#4423)) ([7adb298](7adb298)), closes [#4421](#4421) [#4422](#4422)
* **core:** wire the computed theme into Vuetify so auto dark-mode works ([#4466](#4466)) ([ed35252](ed35252)), closes [#4462](#4462)
* **prerender:** strip local server origin from captured HTML + fail-hard leak assert ([#4502](#4502)) ([#4503](#4503)) ([dbcf8d7](dbcf8d7))
* **skills:** flip PR to ready before waiting on CodeRabbit to avoid a draft deadlock ([#4473](#4473)) ([a733998](a733998)), closes [#4450](#4450)

### Features

* **billing:** pricing feature rows wrap + explicit ✗ not-included marker ([#4390](#4390)) ([4bc2515](4bc2515))
* **invitations:** show invite form when user-facing invitations enabled with open signup ([#4501](#4501)) ([daa6688](daa6688)), closes [#4500](#4500)
* **skills:** /feature non-interactive invocation — structured SKIP/STOP instead of prompts ([#4455](#4455)) ([197c5f3](197c5f3)), closes [#N](https://github.com/pierreb-devkit/Vue/issues/N) [#failure](https://github.com/pierreb-devkit/Vue/issues/failure) [#4454](#4454)
* **ui:** config-overridable loader, plan-badge set, and copy strings ([#4475](#4475)) ([688911a](688911a)), closes [#4474](#4474)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

billing Phase 3 — Stripe billing integration Fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Migrate default packs to the V4 pricing-card schema

1 participant