fix(frontend): UTC day boundaries so dashboard today matches everywhere - #2931
Conversation
Dashboard charts derived "today" from browser-local midnight then converted with toISOString(), so Europe (east of UTC) requested yesterday while Brazil kept today. Align all dashboard date ranges and API params with UTC days to match backend daily_* buckets. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
📝 WalkthroughWalkthroughThe PR centralizes UTC date handling and dashboard chart behavior. It adds shared billing-cycle, series transformation, Chart.js registration, color, today-line, filtering, and loading-delay utilities. ChangesUTC date and range foundation
Shared chart contracts and services
Dashboard data and loading flows
Dashboard chart integrations
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
Remove unused normalizeToStartOfDay and share billing-period filter plus chart today/transform helpers to drop new-code duplication. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Move billing-cycle chart helpers into useOrgBillingCycleChart and reuse computeLastDayEvolution / ensureMinDelay so new-code duplication stays under the Sonar quality gate. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Extract createTodayLineOptions and hue helpers so Sonar no longer counts duplicated new-code blocks across dashboard charts. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Register dashboard charts once and use useCurrentOrgBillingCycleChart so file preambles no longer share long duplicated new-code blocks. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Extract dailyChartBaseProps so dashboard charts no longer share long identical defineProps blocks counted as new duplication. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Share useDashboardDailyChartCycle and diversify chart file preambles so identical new-code blocks stay under the duplication threshold. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_e12b6610-73ad-498d-abfa-a036f67453e7) |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 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/components/dashboard/BundleUploadsChart.vue`:
- Around line 16-17: Replace the relative imports in
src/components/dashboard/BundleUploadsChart.vue lines 16-17 and
src/components/dashboard/DeploymentStatsChart.vue lines 16-17 with
~/components/dashboard/... alias paths for createChartLegendItems and
ChartLegend. Replace the relative chartTooltip import in
src/components/dashboard/UpdateStatsChart.vue line 17 with
~/services/chartTooltip.
In `@src/components/dashboard/DeploymentStatsCard.vue`:
- Around line 145-146: Update the DeploymentStatsCard date-range query to
compute the next UTC day after last30DaysEnd, format it with formatUtcDateParam,
and filter deployed_at with an exclusive .lt() bound instead of .lte(). Add a
boundary test covering a deployment later on the current UTC day.
In `@src/services/chartTooltip.ts`:
- Around line 64-77: Update the log-navigation handler in UpdateStatsChart.vue
to derive both start and end query bounds from the selected UTC calendar date
using Date.UTC, preserving the day represented by clickContext.date across local
time zones. Remove the unused dayjs import, and add a non-UTC timezone test
covering the generated start and end parameters.
In `@src/services/dashboardChartRegister.ts`:
- Around line 1-30: Update registerDashboardCharts to import the Chart.js Legend
component and include Legend in the Chart.register call, ensuring
UpdateStatsChart’s enabled plugins.legend renders on direct page loads.
In `@src/services/date.ts`:
- Around line 186-190: Update getChartDateRange to parse date-only billingStart
and billingEnd values with a dedicated UTC range-boundary parser before
normalizeToUtcStartOfDay, while preserving parseDatePreservingUtc for localized
display paths. Add a regression test covering getChartDateRange(true,
'2026-08-07', ...) in a positive-offset timezone and verify the UTC date remains
August 7.
- Around line 159-165: Replace the nested ternary in formatUtcDateParam with an
if-based branch that selects the DATE_ONLY_RE UTC parsing path versus the
regular string parsing path, while preserving the existing Date handling,
invalid-date empty-string return, and ISO date formatting.
In `@src/stores/organization.ts`:
- Around line 360-363: Normalize the UTC start-of-day once before calculating
the billing range, then derive both last30DaysEnd and last30DaysStart from that
single anchor. Preserve the existing offsets so the request consistently covers
the intended 30-day window.
In `@src/utils/chartOptimizations.ts`:
- Around line 27-49: Update filterDailySeriesToBillingPeriod to normalize
billingStart to the UTC start of its day, derive the active billing-cycle start
using calendar-month arithmetic, and calculate currentBillingDay as the UTC
elapsed-day difference from that boundary plus one. Use the normalized
billingStart for date comparisons and billingIndex calculations so cycles
spanning months of different lengths retain every day.
🪄 Autofix
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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 047a23cf-49a1-4fac-9b4f-33925a2164b4
📒 Files selected for processing (26)
src/components/dashboard/BundleUploadsCard.vuesrc/components/dashboard/BundleUploadsChart.vuesrc/components/dashboard/DeploymentStatsCard.vuesrc/components/dashboard/DeploymentStatsChart.vuesrc/components/dashboard/DevicesStats.vuesrc/components/dashboard/LineChartStats.vuesrc/components/dashboard/UpdateStatsCard.vuesrc/components/dashboard/UpdateStatsChart.vuesrc/components/dashboard/Usage.vuesrc/composables/useBuildChartConfig.tssrc/composables/useOrgBillingCycleChart.tssrc/services/buildCharts.tssrc/services/chartDataService.tssrc/services/chartTodayLine.tssrc/services/chartTooltip.tssrc/services/conversion.tssrc/services/dailyChartProps.tssrc/services/dashboardChartRegister.tssrc/services/date.tssrc/services/supabase.tssrc/stores/main.tssrc/stores/organization.tssrc/utils/chartOptimizations.tssrc/utils/minDelay.tstests/dashboard-date-range.unit.test.tstests/date.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
| import { createChartLegendItems } from './chartLegend' | ||
| import ChartLegend from './ChartLegend.vue' |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Use the frontend alias for all new src/ imports.
src/components/dashboard/BundleUploadsChart.vue#L16-L17: Replace./chartLegendand./ChartLegend.vuewith their~/components/dashboard/...paths.src/components/dashboard/DeploymentStatsChart.vue#L16-L17: Replace./chartLegendand./ChartLegend.vuewith their~/components/dashboard/...paths.src/components/dashboard/UpdateStatsChart.vue#L17-L17: Replace../../services/chartTooltipwith~/services/chartTooltip.
As per coding guidelines, import using the ~/ alias for the src/ directory in frontend code instead of relative paths.
📍 Affects 3 files
src/components/dashboard/BundleUploadsChart.vue#L16-L17(this comment)src/components/dashboard/DeploymentStatsChart.vue#L16-L17src/components/dashboard/UpdateStatsChart.vue#L17-L17
🤖 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/components/dashboard/BundleUploadsChart.vue` around lines 16 - 17,
Replace the relative imports in src/components/dashboard/BundleUploadsChart.vue
lines 16-17 and src/components/dashboard/DeploymentStatsChart.vue lines 16-17
with ~/components/dashboard/... alias paths for createChartLegendItems and
ChartLegend. Replace the relative chartTooltip import in
src/components/dashboard/UpdateStatsChart.vue line 17 with
~/services/chartTooltip.
Source: Coding guidelines
| export function filterDailySeriesToBillingPeriod(fullData: number[], last30DaysStart: Date, billingStart: Date) { | ||
| const currentDate = normalizeToUtcStartOfDay() | ||
|
|
||
| let currentBillingDay: number | ||
| if (billingStart.getUTCDate() === 1) { | ||
| currentBillingDay = currentDate.getUTCDate() | ||
| } | ||
| else { | ||
| const billingStartDay = billingStart.getUTCDate() | ||
| const daysInMonth = new Date(Date.UTC(currentDate.getUTCFullYear(), currentDate.getUTCMonth() + 1, 0)).getUTCDate() | ||
| currentBillingDay = (currentDate.getUTCDate() - billingStartDay + 1 + daysInMonth) % daysInMonth | ||
| if (currentBillingDay === 0) | ||
| currentBillingDay = daysInMonth | ||
| } | ||
|
|
||
| const billingData = Array.from({ length: currentBillingDay }).fill(0) as number[] | ||
| for (let i = 0; i < 30; i++) { | ||
| const dataDate = addUtcDays(last30DaysStart, i) | ||
| if (dataDate >= billingStart && dataDate <= currentDate) { | ||
| const billingIndex = Math.floor((dataDate.getTime() - billingStart.getTime()) / (1000 * 60 * 60 * 24)) | ||
| if (billingIndex >= 0 && billingIndex < currentBillingDay) | ||
| billingData[billingIndex] = fullData[i] | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Calculate the billing-day count from the active cycle boundary.
The day-of-month formula fails when the cycle crosses months with different lengths. For a cycle starting March 30 and a current date of April 1, Lines 36-39 calculate 2. The cycle contains three UTC days. Line 47 then drops the April 1 value at billing index 2.
Derive the active billing-cycle start with calendar-month arithmetic. Then calculate currentBillingDay from the UTC elapsed-day difference plus one. Normalize billingStart in this helper before comparisons.
🤖 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/utils/chartOptimizations.ts` around lines 27 - 49, Update
filterDailySeriesToBillingPeriod to normalize billingStart to the UTC start of
its day, derive the active billing-cycle start using calendar-month arithmetic,
and calculate currentBillingDay as the UTC elapsed-day difference from that
boundary plus one. Use the normalized billingStart for date comparisons and
billingIndex calculations so cycles spanning months of different lengths retain
every day.
There was a problem hiding this comment.
5 issues found across 26 files
Confidence score: 3/5
- In
src/components/dashboard/LineChartStats.vue, the chart now mixes UTC day boundaries with localgetCurrentDayMonth()inaccumulateData/per-app reduction, so cumulative cutoff and “today” indexing can drift by timezone and show shifted data windows—standardize all day-boundary math on one timezone basis and add cross-timezone coverage. - In
src/services/chartTooltip.ts(withUpdateStatsChartconsumers), a UTC-midnight click date is still resolved through local day derivation, so clicking a day can open logs for the wrong date for some users—normalize the click context/day-key mapping end-to-end (UTC or local, but consistently). - In
src/services/buildCharts.ts, changing the upper bound from end-of-day tonew Date()can alter created_at filtering near day boundaries and produce inconsistent chart ranges versus prior behavior—confirm intended semantics and either restore explicit end-of-day handling or update bounds logic/tests accordingly. src/composables/useBuildChartConfig.tsandsrc/utils/minDelay.tsleave deduplication only partially applied (inline today-line config and repeated spinner-delay logic), which increases drift risk across views over time—route existing call sites through the shared helpers to keep behavior and styling consistent.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/services/chartTooltip.ts">
<violation number="1" location="src/services/chartTooltip.ts:64">
P2: Clicking an update-chart day can open logs for a different day depending on the viewer's timezone. The UTC-midnight `Date` introduced here is passed through `clickContext`, but `UpdateStatsChart` still derives local-day bounds; the click path should preserve the UTC calendar day when constructing the log query (or pass a calendar-day-safe local `Date` separately from the UTC tooltip date).</violation>
</file>
<file name="src/utils/minDelay.ts">
<violation number="1" location="src/utils/minDelay.ts:2">
P3: This helper duplicates the identical `elapsed < 300 → setTimeout` spinner-delay block already inlined in useBuildCardStats.ts, Usage.vue, and UpdateStatsCard.vue. Since the PR's goal is deduplication, migrate those three inline spots to call `ensureMinDelay(startTime)`; otherwise refreshData/usage/update paths keep two parallel implementations of the same logic.</violation>
</file>
<file name="src/components/dashboard/LineChartStats.vue">
<violation number="1" location="src/components/dashboard/LineChartStats.vue:48">
P1: Cumulative usage can still shift its cutoff by the browser timezone: this line switches the chart cycle/today index to UTC, but `accumulateData` and the per-app reducer continue using local `getCurrentDayMonth()`. Reusing the composable’s UTC `transformDailySeries` for both total and per-app data would keep the cumulative today bucket aligned with the UTC marker.</violation>
</file>
<file name="src/composables/useBuildChartConfig.ts">
<violation number="1" location="src/composables/useBuildChartConfig.ts:7">
P3: The new `createTodayLineOptions` helper centralizes the today-line styling, but `useBuildChartConfig` still returns the same inline today-line config with the identical hardcoded colors rather than delegating to the helper. This leaves two copies of the today-line look in the codebase, defeating the PR's dedup goal: future styling changes have to be updated in both places. Consider calling `createTodayLineOptions` here too (it already replicates the same `useBillingPeriod`/index guard).</violation>
</file>
<file name="src/services/buildCharts.ts">
<violation number="1" location="src/services/buildCharts.ts:62">
P3: The build chart's created_at upper bound changed behavior: the old `endOfToday.setHours(23,59,59,999)` (end of local day) was removed, so `endISO` is now the instant the window is built (`new Date()`), while `todayStart` moved to UTC midnight. This makes `endISO` time-of-fetch dependent and no longer 'end of today' as the `BuildChartWindow.endISO` comment documents, and it isn't aligned with the UTC day boundary that `todayStart` now uses. Consider restoring the intent by normalizing to the UTC end of day (e.g. `endOfToday.setUTCHours(23,59,59,999)`) or updating the comment/name to reflect that `endISO` is now 'fetch time' rather than end of today.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| }) | ||
| return mapping | ||
| }) | ||
| const { cycleStart, cycleEnd, todayLimit } = useDashboardDailyChartCycle(() => props.useBillingPeriod) |
There was a problem hiding this comment.
P1: Cumulative usage can still shift its cutoff by the browser timezone: this line switches the chart cycle/today index to UTC, but accumulateData and the per-app reducer continue using local getCurrentDayMonth(). Reusing the composable’s UTC transformDailySeries for both total and per-app data would keep the cumulative today bucket aligned with the UTC marker.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/dashboard/LineChartStats.vue, line 48:
<comment>Cumulative usage can still shift its cutoff by the browser timezone: this line switches the chart cycle/today index to UTC, but `accumulateData` and the per-app reducer continue using local `getCurrentDayMonth()`. Reusing the composable’s UTC `transformDailySeries` for both total and per-app data would keep the cumulative today bucket aligned with the UTC marker.</comment>
<file context>
@@ -49,48 +39,29 @@ const props = defineProps({
- })
- return mapping
-})
+const { cycleStart, cycleEnd, todayLimit } = useDashboardDailyChartCycle(() => props.useBillingPeriod)
-// Click handler for tooltip items - navigates to app detail page
</file context>
| @@ -0,0 +1,6 @@ | |||
| /** Ensure at least `minMs` elapsed since `startTime` before continuing (spinner UX). */ | |||
| export async function ensureMinDelay(startTime: number, minMs = 300) { | |||
There was a problem hiding this comment.
P3: This helper duplicates the identical elapsed < 300 → setTimeout spinner-delay block already inlined in useBuildCardStats.ts, Usage.vue, and UpdateStatsCard.vue. Since the PR's goal is deduplication, migrate those three inline spots to call ensureMinDelay(startTime); otherwise refreshData/usage/update paths keep two parallel implementations of the same logic.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/minDelay.ts, line 2:
<comment>This helper duplicates the identical `elapsed < 300 → setTimeout` spinner-delay block already inlined in useBuildCardStats.ts, Usage.vue, and UpdateStatsCard.vue. Since the PR's goal is deduplication, migrate those three inline spots to call `ensureMinDelay(startTime)`; otherwise refreshData/usage/update paths keep two parallel implementations of the same logic.</comment>
<file context>
@@ -0,0 +1,6 @@
+/** Ensure at least `minMs` elapsed since `startTime` before continuing (spinner UX). */
+export async function ensureMinDelay(startTime: number, minMs = 300) {
+ const elapsed = Date.now() - startTime
+ if (elapsed < minMs)
</file context>
| import { createLegendConfig, createStackedChartScales } from '~/services/chartConfig' | ||
| import { createTooltipConfig } from '~/services/chartTooltip' | ||
| import { generateMonthDays } from '~/services/date' | ||
| import { generateMonthDays, normalizeToUtcStartOfDay } from '~/services/date' |
There was a problem hiding this comment.
P3: The new createTodayLineOptions helper centralizes the today-line styling, but useBuildChartConfig still returns the same inline today-line config with the identical hardcoded colors rather than delegating to the helper. This leaves two copies of the today-line look in the codebase, defeating the PR's dedup goal: future styling changes have to be updated in both places. Consider calling createTodayLineOptions here too (it already replicates the same useBillingPeriod/index guard).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/composables/useBuildChartConfig.ts, line 7:
<comment>The new `createTodayLineOptions` helper centralizes the today-line styling, but `useBuildChartConfig` still returns the same inline today-line config with the identical hardcoded colors rather than delegating to the helper. This leaves two copies of the today-line look in the codebase, defeating the PR's dedup goal: future styling changes have to be updated in both places. Consider calling `createTodayLineOptions` here too (it already replicates the same `useBillingPeriod`/index guard).</comment>
<file context>
@@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
import { createLegendConfig, createStackedChartScales } from '~/services/chartConfig'
import { createTooltipConfig } from '~/services/chartTooltip'
-import { generateMonthDays } from '~/services/date'
+import { generateMonthDays, normalizeToUtcStartOfDay } from '~/services/date'
import { useOrganizationStore } from '~/stores/organization'
</file context>
| export function getBuildChartWindow(useBillingPeriod: boolean, subscriptionStart?: string | null): BuildChartWindow { | ||
| const todayStart = new Date() | ||
| todayStart.setHours(0, 0, 0, 0) | ||
| const todayStart = normalizeToUtcStartOfDay() |
There was a problem hiding this comment.
P3: The build chart's created_at upper bound changed behavior: the old endOfToday.setHours(23,59,59,999) (end of local day) was removed, so endISO is now the instant the window is built (new Date()), while todayStart moved to UTC midnight. This makes endISO time-of-fetch dependent and no longer 'end of today' as the BuildChartWindow.endISO comment documents, and it isn't aligned with the UTC day boundary that todayStart now uses. Consider restoring the intent by normalizing to the UTC end of day (e.g. endOfToday.setUTCHours(23,59,59,999)) or updating the comment/name to reflect that endISO is now 'fetch time' rather than end of today.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/services/buildCharts.ts, line 62:
<comment>The build chart's created_at upper bound changed behavior: the old `endOfToday.setHours(23,59,59,999)` (end of local day) was removed, so `endISO` is now the instant the window is built (`new Date()`), while `todayStart` moved to UTC midnight. This makes `endISO` time-of-fetch dependent and no longer 'end of today' as the `BuildChartWindow.endISO` comment documents, and it isn't aligned with the UTC day boundary that `todayStart` now uses. Consider restoring the intent by normalizing to the UTC end of day (e.g. `endOfToday.setUTCHours(23,59,59,999)`) or updating the comment/name to reflect that `endISO` is now 'fetch time' rather than end of today.</comment>
<file context>
@@ -57,25 +59,22 @@ export interface BuildChartWindow {
export function getBuildChartWindow(useBillingPeriod: boolean, subscriptionStart?: string | null): BuildChartWindow {
- const todayStart = new Date()
- todayStart.setHours(0, 0, 0, 0)
+ const todayStart = normalizeToUtcStartOfDay()
const endOfToday = new Date()
- endOfToday.setHours(23, 59, 59, 999)
</file context>
Include today's deployments with an exclusive UTC upper bound, keep billing-cycle math on elapsed UTC days, and preserve UTC calendar days for chart click log navigation and date-only billing boundaries. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f38c1f75-ed55-4c5f-85cd-fefb8b73bbf8) |
There was a problem hiding this comment.
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/components/dashboard/BundleUploadsCard.vue`:
- Around line 181-186: Update the raw-row caching used by the series-window
query in BundleUploadsCard.vue (lines 181-186) and DeploymentStatsCard.vue
(lines 200-201) so each cache entry is keyed by organization plus the resolved
UTC start and exclusive-end bounds derived from useBillingPeriod, seriesStart,
and exclusiveEnd; ensure calculateStats(false) never reuses rows from a
different window.
In `@src/components/dashboard/LineChartStats.vue`:
- Line 14: In LineChartStats, replace both unresolved getDaysInCurrentMonth
calls with the imported getDaysInCurrentUtcMonth helper, preserving the existing
call arguments and behavior.
In `@src/services/date.ts`:
- Around line 206-213: Update formatUtcDateParam to validate date-only strings
against a real calendar date before constructing the UTC Date, so values such as
2026-02-30 follow the existing empty invalid-date fallback instead of being
normalized. Add a regression test covering 2026-02-30 and preserve valid
date-only formatting.
In `@tests/date.unit.test.ts`:
- Around line 143-145: Update the zone-less ISO regression test for
formatUtcDateParam to run under TZ=America/Los_Angeles, use the
timezone-sensitive input 2026-08-07T23:30:00, and continue expecting 2026-08-07.
Configure the test command or runner environment rather than mutating
process.env.TZ within the suite.
🪄 Autofix
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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: eeca0302-617a-4478-8383-3a1b242e6532
📒 Files selected for processing (15)
src/components/dashboard/BundleUploadsCard.vuesrc/components/dashboard/BundleUploadsChart.vuesrc/components/dashboard/DeploymentStatsCard.vuesrc/components/dashboard/DeploymentStatsChart.vuesrc/components/dashboard/LineChartStats.vuesrc/components/dashboard/UpdateStatsChart.vuesrc/components/dashboard/Usage.vuesrc/composables/useOrgBillingCycleChart.tssrc/services/dailyChartProps.tssrc/services/dashboardChartRegister.tssrc/services/date.tssrc/stores/organization.tssrc/utils/chartOptimizations.tstests/chart-optimizations.unit.test.tstests/date.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
| // Fetch series window (billing cycle or last 30 UTC days) | ||
| const query = useSupabase() | ||
| .from('app_versions') | ||
| .select('created_at, app_id, deleted, r2_path, external_url, user_id') | ||
| .gte('created_at', last30DaysStart.toISOString()) | ||
| .lte('created_at', last30DaysEnd.toISOString()) | ||
| .lt('created_at', last30DaysEnd.toISOString()) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Key cached raw data by the resolved series window.
Both components cache raw rows only by organization, but the query now depends on useBillingPeriod, seriesStart, and exclusiveEnd. When a user switches views, calculateStats(false) can reuse rows from a different window. This produces missing billing-cycle history or stale out-of-range data.
src/components/dashboard/BundleUploadsCard.vue#L181-L186: Include the resolved UTC bounds in the cache key, or cache a verified superset before filtering.src/components/dashboard/DeploymentStatsCard.vue#L200-L201: Include the resolved UTC bounds in the cache key, or cache a verified superset before filtering.
📍 Affects 2 files
src/components/dashboard/BundleUploadsCard.vue#L181-L186(this comment)src/components/dashboard/DeploymentStatsCard.vue#L200-L201
🤖 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/components/dashboard/BundleUploadsCard.vue` around lines 181 - 186,
Update the raw-row caching used by the series-window query in
BundleUploadsCard.vue (lines 181-186) and DeploymentStatsCard.vue (lines
200-201) so each cache entry is keyed by organization plus the resolved UTC
start and exclusive-end bounds derived from useBillingPeriod, seriesStart, and
exclusiveEnd; ensure calculateStats(false) never reuses rows from a different
window.
| export function formatUtcDateParam(date: Date | string = new Date()) { | ||
| let parsed: Date | ||
| if (typeof date === 'string' && DATE_ONLY_RE.test(date)) | ||
| parsed = new Date(`${date}T00:00:00.000Z`) | ||
| else if (typeof date === 'string' && ZONELESS_ISO_DATETIME_RE.test(date)) | ||
| parsed = new Date(`${date}Z`) | ||
| else | ||
| parsed = new Date(date) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject calendar-invalid date-only parameters.
new Date('2026-02-30T00:00:00.000Z') normalizes to March 2. This function returns 2026-03-02 instead of its empty invalid-date fallback. Validate date-only fields before formatting. Add a regression test for 2026-02-30.
Proposed fix
export function formatUtcDateParam(date: Date | string = new Date()) {
+ if (typeof date === 'string' && DATE_ONLY_RE.test(date)) {
+ const parsed = parseUtcRangeBoundary(date)
+ return parsed ? parsed.toISOString().slice(0, 10) : ''
+ }
+
let parsed: Date
- if (typeof date === 'string' && DATE_ONLY_RE.test(date))
- parsed = new Date(`${date}T00:00:00.000Z`)
- else if (typeof date === 'string' && ZONELESS_ISO_DATETIME_RE.test(date))
+ if (typeof date === 'string' && ZONELESS_ISO_DATETIME_RE.test(date))
parsed = new Date(`${date}Z`)
else
parsed = new Date(date)📝 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.
| export function formatUtcDateParam(date: Date | string = new Date()) { | |
| let parsed: Date | |
| if (typeof date === 'string' && DATE_ONLY_RE.test(date)) | |
| parsed = new Date(`${date}T00:00:00.000Z`) | |
| else if (typeof date === 'string' && ZONELESS_ISO_DATETIME_RE.test(date)) | |
| parsed = new Date(`${date}Z`) | |
| else | |
| parsed = new Date(date) | |
| export function formatUtcDateParam(date: Date | string = new Date()) { | |
| if (typeof date === 'string' && DATE_ONLY_RE.test(date)) { | |
| const parsed = parseUtcRangeBoundary(date) | |
| return parsed ? parsed.toISOString().slice(0, 10) : '' | |
| } | |
| let parsed: Date | |
| if (typeof date === 'string' && ZONELESS_ISO_DATETIME_RE.test(date)) | |
| parsed = new Date(`${date}Z`) | |
| else | |
| parsed = new Date(date) |
🤖 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/services/date.ts` around lines 206 - 213, Update formatUtcDateParam to
validate date-only strings against a real calendar date before constructing the
UTC Date, so values such as 2026-02-30 follow the existing empty invalid-date
fallback instead of being normalized. Add a regression test covering 2026-02-30
and preserve valid date-only formatting.
| it('treats zone-less ISO datetimes as UTC in formatUtcDateParam', () => { | ||
| expect(formatUtcDateParam('2026-08-07T15:30:00')).toBe('2026-08-07') | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -HI -t f '^(package\.json|vitest\.config\..*|vite\.config\..*)$' . \
-x sh -c 'printf "\n### %s\n" "$1"; rg -n -C 3 "vitest|test|TZ" "$1" || true' sh {}Repository: Cap-go/capgo.app
Length of output: 28473
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '### tests/date.unit.test.ts'
sed -n '1,190p' tests/date.unit.test.ts
printf '%s\n' '### date helpers and usages'
rg -n -C 5 "formatUtcDateParam|formatUtc|DateParam" src tests --glob '*.{ts,tsx,vue}' || true
printf '%s\n' '### timezone-sensitive runtime probe'
TZ=America/Los_Angeles node - <<'JS'
const input = '2026-08-07T23:30:00'
const local = new Date(input)
const utc = new Date(`${input}Z`)
const day = date => date.toISOString().slice(0, 10)
console.log(JSON.stringify({
timezone: process.env.TZ,
localISO: local.toISOString(),
utcISO: utc.toISOString(),
localDay: day(local),
utcDay: day(utc),
}))
JSRepository: Cap-go/capgo.app
Length of output: 24651
Make the zone-less ISO regression test timezone-sensitive.
The current 15:30:00 input does not distinguish UTC parsing from local parsing. Run an equivalent assertion with TZ=America/Los_Angeles and 2026-08-07T23:30:00, expecting 2026-08-07. Do not mutate process.env.TZ inside this suite.
🤖 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 `@tests/date.unit.test.ts` around lines 143 - 145, Update the zone-less ISO
regression test for formatUtcDateParam to run under TZ=America/Los_Angeles, use
the timezone-sensitive input 2026-08-07T23:30:00, and continue expecting
2026-08-07. Configure the test command or runner environment rather than
mutating process.env.TZ within the suite.
Source: Coding guidelines
There was a problem hiding this comment.
3 issues found across 15 files (changes from recent commits).
Confidence score: 3/5
- In
src/components/dashboard/DeploymentStatsCard.vue, cache entries are reused across different time windows, so moving from 30-day to longer billing cycles can hide early-cycle deployments and underreport activity — include the queried date range in the cache key or invalidate when the window changes. - In
src/components/dashboard/BundleUploadsCard.vue, switching from billing-cycle to 30-day mode can reuse broader-window data keyed only by org, which can omit expected earlier days in the new view and mislead trend interpretation — key/invalidate cached rows by both org and active series window. - In
src/utils/chartOptimizations.ts, rows fetched for one window are reindexed with a differentseriesStart, which can shift day buckets and add/drop edge days, producing visibly incorrect daily counts — store and validate cached start/end bounds before remapping chart series.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/components/dashboard/DeploymentStatsCard.vue">
<violation number="1" location="src/components/dashboard/DeploymentStatsCard.vue:140">
P1: Switching from the default 30-day view to a billing cycle longer than 30 days omits early-cycle deployments because the per-org cache is reused despite its narrower query window. Store/query cache coverage with the range or force a refetch when billing mode needs an earlier `seriesStart`.</violation>
</file>
<file name="src/components/dashboard/BundleUploadsCard.vue">
<violation number="1" location="src/components/dashboard/BundleUploadsCard.vue:126">
P1: Switching from billing-cycle to 30-day mode can omit earlier days because cache entries are keyed only by org although this new window changes the query range. Key or invalidate cached rows by series window (and app set), or force a refetch when `useBillingPeriod` changes.</violation>
</file>
<file name="src/utils/chartOptimizations.ts">
<violation number="1" location="src/utils/chartOptimizations.ts:52">
P1: Switching billing mode reuses rows fetched for the prior window but reindexes them using this new `seriesStart`, shifting daily counts (and dropping/adding edge days). Cache the fetched window with its start/end or force a refetch when `useBillingPeriod` changes.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const billingStart = new Date(targetOrganization.subscription_start ?? new Date()) | ||
| billingStart.setHours(0, 0, 0, 0) | ||
| const billingStart = normalizeToUtcStartOfDay(new Date(targetOrganization.subscription_start ?? new Date())) | ||
| const { seriesStart: last30DaysStart, exclusiveEnd, dayCount } = resolveDashboardDailySeriesWindow( |
There was a problem hiding this comment.
P1: Switching from the default 30-day view to a billing cycle longer than 30 days omits early-cycle deployments because the per-org cache is reused despite its narrower query window. Store/query cache coverage with the range or force a refetch when billing mode needs an earlier seriesStart.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/dashboard/DeploymentStatsCard.vue, line 140:
<comment>Switching from the default 30-day view to a billing cycle longer than 30 days omits early-cycle deployments because the per-org cache is reused despite its narrower query window. Store/query cache coverage with the range or force a refetch when billing mode needs an earlier `seriesStart`.</comment>
<file context>
@@ -135,15 +135,16 @@ async function calculateStats(forceRefetch = false) {
-
// Get billing period dates for filtering
const billingStart = normalizeToUtcStartOfDay(new Date(targetOrganization.subscription_start ?? new Date()))
+ const { seriesStart: last30DaysStart, exclusiveEnd, dayCount } = resolveDashboardDailySeriesWindow(
+ props.useBillingPeriod,
+ billingStart,
</file context>
| billingStart.setHours(0, 0, 0, 0) | ||
| const billingStart = normalizeToUtcStartOfDay(new Date(organizationStore.currentOrganization?.subscription_start ?? new Date())) | ||
| const { seriesStart: last30DaysStart, exclusiveEnd: last30DaysEnd, dayCount } = resolveDashboardDailySeriesWindow( | ||
| props.useBillingPeriod, |
There was a problem hiding this comment.
P1: Switching from billing-cycle to 30-day mode can omit earlier days because cache entries are keyed only by org although this new window changes the query range. Key or invalidate cached rows by series window (and app set), or force a refetch when useBillingPeriod changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/dashboard/BundleUploadsCard.vue, line 126:
<comment>Switching from billing-cycle to 30-day mode can omit earlier days because cache entries are keyed only by org although this new window changes the query range. Key or invalidate cached rows by series window (and app set), or force a refetch when `useBillingPeriod` changes.</comment>
<file context>
@@ -120,12 +120,12 @@ async function calculateStats(forceRefetch = false) {
// Get billing period dates for filtering
const billingStart = normalizeToUtcStartOfDay(new Date(organizationStore.currentOrganization?.subscription_start ?? new Date()))
+ const { seriesStart: last30DaysStart, exclusiveEnd: last30DaysEnd, dayCount } = resolveDashboardDailySeriesWindow(
+ props.useBillingPeriod,
+ billingStart,
+ )
</file context>
| if (useBillingPeriod && !Number.isNaN(cycleStart.getTime())) { | ||
| const elapsedDays = Math.floor((todayUtc.getTime() - cycleStart.getTime()) / DAY_MS) | ||
| if (elapsedDays >= 0 && elapsedDays <= 366) | ||
| seriesStart = cycleStart |
There was a problem hiding this comment.
P1: Switching billing mode reuses rows fetched for the prior window but reindexes them using this new seriesStart, shifting daily counts (and dropping/adding edge days). Cache the fetched window with its start/end or force a refetch when useBillingPeriod changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/chartOptimizations.ts, line 52:
<comment>Switching billing mode reuses rows fetched for the prior window but reindexes them using this new `seriesStart`, shifting daily counts (and dropping/adding edge days). Cache the fetched window with its start/end or force a refetch when `useBillingPeriod` changes.</comment>
<file context>
@@ -20,33 +22,62 @@ export function incrementArrayValue(arr: (number | undefined)[], index: number,
+ if (useBillingPeriod && !Number.isNaN(cycleStart.getTime())) {
+ const elapsedDays = Math.floor((todayUtc.getTime() - cycleStart.getTime()) / DAY_MS)
+ if (elapsedDays >= 0 && elapsedDays <= 366)
+ seriesStart = cycleStart
}
</file context>
Import was renamed but two call sites still referenced getDaysInCurrentMonth, breaking vue-tsc on Lint and typecheck. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_aead4611-69cf-4114-a8c7-721bfefff9ba) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ccc84808-3ddd-44cb-a2d4-e24f2b58a032) |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/dashboard/LineChartStats.vue (1)
32-32: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftUse the billing-cycle label count for every chart series.
When
useBillingPeriodis enabled,monthdays()usescycleStartandcycleEnd, but projection and annotation calculations still use the calendar month, while cumulative filling uses the local calendar day. UsetransformDailySeriesor a cycle-relative UTC index. Size all arrays and annotation positions fromlabels.length. Add a test for a cycle that starts mid-month.🤖 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/components/dashboard/LineChartStats.vue` at line 32, Update the chart data initialization and related projection, annotation, and cumulative-fill calculations in LineChartStats.vue to use the billing-cycle label count and cycle-relative UTC indices whenever useBillingPeriod is enabled, rather than calendar-month or local-day values. Derive all series array sizes and annotation positions from labels.length, reusing transformDailySeries where appropriate, and add coverage for a billing cycle that starts mid-month.
🤖 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.
Outside diff comments:
In `@src/components/dashboard/LineChartStats.vue`:
- Line 32: Update the chart data initialization and related projection,
annotation, and cumulative-fill calculations in LineChartStats.vue to use the
billing-cycle label count and cycle-relative UTC indices whenever
useBillingPeriod is enabled, rather than calendar-month or local-day values.
Derive all series array sizes and annotation positions from labels.length,
reusing transformDailySeries where appropriate, and add coverage for a billing
cycle that starts mid-month.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 82786bf5-cff5-4444-ba45-50ba0766bddb
📒 Files selected for processing (1)
src/components/dashboard/LineChartStats.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
|



Summary (AI generated)
Legendregistration, reactive cycle ends for expired subscriptions.LineChartStatstypecheck: remaininggetDaysInCurrentMonthcall sites updated togetDaysInCurrentUtcMonth.Motivation (AI generated)
Brazil customers saw today’s device/stats after a release; Capgo (Europe) viewing the same org only saw through yesterday. Local midnight +
toISOString()shifted the UTC day for positive offsets. Review bots also flagged remaining UTC edge cases that could drop today’s deployments or mis-size billing charts.Business Impact (AI generated)
Dashboard “today” and billing-period charts stay consistent worldwide for the same org, avoiding false “missing data” support issues around timezone boundaries.
Test Plan (AI generated)
bun run typecheck:frontendpasses aftergetDaysInCurrentUtcMonthfixfrom/toend on the same UTC dayGenerated with AI
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by CodeRabbit
Bug Fixes
Refactor