fix(billing): mega vue hardening v3 — UX + design + a11y + V4 - #4063
Conversation
Bundles UX post-payment fixes + design/a11y/V4 migration into one PR. UX HARDENING (🔴 + 3× 🟠 + 4× 🟡 + 2× 🔵 + ⚪) - successUrl includes ?success=true preserved through router redirect, success v-alert fires post-payment + URL cleaned (close trust-breaking silent landing) - Dual CTA "Manage Subscription" + "Upgrade" gated on highest plan - "Buy units" opens BillingExtrasCheckoutModal inline (no /pricing nav hop) - openPortal errors surfaced via portalError + v-alert - subscription.status mapped to colored v-chip + actionable button on past_due/canceled - BillingUpgradePrompt meter-mode aware (emits buy-pack vs link to /pricing) - pricingCard skeleton + tooltip while Stripe data loading - Annual savings hint always visible - Signup error surfaced via v-alert (not just console.error) - meterProgress role/cursor conditional on @click listener DESIGN + a11y + V4 (4× 🟠 + 4× 🟡 + 2× 🔵 + ⚪) - text-body-2 V3 alias → text-body-medium V4 (6 occurrences) - Hex hardcoded admin gradient → Vuetify theme tokens via CSS custom properties - aria-live="polite" on summary, "assertive" on overage chip - v-dialog extrasCheckout :fullscreen smAndDown - Inline styles → scoped CSS classes (cursor, max-width, legend dot, monospace) - v-card-title typography override cleaned - billingPlanBadgeComponent → BillingPlanBadgeComponent (PascalCase consistency) - pricingCard hover transform respects prefers-reduced-motion
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
WalkthroughThis PR updates billing checkout flows to redirect to the subscriptions tab with success/error messages, adds signup error alert UI, makes numerous billing component improvements for UX and accessibility, and updates typography in the notfound view. ChangesSignup Error Handling
Billing Checkout Success & Query Parameter Handling
Billing Component UI & Accessibility Improvements
Home Notfound View Typography
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Review rate limit: 0/1 reviews remaining, refill in 48 minutes and 22 seconds.Comment |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 5 high |
🟢 Metrics 30 complexity · 43 duplication
Metric Results Complexity 30 Duplication 43
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4063 +/- ##
=======================================
Coverage 99.51% 99.51%
=======================================
Files 31 31
Lines 1034 1034
Branches 278 278
=======================================
Hits 1029 1029
Misses 5 5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull Request Overview
This PR is currently not up to standards according to Codacy. While the functional additions for billing hardening are mostly present, there are critical issues regarding destructive state management in the router and brittle implementation patterns that should be addressed before merging.
Specifically, the URL cleanup logic in the subscriptions component inadvertently discards all other application state, and the method used to detect click listeners relies on Vue internals that may break in future updates. Additionally, a potential 'object Object' display bug exists in the signup error handling, which lacks sufficient unit test coverage for its branching logic.
About this PR
- Detection of parent click listeners in
BillingMeterProgressComponentrelies oninstance?.vnode?.props?.onClick. This is an internal Vue property that is susceptible to breaking in minor version updates. Use standardattrsor a dedicated boolean prop to signal interactivity instead. - The implementation of
handleCheckoutSuccessQueryuses a 100mssetTimeoutto clean the URL. Consider using Vue'snextTickor managing this through a router navigation guard to ensure the state is cleared predictably after the UI has updated.
Test suggestions
- Verify query parameter preservation on /billing to /users?tab=subscriptions redirect.
- Verify success alert rendering and subsequent URL cleanup in BillingSubscriptionsComponent.
- Verify subscription status mapping for all 5 statuses including chip colors and recovery button text.
- Verify 'Change Plan' CTA gating when user is on the highest available plan.
- Verify BillingMeterProgress affordance logic (role=button and pointer) toggles based on listener presence.
- Verify presence of aria-live='polite' and aria-live='assertive' on usage and overage regions.
- Verify PricingCard displays v-skeleton-loader when pricesLoading prop is true.
- Verify that API signup errors are surfaced and rendered in a closable v-alert.
- Verify signupErrorMessage branch coverage for strings, error objects, and arrays of error messages.
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify signupErrorMessage branch coverage for strings, error objects, and arrays of error messages.
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
There was a problem hiding this comment.
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 (4)
src/modules/auth/views/signup.view.vue (1)
177-199: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd JSDoc to the modified
data()function.
data()was modified (newsignupErrorstate) but still has no JSDoc header; this violates the function-doc requirement for changed.vuecode.Proposed fix
export default { components: { AuthOrganizationSetupComponent, }, + /** + * `@desc` Initialize signup view reactive state. + * `@returns` {object} Component data state. + */ data() { const theme = useTheme(); return {As per coding guidelines: "
**/*.{js,ts,vue}: Every new or modified function must have a JSDoc header with one-line description,@paramfor each argument, and@returnsfor any non-void return value."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/auth/views/signup.view.vue` around lines 177 - 199, Add a JSDoc header above the data() function describing its purpose (returns the component reactive state), include an `@returns` tag specifying the returned object (e.g., `@returns` {{theme: *, valid: boolean, signupError: (null|*), ...}} or simply {`@link` Object}) and, since data() has no parameters, no `@param` tags are required; update the JSDoc to reference the new signupError state so the header reflects the modified return shape and place it immediately above the data() declaration in signup.view.vue.src/modules/billing/router/billing.router.js (1)
24-32: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winDocument the new redirect function with JSDoc (extract to named helper).
Line 26 introduces a modified function without a JSDoc header. Extracting it to a named constant keeps route config clean and satisfies the function-doc requirement.
♻️ Suggested fix
+/** + * `@desc` Redirect legacy /billing route to subscriptions while preserving incoming query params. + * `@param` {import('vue-router').RouteLocationNormalized} to - Incoming route location. + * `@returns` {{path: string, query: Object}} Redirect target route. + */ +const redirectBillingToSubscriptions = (to) => ({ path: '/users', query: { ...to.query, tab: 'subscriptions' } }); + export default [ @@ { path: '/billing', name: 'Billing', - redirect: (to) => ({ path: '/users', query: { ...to.query, tab: 'subscriptions' } }), + redirect: redirectBillingToSubscriptions,As per coding guidelines
**/*.{js,ts,vue}: Every new or modified function must have a JSDoc header with one-line description,@paramfor each argument, and@returnsfor any non-void return value (always include@returnsfor async functions).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/billing/router/billing.router.js` around lines 24 - 32, Extract the inline redirect function used in the Billing route (the anonymous function at path '/billing' that redirects to { path: '/users', query: { ...to.query, tab: 'subscriptions' } }) into a named constant (e.g., billingRedirect) and replace the inline function with that constant; add a JSDoc header above the new helper with a one-line description, a `@param` for the 'to' route/location argument, and a `@returns` describing the returned route object; ensure the router's redirect property references billingRedirect.src/modules/billing/views/billing.pricing.view.vue (1)
205-229: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd missing
@returnsin asynccreated()JSDoc.Line 208 is an async lifecycle method and its JSDoc should include an explicit
@returnsannotation for consistency/compliance.♻️ Suggested fix
/** * `@desc` Fetch billing plans and subscription data on component creation. + * `@returns` {Promise<void>} */ async created() {As per coding guidelines
**/*.{js,ts,vue}: Every new or modified function must have a JSDoc header with one-line description,@paramfor each argument, and@returnsfor any non-void return value (always include@returnsfor async functions).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/billing/views/billing.pricing.view.vue` around lines 205 - 229, The JSDoc for the async created() lifecycle method is missing an `@returns` annotation; update the comment block above the async created() method to include an explicit `@returns` tag (e.g. `@returns` {Promise<void>} or similar descriptive text) since created() is declared async (ensure the tag matches the async signature and mention Promise<void>), leaving existing description and any `@param` tags intact; locate the async created() method that calls this.billingStore.fetchPlans(), this.billingStore.fetchSubscription(), and inspects this.$route.query/hash to apply the change.src/modules/billing/components/billing.subscriptions.component.vue (1)
407-432: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd missing JSDoc contract on modified lifecycle hooks.
mountedis async and was modified but has no@returns, and the newbeforeUnmounthook has no JSDoc header.Proposed patch
/** * `@desc` Fetch subscription data on mount and handle Stripe redirect query params. * The legacy /billing page is retired; this component is now the landing point for * Stripe redirects (success, cancel, packPurchased). + * `@returns` {Promise<void>} */ async mounted() { @@ - beforeUnmount() { + /** + * `@desc` Clear pending checkout-success URL cleanup timer. + * `@returns` {void} + */ + beforeUnmount() { if (this.successCleanupTimer) { clearTimeout(this.successCleanupTimer); } },As per coding guidelines
**/*.{js,ts,vue}: “Every new or modified function must have a JSDoc header with one-line description,@paramfor each argument, and@returnsfor any non-void return value (always include@returnsfor async functions)”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/billing/components/billing.subscriptions.component.vue` around lines 407 - 432, Add JSDoc headers for the modified lifecycle hooks: add a one-line description and an `@returns` tag for the async mounted() (since it now returns a Promise), and add a one-line description plus `@returns` (void) for beforeUnmount(); also include an explicit `@param` section (none) if your linter requires it for functions with no args. Update the comments immediately above the mounted() and beforeUnmount() methods in the component (referencing mounted, beforeUnmount, successCleanupTimer, and the billingStore.fetchSubscription call) to satisfy the rule that async functions must document `@returns` and all new/modified functions have JSDoc.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/modules/billing/components/billing.subscriptions.component.vue`:
- Around line 440-445: The code treats query.packPurchased as truthy which
causes 'false' strings to be considered success; update the logic in the
isSuccess calculation and the ternary that sets paymentSuccessMessage to
explicitly parse packPurchased (e.g., compare query.packPurchased === 'true' or
coerce with a safe parser) instead of raw truthiness so only an explicit true
value signals success; adjust references to isSuccess, paymentSuccessMessage and
the packPurchased checks accordingly.
In `@src/modules/billing/components/billing.upgradePrompt.component.vue`:
- Around line 76-80: Add a JSDoc header above the validator function for the
`mode` prop in billing.upgradePrompt.component.vue: include a one-line
description, a `@param` tag for the `value` parameter (string) and a `@returns` tag
indicating it returns a boolean; apply this JSDoc immediately above the
validator arrow function that validates ['subscription','meter'] so the file
meets the new JSDoc requirement for modified/added functions.
---
Outside diff comments:
In `@src/modules/auth/views/signup.view.vue`:
- Around line 177-199: Add a JSDoc header above the data() function describing
its purpose (returns the component reactive state), include an `@returns` tag
specifying the returned object (e.g., `@returns` {{theme: *, valid: boolean,
signupError: (null|*), ...}} or simply {`@link` Object}) and, since data() has no
parameters, no `@param` tags are required; update the JSDoc to reference the new
signupError state so the header reflects the modified return shape and place it
immediately above the data() declaration in signup.view.vue.
In `@src/modules/billing/components/billing.subscriptions.component.vue`:
- Around line 407-432: Add JSDoc headers for the modified lifecycle hooks: add a
one-line description and an `@returns` tag for the async mounted() (since it now
returns a Promise), and add a one-line description plus `@returns` (void) for
beforeUnmount(); also include an explicit `@param` section (none) if your linter
requires it for functions with no args. Update the comments immediately above
the mounted() and beforeUnmount() methods in the component (referencing mounted,
beforeUnmount, successCleanupTimer, and the billingStore.fetchSubscription call)
to satisfy the rule that async functions must document `@returns` and all
new/modified functions have JSDoc.
In `@src/modules/billing/router/billing.router.js`:
- Around line 24-32: Extract the inline redirect function used in the Billing
route (the anonymous function at path '/billing' that redirects to { path:
'/users', query: { ...to.query, tab: 'subscriptions' } }) into a named constant
(e.g., billingRedirect) and replace the inline function with that constant; add
a JSDoc header above the new helper with a one-line description, a `@param` for
the 'to' route/location argument, and a `@returns` describing the returned route
object; ensure the router's redirect property references billingRedirect.
In `@src/modules/billing/views/billing.pricing.view.vue`:
- Around line 205-229: The JSDoc for the async created() lifecycle method is
missing an `@returns` annotation; update the comment block above the async
created() method to include an explicit `@returns` tag (e.g. `@returns`
{Promise<void>} or similar descriptive text) since created() is declared async
(ensure the tag matches the async signature and mention Promise<void>), leaving
existing description and any `@param` tags intact; locate the async created()
method that calls this.billingStore.fetchPlans(),
this.billingStore.fetchSubscription(), and inspects this.$route.query/hash to
apply the change.
🪄 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: cd1b7fdc-62d9-413e-969c-97871ba4d486
⛔ Files ignored due to path filters (2)
src/modules/billing/tests/__snapshots__/billing.meterProgress.component.unit.tests.js.snapis excluded by!**/*.snapsrc/modules/billing/tests/__snapshots__/billing.usageBar.component.unit.tests.js.snapis excluded by!**/*.snap
📒 Files selected for processing (24)
src/modules/app/tests/app.router.unit.tests.jssrc/modules/auth/tests/auth.signup.view.unit.tests.jssrc/modules/auth/views/signup.view.vuesrc/modules/billing/components/billing.extrasCheckoutModal.component.vuesrc/modules/billing/components/billing.extrasLedger.component.vuesrc/modules/billing/components/billing.meterBreakdownChart.component.vuesrc/modules/billing/components/billing.meterProgress.component.vuesrc/modules/billing/components/billing.pricingCard.component.vuesrc/modules/billing/components/billing.pricingToggle.component.vuesrc/modules/billing/components/billing.subscriptions.component.vuesrc/modules/billing/components/billing.upgradePrompt.component.vuesrc/modules/billing/components/billing.usageBar.component.vuesrc/modules/billing/router/billing.router.jssrc/modules/billing/stores/billing.store.jssrc/modules/billing/tests/billing.extrasCheckoutModal.component.unit.tests.jssrc/modules/billing/tests/billing.meterProgress.component.unit.tests.jssrc/modules/billing/tests/billing.pricingCard.component.unit.tests.jssrc/modules/billing/tests/billing.pricingToggle.component.unit.tests.jssrc/modules/billing/tests/billing.store.unit.tests.jssrc/modules/billing/tests/billing.subscriptions.component.unit.tests.jssrc/modules/billing/tests/billing.upgradePrompt.component.unit.tests.jssrc/modules/billing/tests/billing.usageBar.component.unit.tests.jssrc/modules/billing/views/billing.pricing.view.vuesrc/modules/home/views/home.notfound.view.vue
There was a problem hiding this comment.
Pull request overview
This PR hardens the billing UX around Stripe checkout returns and improves UI consistency/accessibility across billing (and a couple adjacent) views as part of the Vuetify v4 typography migration.
Changes:
- Updates Stripe checkout success/cancel routing to land users on the
/users?tab=subscriptionsbilling area, preserving relevant redirect query parameters. - Improves billing UI/UX: inline “Buy units” modal flow for meter mode, status chip/action mapping, error surfacing, and clearer pricing loading/unavailable states.
- Adds/updates unit tests and snapshots covering new a11y attributes, redirect/query cleanup behavior, and new UI states.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/home/views/home.notfound.view.vue | Updates typography classes to v4 utilities. |
| src/modules/billing/views/billing.pricing.view.vue | Passes pricing-loading state to cards; supports #units deep-link tab selection. |
| src/modules/billing/components/billing.pricingCard.component.vue | Adds price skeleton + “pricing unavailable” tooltip/CTA gating. |
| src/modules/billing/components/billing.pricingToggle.component.vue | Adds always-visible annual savings hint and refines chip logic. |
| src/modules/billing/components/billing.subscriptions.component.vue | Handles Stripe success query, portal error UI, status chip/action mapping, inline extras modal, and “Change Plan” CTA gating. |
| src/modules/billing/components/billing.upgradePrompt.component.vue | Adds meter-mode behavior to emit buy-pack instead of routing to pricing. |
| src/modules/billing/components/billing.usageBar.component.vue | Migrates typography, removes inline gradients, and adds aria-live regions. |
| src/modules/billing/components/billing.meterProgress.component.vue | Conditional interactivity semantics, aria-live regions, and click handling only when listeners exist. |
| src/modules/billing/components/billing.meterBreakdownChart.component.vue | Replaces inline styles with scoped CSS for bar/legend dot sizing. |
| src/modules/billing/components/billing.extrasLedger.component.vue | Moves inline styles to scoped CSS (monospace + truncation). |
| src/modules/billing/components/billing.extrasCheckoutModal.component.vue | Adds mobile fullscreen dialog behavior and v4 typography updates. |
| src/modules/billing/stores/billing.store.js | Updates Stripe checkout success/cancel URLs for subscription + extras flows. |
| src/modules/billing/router/billing.router.js | Redirects /billing to /users?tab=subscriptions while preserving incoming query params. |
| src/modules/billing/tests/billing.store.unit.tests.js | Updates expectations for new success/cancel URLs. |
| src/modules/billing/tests/billing.subscriptions.component.unit.tests.js | Adds coverage for inline extras modal, portal error UI, status chip mapping, and success-query cleanup. |
| src/modules/billing/tests/billing.upgradePrompt.component.unit.tests.js | Tests meter-mode buy-pack emit behavior. |
| src/modules/billing/tests/billing.pricingCard.component.unit.tests.js | Tests skeleton and tooltip behavior for pricing load/unavailable states. |
| src/modules/billing/tests/billing.pricingToggle.component.unit.tests.js | Updates tests for new annual hint + chip conditions. |
| src/modules/billing/tests/billing.meterProgress.component.unit.tests.js | Adds tests for conditional button semantics + aria-live; adds snapshot. |
| src/modules/billing/tests/billing.usageBar.component.unit.tests.js | Adds aria-live assertions + snapshot for meter overage rendering. |
| src/modules/billing/tests/billing.extrasCheckoutModal.component.unit.tests.js | Tests fullscreen prop wiring and viewport/display behavior. |
| src/modules/billing/tests/snapshots/billing.meterProgress.component.unit.tests.js.snap | Snapshot output for meter overage state. |
| src/modules/billing/tests/snapshots/billing.usageBar.component.unit.tests.js.snap | Snapshot output for usage bar meter overage state. |
| src/modules/auth/views/signup.view.vue | Surfaces signup errors via a dismissible alert; adds API error-to-message mapping helper. |
| src/modules/auth/tests/auth.signup.view.unit.tests.js | Adds assertions for new signup error UI and message mapping. |
| src/modules/app/tests/app.router.unit.tests.js | Verifies /billing redirect preserves Stripe success query params. |
…servation, JSDoc, branch tests - Parse packPurchased strictly (=== 'true') to prevent 'false' string triggering success banner - Preserve existing query params on router.replace; only unset success/type/packPurchased keys - Add JSDoc to async mounted(), beforeUnmount(), data(), mode validator, async created() - Extract billing router redirect to named helper with JSDoc - Add signupErrorMessage entry.error fallback in errors array map - Add branch tests: packPurchased strict/false, string data, error key, plain-string arrays
Stale review — all issues addressed in d396da6. CodeRabbit has already re-approved the new push.
# [2.1.0](v2.0.0...v2.1.0) (2026-06-01) ### Bug Fixes * **billing:** 4 pricing page bugs (signed-in features, hero bg, truncation, guest CTA) ([#4123](#4123)) ([bd399bd](bd399bd)) * **billing:** add aria-expanded + touch support to BillingComputeGauge components ([#4181](#4181)) ([7e7fa97](7e7fa97)) * **billing:** billing UX hardening — 5 reliability fixes ([#4079](#4079)) ([dd62b3d](dd62b3d)), closes [#4078](#4078) * **billing:** canonical FA6 icons + meterError lifecycle + reject URL credentials ([#4082](#4082)) ([be16045](be16045)), closes [#4081](#4081) * **billing:** clear intentId on Stripe cancel-redirect ([#4085](#4085)) ([11be9ab](11be9ab)) * **billing:** drop empty overage aria-live div + add fr overDetail key ([#4142](#4142)) ([10e5d81](10e5d81)) * **billing:** i18n migration + Intl.NumberFormat USD (audit Codex P1) ([#4069](#4069)) ([3d3a4f6](3d3a4f6)) * **billing:** mega vue hardening v3 — UX + design + a11y + V4 ([#4063](#4063)) ([802cbc4](802cbc4)) * **billing:** send {} body on portal POST to satisfy Zod PortalRequest schema ([#4137](#4137)) ([bf6b9c8](bf6b9c8)) * **billing:** send intentId UUID to close extras double-charge window ([#4084](#4084)) ([3e27c4c](3e27c4c)) * **billing:** subscription state safety + webhook lag polling (audit Codex P1) ([#4068](#4068)) ([328700b](328700b)) * **billing:** surface meterError + remove dead 409 dialog from subscriptions ([#4081](#4081)) ([61f5d53](61f5d53)) * **billing:** V5 polish — F5 polling recovery + visibility refetch + i18n residual + locale null-guard ([#4070](#4070)) ([4908adb](4908adb)) * **billing:** V6 polish — sessionStorage guard + locale BCP47 + i18n plural + NaN guard ([#4076](#4076)) ([2ad018c](2ad018c)) * **build:** drop ARG defaults for analytics_* + filter empty env vars ([#4112](#4112)) ([afd336e](afd336e)), closes [Vue#4110](https://github.com/Vue/issues/4110) [#4110](#4110) * **build:** restore Layer 5 empty-string env override (revert [#4112](#4112) part 2) ([c9aa9e6](c9aa9e6)), closes [Vue#4110](https://github.com/Vue/issues/4110) [comes-io/trawl_vue#880](https://github.com/comes-io/trawl_vue/issues/880) [Vue#4110](https://github.com/Vue/issues/4110) * **core:** pin CorePageHeader to content size in flex-column contexts ([#4210](#4210)) ([e182512](e182512)), closes [#4202](#4202) * **layout:** route /pricing outside app shell — no drawer offset on signed-in ([#4124](#4124)) ([d862fbb](d862fbb)) * **legal:** cookie banner UA-detection + appName fallback to app.title ([#4113](#4113)) ([04cc70a](04cc70a)), closes [trawl_vue#876](https://github.com/trawl_vue/issues/876) [#4109](#4109) * **legal:** cookie consent + footer polish (5 QA bugs) ([#4098](#4098)) ([a9bf0a3](a9bf0a3)) * **legal:** gate cookie banner on isMounted to prevent prerender hydration double-render ([#4109](#4109)) ([3198e48](3198e48)), closes [#app](https://github.com/pierreb-devkit/Vue/issues/app) * **router:** redirect authenticated users to config.sign.route instead of hardcoded '/' ([#4088](#4088)) ([ca9094e](ca9094e)), closes [#4083](#4083) * **tasks:** propagate store errors so views gate navigation on success ([#4221](#4221)) ([b029d39](b029d39)), closes [#4218](#4218) * **ui:** chrome convergence — restore section title + surface backgrounds + homogeneous gutter ([#4188](#4188)) ([1e5fdf1](1e5fdf1)) * **users:** re-apply route tab when serverConfig.billing arrives async ([#4138](#4138)) ([87cc7b2](87cc7b2)) * **users:** refetch billing subscription on auth state change ([#4125](#4125)) ([5a3425a](5a3425a)) ### Features * **admin:** invitations management tab ([#4217](#4217)) ([61ad86c](61ad86c)), closes [invitedBy/#actions](https://github.com/pierreb-devkit/Vue/issues/actions) * **analytics:** add identify and reset helpers, wire auth store ([#4104](#4104)) ([c4d8b87](c4d8b87)) * **auth:** invite-gated signup UI ([#4212](#4212)) ([d17a841](d17a841)) * **billing:** add 'Manage subscription' footer link in meterDrawer ([#4056](#4056)) ([#4057](#4057)) ([7d494af](7d494af)) * **billing:** align packs.component on BillingCardComponent (V4 unified schema) ([#4147](#4147)) ([7f5dd41](7f5dd41)) * **billing:** combined pool gauge + linear breakdown bars + alerts cleanup ([f5034b1](f5034b1)) * **billing:** config-driven static-content resolver (no file replace) ([e95d944](e95d944)) * **billing:** expose netRemainingRaw + overage in useMeter for negative quota display ([#4061](#4061)) ([111e64b](111e64b)), closes [#4060](#4060) * **billing:** meter gauges display % primary, compute units in overflow tooltip ([#4139](#4139)) ([50498ba](50498ba)) * **billing:** meter UX refonte — drop drawer + subscriptions tab ([#4059](#4059)) ([891b5ae](891b5ae)), closes [#subscriptions](https://github.com/pierreb-devkit/Vue/issues/subscriptions) [#1](#1) [#2](#2) [#3](#3) [#4](#4) * **billing:** post-grant upgrade prompt variant for depleted signupGrant ([#4128](#4128)) ([0ae8558](0ae8558)) * **billing:** pricing page redesign — multi-mode + auto-savings + sectioned features + FAQ ([#4102](#4102)) ([2c83ab6](2c83ab6)) * **billing:** pricingCard Free CTA — 'Sign up' for guests, route to /signup ([#4106](#4106)) ([cc92ac8](cc92ac8)) * **billing:** redesign Subscriptions view to match user-tabs aesthetic ([#4127](#4127)) ([15fd466](15fd466)) * **billing:** relocate billing under Organization settings ([#4175](#4175)) ([1f36137](1f36137)) * **billing:** sidenav compute gauge above sign-out row (meter mode) ([#4126](#4126)) ([fe1497f](fe1497f)) * **billing:** sidenav compute gauge redesign — button-shape above sign-out row ([#4140](#4140)) ([9d3a090](9d3a090)) * **billing:** sidenav compute gauge revamp — v-progress-circular + v-tooltip ([#4144](#4144)) ([5d60f1b](5d60f1b)), closes [#prepend](https://github.com/pierreb-devkit/Vue/issues/prepend) * **billing:** subscriptions view 2-col layout — drop dup bar, CTA to /pricing#units ([#4141](#4141)) ([b7ef516](b7ef516)), closes [pricing#units](https://github.com/pricing/issues/units) * **billing:** unified BillingCardComponent + annual toggle disabled state ([#4146](#4146)) ([474bb11](474bb11)) * **billing:** unified BillingCardComponent + annual toggle disabled state ([#4149](#4149)) ([d1403ff](d1403ff)) * **billing:** v4 hardening + Phase 3 polish — equivalences chips + UX gaps + a11y ([#4066](#4066)) ([dd40b2a](dd40b2a)) * **billing:** wire netRemainingRaw + overage into devkit components ([#4062](#4062)) ([61d6897](61d6897)), closes [#4061](#4061) * **core:** reusable PageTabs component + Account view refactor ([#4183](#4183)) ([dc2504a](dc2504a)) * **core:** unified logo+title lockup in header and sidenav ([#4086](#4086)) ([ec457db](ec457db)), closes [#4083](#4083) * **feature:** read ERRORS.md in Phase 0 before coding ([#4089](#4089)) ([ddf8f60](ddf8f60)) * **legal:** Add legal module + cookie consent (RGPD) ([#4097](#4097)) ([595e6c4](595e6c4)), closes [#3204116570](https://github.com/pierreb-devkit/Vue/issues/3204116570) [#3204116650](https://github.com/pierreb-devkit/Vue/issues/3204116650) [#1](#1) [#19](#19) [#18](#18) [#13](#13) [#11](#11) [#18](#18) [#3](#3) [#10](#10) [#12](#12) [#14](#14) [#15](#15) [#17](#17) [#20](#20) [#4](#4) [#5](#5) [#6](#6) [#7](#7) [#8](#8) [#9](#9) [#16](#16) [#3](#3) [#16](#16) [#22](#22) * **legal:** liquid glass cookie banner — Vuetify-only with friendlier copy ([#4115](#4115)) ([3d74789](3d74789)), closes [#4114](#4114) * **monitoring:** single-source PostHog Error Tracking (drop Sentry) ([#4118](#4118)) ([82dfca6](82dfca6)) * **organizations:** add Organization tab + rename General route to tab-addressable ([#4184](#4184)) ([fdfbf0b](fdfbf0b)) * **organizations:** soft suggestedJoin onboarding banner + recovery-screen copy ([#4176](#4176)) ([25a3ceb](25a3ceb)) * **seo:** enrich seoInjectPlugin — multi-schemas + rich SoftwareApplication + themeColor ([#4092](#4092)) ([#4111](#4111)) ([7f8bd09](7f8bd09)) * **skill/feature:** add Phase 0.0 issue claim-on-start ([#4117](#4117)) ([a582430](a582430)), closes [pierreb-projects/infra#28](https://github.com/pierreb-projects/infra/issues/28) * **skills/update-stack:** block on undeclared drift vs upstream ([#4228](#4228)) ([ab8ee68](ab8ee68)), closes [#4227](#4227) * **users:** subs full-width + delete account danger zone + halo full-bleed fix ([#4143](#4143)) ([981f073](981f073))
Summary
Comprehensive Vue billing module hardening bundling UX post-payment fixes + design/a11y/V4 typography migration. Goal: module simple, polished, accessible.
Source: 4-agent audit 2026-05-02 found 21 frontend findings (1 🔴 + 7 🟠 + 8 🟡 + 4 🔵 + 1 ⚪).
Sections
A — UX hardening (🔴 + 9 issues)
Critical: successUrl strip caused silent post-payment landing — fixed via param-aware route + v-alert + URL clean. Plus dual CTA gating, inline modal for units, portal error UI, status badge mapping, meter-mode upgrade prompt, pricing skeleton, annual hint, signup error surface, meterProgress affordance honesty.
B — Design + a11y + V4 (12 issues)
text-body-2 → text-body-medium V4 migration (6 sites), gradient theme tokens, aria-live announcements, mobile fullscreen dialog, inline → scoped CSS, v-card-title cleanup, casing fix, prefers-reduced-motion guard.
Test plan
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
UX Improvements