Skip to content

fix(billing): surface meterError alert + remove dead showAlreadyActiveDialog - #4081

Merged
PierreBrisorgueil merged 1 commit into
masterfrom
feat/billing-meter-error-surface
May 5, 2026
Merged

PierreBrisorgueil merged 1 commit into
masterfrom
feat/billing-meter-error-surface

Conversation

@PierreBrisorgueil

@PierreBrisorgueil PierreBrisorgueil commented May 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • C2 — Dead code removal: removed showAlreadyActiveDialog method, alreadyActiveDialog / alreadyActivePortalUrl state, and the 409 dialog template block from billing.subscriptions.component.vue. Confirmed zero production callers (the live 409 handling is self-contained in billing.pricing.view.vue). Dropped Suite 9 from unit tests — those 5 tests called the method directly, bypassing the real flow (fake coverage). i18n keys retained as they are still used by billing.pricing.view.vue.
  • C3 — meterError surface: wired meterError from useMeter() into both consumers' setup() returns (billing.subscriptions.component.vue and billing.usageBar.component.vue). Renders a closable v-alert[type=warning] above the meter section in the subscriptions component when polling fails. Added billing.meter.error.refreshFailed i18n keys (en + fr). Added new Suite 9 (3 tests) covering render when error is set, suppression when null, and dismiss by setting meterError = null. usageBar exposes meterError in its return but defers the UI to the parent subscriptions alert (no duplicate indicator — bar lives adjacent to subscriptions component).

Test plan

  • All 74 unit tests in billing.subscriptions.component.unit.tests.js pass (was 79, Suite 9 swapped: 5 dead removed, 3 real added)
  • Full coverage suite: 1452 tests pass, coverage unchanged (99.06% stmts)
  • Build: npm run build clean
  • Lint: npm run lint clean

Summary by CodeRabbit

  • New Features

    • Usage meter error alerts: failed refresh attempts now display as dismissible warning notifications
    • Subscription statuses: expanded to support "paused" and "unpaid" states with localized status labels
  • Bug Fixes

    • Polling behavior: improved stability and cleanup during component unmounting
    • Stripe redirects: enhanced URL validation for billing portal and checkout links
  • Localization

    • French translations: corrected spelling, accents, and billing terminology throughout

Copilot AI review requested due to automatic review settings May 5, 2026 06:02
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94252ce8-7127-4c5a-9fb7-de086c1fc667

📥 Commits

Reviewing files that changed from the base of the PR and between 208a0d0 and 4c580c9.

📒 Files selected for processing (4)
  • src/modules/billing/components/billing.subscriptions.component.vue
  • src/modules/billing/components/billing.usageBar.component.vue
  • src/modules/billing/lang/en.js
  • src/modules/billing/tests/billing.subscriptions.component.unit.tests.js

Walkthrough

The PR enhances the billing module with error handling visibility, extended subscription statuses, polling cleanup hardening, and Stripe URL validation. It adds shared meterError state to display meter refresh failures; extends subscription statuses to include paused and unpaid with corresponding i18n and icon mappings; introduces a pollAborted flag to prevent polling after component unmount; implements validateStripeUrl to whitelist Stripe redirect destinations; and updates French i18n with corrected diacritics.

Changes

Billing Module Enhancements

Layer / File(s) Summary
Error State & Composable
src/modules/billing/composables/billing.useMeter.js
Introduces module-scoped shared meterError Ref; safeRefresh now clears stale errors, logs failures, and stores errors into meterError. Initial fetchMissingMeterData failures also recorded to meterError; error cleared on final consumer unmount.
Stripe URL Validation
src/modules/billing/lib/stripeRedirect.js
New validateStripeUrl(url) function validates protocol (https:), parses URL, and enforces whitelist of allowed hostnames (checkout.stripe.com, billing.stripe.com); returns normalized URL or throws with specific error messages.
Internationalization
src/modules/billing/lang/en.js, src/modules/billing/lang/fr.js
English: added billing.meter.error.refreshFailed and billing.subscriptions.status.paused/unpaid. French: corrected diacritics/accents throughout (e.g., "Répartition", "Quota épuisé", "Illimité") and added missing status translations.
Subscription Status Mapping
src/modules/billing/components/billing.subscriptions.component.vue
Extended subscriptionStatusMeta, subscriptionStatusIcon, and subscriptionStatusAction to handle paused (reactivate action) and unpaid (update payment method action) states with i18n labels and Font Awesome icons. Removed "409 already-active" dialog and backing showAlreadyActiveDialog method.
Polling Cleanup
src/modules/billing/components/billing.subscriptions.component.vue
Introduces pollAborted flag set in beforeUnmount; polling loop callback exits early when flag is set, preventing scheduled timers after unmount.
Error Display
src/modules/billing/components/billing.subscriptions.component.vue
Added meterError warning v-alert in template with closable behavior; wired meterError from useMeter() into component state.
Meter Usage Component
src/modules/billing/components/billing.usageBar.component.vue
Extracted and exposed meterError from useMeter() in component setup; updated JSDoc return type.
Store Integration
src/modules/billing/stores/billing.store.js, src/modules/billing/views/billing.pricing.view.vue
openPortal and createExtrasCheckout now use validateStripeUrl for redirect target validation instead of manual URL parsing and protocol checks; invalid/off-whitelist URLs throw errors. billing.pricing.view applies validation to both main checkout and already-active portal URLs.
Test Coverage
src/modules/billing/tests/billing.stripeRedirect.unit.tests.js
New test suite for validateStripeUrl: validates whitelisted HTTPS hosts, normalizes query parameters, rejects empty/null/undefined/non-string inputs, relative/protocol-relative URLs, non-HTTPS schemes, and non-whitelisted hosts.
Component & Composable Tests
src/modules/billing/tests/billing.subscriptions.component.unit.tests.js, src/modules/billing/tests/billing.useMeter.unit.tests.js
Added paused/unpaid status test cases with i18n label assertions; replaced "409 dialog" tests with meterError alert render/clear tests; added pollAborted unmount cleanup verification. useMeter tests verify meterError initialization, failure transitions, recovery after transient failures, and presence on returned object.
Store & Action Tests
src/modules/billing/tests/billing.store.unit.tests.js
Updated expected error messages for non-HTTPS URLs to unified Stripe URL must use HTTPS text.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • pierreb-devkit/Vue#4078: Directly addresses all code-level improvements in this PR (paused/unpaid status handling, Stripe URL whitelist validation, shared meterError state, pollAborted flag, French diacritics correction).

Possibly related PRs

  • pierreb-devkit/Vue#3726: Modifies the same billing.store portal/checkout redirect logic (openPortal/createExtrasCheckout URL validation).
  • pierreb-devkit/Vue#4037: Introduces the initial useMeter implementation that this PR extends with shared meterError and error handling improvements.
  • pierreb-devkit/Vue#4079: Makes overlapping changes to the same billing files—shared meterError in useMeter, paused/unpaid status handling, pollAborted logic, and validateStripeUrl integration.

Suggested labels

Fix, billing

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely summarizes the two main changes: surfacing meterError alert and removing dead showAlreadyActiveDialog method, matching the PR's core objectives.
Description check ✅ Passed The description covers the two key changes (C2 and C3), includes justification, test plan with pass/fail criteria, and addresses most template sections including validation steps and guardrails.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/billing-meter-error-surface

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.

@codacy-production

codacy-production Bot commented May 5, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 5 duplication

Metric Results
Duplication 5

View in Codacy

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

Copilot AI 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.

Pull request overview

This PR improves the billing UX and security by (1) surfacing useMeter() refresh failures to the user via a warning alert, and (2) centralizing Stripe redirect URL validation (HTTPS + hostname allowlist) while removing dead 409-dialog code from the subscriptions component.

Changes:

  • Added validateStripeUrl() helper and replaced ad-hoc URL parsing in billing store + pricing view redirects.
  • Introduced shared meterError in useMeter() and exposed it to billing UI consumers; added a warning v-alert in BillingSubscriptionsComponent.
  • Removed dead showAlreadyActiveDialog + related state/template, and updated unit tests + i18n keys (including new status labels).

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/modules/billing/views/billing.pricing.view.vue Uses validateStripeUrl() before redirecting to Stripe (checkout + portal URL from 409 payload).
src/modules/billing/stores/billing.store.js Centralizes portal/checkout redirect validation through validateStripeUrl().
src/modules/billing/lib/stripeRedirect.js Adds the Stripe URL validation helper (HTTPS + host allowlist).
src/modules/billing/components/billing.subscriptions.component.vue Surfaces meterError via a warning alert; removes dead already-active dialog; adds paused/unpaid status handling.
src/modules/billing/components/billing.usageBar.component.vue Exposes meterError from useMeter() (UI handled by parent).
src/modules/billing/composables/billing.useMeter.js Adds shared meterError ref and populates it on initial fetch / refresh failures.
src/modules/billing/lang/en.js Adds i18n keys for meter refresh failure + new paused/unpaid status labels.
src/modules/billing/lang/fr.js Adds i18n keys for meter refresh failure + new paused/unpaid status labels; fixes multiple French accent/wording strings.
src/modules/billing/tests/billing.stripeRedirect.unit.tests.js Adds unit coverage for validateStripeUrl().
src/modules/billing/tests/billing.store.unit.tests.js Updates expected error messages to match validateStripeUrl() errors.
src/modules/billing/tests/billing.useMeter.unit.tests.js Adds coverage for meterError behavior on initial fetch and polling failures.
src/modules/billing/tests/billing.subscriptions.component.unit.tests.js Updates status chip expectations, replaces dead-code tests with meterError alert tests, adds poll abort coverage.

Comment on lines +148 to +152
meterError.value = null;
void fetchMissingMeterData(billingStore).catch((err) => {
console.error('[billing.useMeter] initial fetch failed', err);
meterError.value = err;
});
if (['active', 'trialing'].includes(this.subscriptionStatus)) return 'fa-solid fa-circle-check';
if (this.subscriptionStatus === 'past_due') return 'fa-solid fa-triangle-exclamation';
if (this.subscriptionStatus === 'paused') return 'fa-solid fa-pause-circle';
if (this.subscriptionStatus === 'unpaid') return 'fa-solid fa-exclamation-triangle';
coderabbitai[bot]
coderabbitai Bot previously requested changes May 5, 2026

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

🤖 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/components/billing.subscriptions.component.vue`:
- Around line 199-211: The meter error alert is showing for non-meter
deployments because meterError is set even when meterMode is false; in the
template change the v-alert to only render when both meterMode and meterError
are true (use v-if="meterMode && meterError"), and in setup prevent useMeter({
pollIntervalMs: 0 }) and the fetchMissingMeterData call from running unless
meterMode is truthy (wrap the useMeter invocation and any call to
fetchMissingMeterData in an if (meterMode) check) so meter-related logic only
runs for meter-enabled deployments.
- Around line 495-496: Update the Font Awesome icon names returned for
this.subscriptionStatus: replace 'fa-pause-circle' with the FA6+ canonical
'fa-circle-pause' for the 'paused' case, and replace 'fa-exclamation-triangle'
with 'fa-triangle-exclamation' for the 'unpaid' case so the computed/method that
maps this.subscriptionStatus to icon classes (the clauses checking
this.subscriptionStatus === 'paused' and === 'unpaid') uses the same FA6+ naming
as the other icons (e.g., 'fa-circle-check').

In `@src/modules/billing/composables/billing.useMeter.js`:
- Line 11: The module-level sharedMeterError (const sharedMeterError) is being
unconditionally reset on each useMeter() consumer mount which wipes errors for
other components; remove the unconditional clear of sharedMeterError/meterError
performed during useMeter() initialization (the pre-clear around where
meterError is assigned), rely on safeRefresh and the initial-fetch catch to
manage error lifecycle, and add a unit test in billing.useMeter.unit.tests.js
that mounts a second consumer after setting sharedMeterError to assert the error
is preserved across mounts.

In `@src/modules/billing/lib/stripeRedirect.js`:
- Around line 19-36: The validateStripeUrl function currently accepts URLs with
embedded credentials (e.g., parsed.username/parsed.password) which should be
rejected; update validateStripeUrl to throw an error when parsed.username or
parsed.password are non-empty (e.g., "Stripe URL must not contain credentials"),
keep existing checks for protocol and ALLOWED_HOSTS, and add a unit test in
billing.stripeRedirect.unit.tests.js that verifies URLs like
"https://attacker@checkout.stripe.com/..." are rejected.
🪄 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: 7f4b3892-533c-4574-bfcd-2d5a6325e764

📥 Commits

Reviewing files that changed from the base of the PR and between 646859d and 208a0d0.

📒 Files selected for processing (12)
  • src/modules/billing/components/billing.subscriptions.component.vue
  • src/modules/billing/components/billing.usageBar.component.vue
  • src/modules/billing/composables/billing.useMeter.js
  • src/modules/billing/lang/en.js
  • src/modules/billing/lang/fr.js
  • src/modules/billing/lib/stripeRedirect.js
  • src/modules/billing/stores/billing.store.js
  • src/modules/billing/tests/billing.store.unit.tests.js
  • src/modules/billing/tests/billing.stripeRedirect.unit.tests.js
  • src/modules/billing/tests/billing.subscriptions.component.unit.tests.js
  • src/modules/billing/tests/billing.useMeter.unit.tests.js
  • src/modules/billing/views/billing.pricing.view.vue

Comment thread src/modules/billing/components/billing.subscriptions.component.vue Outdated
Comment on lines +495 to +496
if (this.subscriptionStatus === 'paused') return 'fa-solid fa-pause-circle';
if (this.subscriptionStatus === 'unpaid') return 'fa-solid fa-exclamation-triangle';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Font Awesome 7 icon names fa-circle-pause fa-triangle-exclamation canonical class names

💡 Result:

In Font Awesome 7, the canonical CSS class names for the specified icons are fa-circle-pause and fa-triangle-exclamation. These are used with style prefixes like fa-solid (e.g., <i class="fas fa-circle-pause"></i>).[1][2][3][4][5][6] Font Awesome 7 is the current version as of 2026, with official pages at fontawesome.com/icons/circle-pause and fontawesome.com/icons/exclamation-triangle (noting the slug matches circle-pause, while display may vary).[7][8] These classes have been consistent from version 6 to 7, as confirmed by multiple technical references linking Font Awesome 6/7 CDNs and class usage.[2][5][6]

Citations:


Update icon names to Font Awesome 6+ canonical form.

Lines 495–496 use Font Awesome 5 names that are inconsistent with the rest of the file and incompatible with Font Awesome 7.2.0. The canonical FA 6+ names are fa-circle-pause and fa-triangle-exclamation:

Line Current Expected
495 fa-pause-circle fa-circle-pause
496 fa-exclamation-triangle fa-triangle-exclamation

This matches the naming used for other icons in the file (e.g., fa-circle-check, fa-triangle-exclamation, fa-circle-exclamation) and ensures rendering works in FA 7.

Proposed fix
-      if (this.subscriptionStatus === 'paused') return 'fa-solid fa-pause-circle';
-      if (this.subscriptionStatus === 'unpaid') return 'fa-solid fa-exclamation-triangle';
+      if (this.subscriptionStatus === 'paused') return 'fa-solid fa-circle-pause';
+      if (this.subscriptionStatus === 'unpaid') return 'fa-solid fa-triangle-exclamation';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this.subscriptionStatus === 'paused') return 'fa-solid fa-pause-circle';
if (this.subscriptionStatus === 'unpaid') return 'fa-solid fa-exclamation-triangle';
if (this.subscriptionStatus === 'paused') return 'fa-solid fa-circle-pause';
if (this.subscriptionStatus === 'unpaid') return 'fa-solid fa-triangle-exclamation';
🤖 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 `@src/modules/billing/components/billing.subscriptions.component.vue` around
lines 495 - 496, Update the Font Awesome icon names returned for
this.subscriptionStatus: replace 'fa-pause-circle' with the FA6+ canonical
'fa-circle-pause' for the 'paused' case, and replace 'fa-exclamation-triangle'
with 'fa-triangle-exclamation' for the 'unpaid' case so the computed/method that
maps this.subscriptionStatus to icon classes (the clauses checking
this.subscriptionStatus === 'paused' and === 'unpaid') uses the same FA6+ naming
as the other icons (e.g., 'fa-circle-check').

let consumerCount = 0;
// Shared error ref so the single interval callback can write into it
// and all consumers receive the same reactive signal.
const sharedMeterError = ref(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Shared meterError is wiped on every new consumer mount.

Because meterError aliases the module-scope sharedMeterError, the unconditional reset on Line 148 runs every time any component calls useMeter() in setup(). This PR wires meterError into both billing.subscriptions.component.vue and billing.usageBar.component.vue, and the usage bar can render inside the subscriptions view — when the second consumer mounts, it instantly clears any error the first consumer (or the polling loop) just surfaced, hiding the new warning v-alert until the next 30s poll cycle re-captures the failure. The same hazard applies to remounts during route changes.

safeRefresh already clears stale errors before each poll, and the initial-fetch catch on Line 149 only writes on failure, so the pre-clear here is redundant in addition to being racy.

🛠️ Proposed fix — drop the unconditional clear on every consumer mount
-  meterError.value = null;
   void fetchMissingMeterData(billingStore).catch((err) => {
     console.error('[billing.useMeter] initial fetch failed', err);
     meterError.value = err;
   });

Alternatively, gate the clear on first-consumer-only:

-  meterError.value = null;
+  if (consumerCount === 1) meterError.value = null;

Add a test in billing.useMeter.unit.tests.js that mounts a second consumer after meterError is set and asserts it is preserved.

Also applies to: 62-62, 148-152

🤖 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 `@src/modules/billing/composables/billing.useMeter.js` at line 11, The
module-level sharedMeterError (const sharedMeterError) is being unconditionally
reset on each useMeter() consumer mount which wipes errors for other components;
remove the unconditional clear of sharedMeterError/meterError performed during
useMeter() initialization (the pre-clear around where meterError is assigned),
rely on safeRefresh and the initial-fetch catch to manage error lifecycle, and
add a unit test in billing.useMeter.unit.tests.js that mounts a second consumer
after setting sharedMeterError to assert the error is preserved across mounts.

Comment on lines +19 to +36
export function validateStripeUrl(url) {
if (typeof url !== 'string' || !url) {
throw new Error('Stripe URL is empty');
}
let parsed;
try {
parsed = new URL(url); // throws on relative URLs and invalid strings
} catch {
throw new Error('Stripe URL is invalid');
}
if (parsed.protocol !== 'https:') {
throw new Error('Stripe URL must use HTTPS');
}
if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
throw new Error(`Stripe URL host not allowed: ${parsed.hostname}`);
}
return parsed.toString();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject URLs containing embedded credentials.

validateStripeUrl does not inspect parsed.username / parsed.password. A URL like https://attacker@checkout.stripe.com/... parses with hostname checkout.stripe.com and currently passes validation. Stripe never returns redirect URLs with credentials; rejecting them tightens the helper to match its stated security purpose.

🛡️ Proposed hardening
   if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
     throw new Error(`Stripe URL host not allowed: ${parsed.hostname}`);
   }
+  if (parsed.username || parsed.password) {
+    throw new Error('Stripe URL must not contain credentials');
+  }
   return parsed.toString();

Add a matching test in billing.stripeRedirect.unit.tests.js.

🤖 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 `@src/modules/billing/lib/stripeRedirect.js` around lines 19 - 36, The
validateStripeUrl function currently accepts URLs with embedded credentials
(e.g., parsed.username/parsed.password) which should be rejected; update
validateStripeUrl to throw an error when parsed.username or parsed.password are
non-empty (e.g., "Stripe URL must not contain credentials"), keep existing
checks for protocol and ALLOWED_HOSTS, and add a unit test in
billing.stripeRedirect.unit.tests.js that verifies URLs like
"https://attacker@checkout.stripe.com/..." are rejected.

@PierreBrisorgueil
PierreBrisorgueil force-pushed the feat/billing-meter-error-surface branch from 208a0d0 to cc629d3 Compare May 5, 2026 06:42
@codecov

codecov Bot commented May 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.51%. Comparing base (dd62b3d) to head (4c580c9).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4081   +/-   ##
=======================================
  Coverage   99.51%   99.51%           
=======================================
  Files          32       32           
  Lines        1036     1036           
  Branches      278      278           
=======================================
  Hits         1031     1031           
  Misses          5        5           

☔ View full report in Codecov by Sentry.
📢 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.

…iptions

- Surface meterError ref from useMeter via v-alert gated to meterMode
  template (no rendering in legacy mode)
- a11y: aria-live=polite on the alert
- i18n key billing.meter.error.refreshFailed (en.js only)
- Remove dead showAlreadyActiveDialog method, dialog template, state,
  and Suite 9 tests from subscriptions component (real 409 flow lives
  in billing.pricing.view.vue)
@PierreBrisorgueil
PierreBrisorgueil force-pushed the feat/billing-meter-error-surface branch from cc629d3 to 4c580c9 Compare May 5, 2026 07:02
@PierreBrisorgueil
PierreBrisorgueil dismissed coderabbitai[bot]’s stale review May 5, 2026 07:31

Stale review on commit 208a0d0 — branch was reset hard to master and redone clean (commit 4c580c9, Opus). Current HEAD has only the 2 intended fixes, all CI green including CodeRabbit re-review SUCCESS.

@PierreBrisorgueil
PierreBrisorgueil merged commit 61f5d53 into master May 5, 2026
7 checks passed
@PierreBrisorgueil
PierreBrisorgueil deleted the feat/billing-meter-error-surface branch May 5, 2026 07:32
PierreBrisorgueil added a commit that referenced this pull request May 5, 2026
… credentials (#4082)

- subscriptionStatusIcon: fa-pause-circle -> fa-circle-pause,
  fa-exclamation-triangle -> fa-triangle-exclamation
  (FA6+ canonical, sibling-consistent)
- useMeter: drop unconditional sharedMeterError reset on consumer mount;
  safeRefresh manages the lifecycle
- stripeRedirect: reject URLs containing credentials (parsed.username/password)
  to prevent phishing display tricks

CodeRabbit follow-up findings from PR #4081 review on stale commit.
PierreBrisorgueil pushed a commit that referenced this pull request Jun 1, 2026
# [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))
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