docs(07.1-04): codebase-wide tz audit + Plan 05 dispatch
- 81 leak callsites across 39 files identified — Plan 05 closes them - 47 number-format callsites (not dates) excluded - 7 server-side LLM prompt callsites out of scope - 1 deliberate-UTC callsite (engagement sparkline) leave as-is - 5 explicit-zone callsites (Plan 04 already migrated) - Plan 05 dispatch: NEEDED — file paths enumerated for Plan 05's files_modified
This commit is contained in:
parent
14f4da3483
commit
dfd0a9f2b2
1 changed files with 183 additions and 0 deletions
|
|
@ -0,0 +1,183 @@
|
|||
# Phase 7.1 — Codebase-wide tz audit (Plan 04 Task 3)
|
||||
|
||||
Discovery date: 2026-05-07
|
||||
Excluded: `app/mobile/finance/page.tsx`,
|
||||
`app/mobile/tickets/[id]/page.tsx`,
|
||||
`components/ui/calendar.tsx`,
|
||||
`app/admin/data-browser/time-entries/page.tsx.backup` (orphaned backup file),
|
||||
`node_modules/**`
|
||||
|
||||
Discovery grep:
|
||||
|
||||
grep -rEn "Intl\.DateTimeFormat|\.toLocaleDateString\(|\.toLocaleTimeString\(|\.toLocaleString\(" \
|
||||
app components lib/hooks
|
||||
|
||||
## Leak callsites (must migrate via Plan 05)
|
||||
|
||||
Each row is a `Date.toLocale*` callsite with NO `timeZone:` option in the same call.
|
||||
These render in the browser's local zone — the bug TZ-02 is patching.
|
||||
|
||||
| File | Line | Snippet | Notes |
|
||||
|------|------|---------|-------|
|
||||
| app/engagement/profile/page.tsx | 141 | `d.toLocaleDateString('en-US', { month: 'short' })` | sparkline label |
|
||||
| app/engagement/profile/page.tsx | 264 | `new Date(m + '-02').toLocaleDateString('en-US', { month: 'short', year: 'numeric' })` | month label |
|
||||
| app/engagement/page.tsx | 842 | `new Date(user.lastActivity).toLocaleDateString()` | last activity |
|
||||
| app/engagement/page.tsx | 1010 | `new Date(snap.last_activity_date).toLocaleDateString()` | snapshot last activity |
|
||||
| app/engagement/page.tsx | 1106 | `d.toLocaleDateString()` + `d.toLocaleTimeString([], …)` | call timestamp (2 calls on one line) |
|
||||
| app/engagement/page.tsx | 1225 | `new Date(call.startTime).toLocaleDateString()` | call start |
|
||||
| app/engagement/page.tsx | 1253 | `d.toLocaleDateString()` + `d.toLocaleTimeString([], …)` | call timestamp (2 calls on one line) |
|
||||
| app/admin/sync/duo/page.tsx | 117 | `new Date(d).toLocaleString()` | sync timestamp |
|
||||
| app/admin/sync/datto-rmm/page.tsx | 23 | `new Date(d).toLocaleString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })` | sync timestamp |
|
||||
| app/admin/sync/veeam/page.tsx | 26 | `new Date(d).toLocaleString(undefined, { … })` | sync timestamp |
|
||||
| app/admin/sync/itglue/page.tsx | 24 | `new Date(d).toLocaleString(undefined, { … })` | sync timestamp |
|
||||
| app/admin/sync/mimecast/page.tsx | 28 | `new Date(d).toLocaleString(undefined, { … })` | sync timestamp |
|
||||
| app/admin/sync/mimecast/page.tsx | 651 | `new Date(message.dateReceived).toLocaleString()` | message timestamp |
|
||||
| app/admin/sync/mimecast/page.tsx | 924 | `new Date(m.dateReceived).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })` | message timestamp |
|
||||
| app/admin/sync/mimecast/page.tsx | 1239 | `new Date(message.received).toLocaleString()` | message timestamp |
|
||||
| app/admin/sync/mimecast/page.tsx | 1405 | `new Date(m.receivedDateTime).toLocaleString(undefined, { … })` | message timestamp |
|
||||
| app/admin/sync/mimecast/page.tsx | 1840 | `new Date(m.received).toLocaleString(undefined, { … })` | message timestamp |
|
||||
| app/admin/sync/sentinelone/page.tsx | 15 | `new Date(d).toLocaleString()` | sync timestamp |
|
||||
| app/admin/zabbix-wan/page.tsx | 212 | `new Date(ts).toLocaleString()` | last synced |
|
||||
| app/admin/zabbix-wan/page.tsx | 213 | `new Date(ts).toLocaleString()` | last synced |
|
||||
| app/admin/rmm-overshell/page.tsx | 142 | `new Date(settings.discoveredAt).toLocaleString()` | discovered timestamp |
|
||||
| app/admin/rmm-overshell/page.tsx | 199 | `new Date(settings.logliftDiscoveredAt).toLocaleString()` | discovered timestamp |
|
||||
| app/admin/rmm-overshell/page.tsx | 265 | `new Date(e.queuedAt).toLocaleString()` | queued timestamp |
|
||||
| app/admin/itglue-writes/page.tsx | 144 | `new Date(w.performed_at).toLocaleString()` | performed timestamp |
|
||||
| app/admin/data-browser/tasks/page.tsx | 100 | `new Date(value).toLocaleDateString()` | DataTable column render |
|
||||
| app/admin/data-browser/projects/page.tsx | 88 | `new Date(value).toLocaleDateString()` | DataTable column render |
|
||||
| app/admin/data-browser/ticket-notes/page.tsx | 86 | `new Date(v).toLocaleDateString()` | DataTable column render |
|
||||
| app/admin/data-browser/contracts/page.tsx | 88 | `new Date(value).toLocaleDateString()` | DataTable column render |
|
||||
| app/admin/data-browser/contracts/page.tsx | 96 | `new Date(value).toLocaleDateString()` | DataTable column render |
|
||||
| app/admin/data-browser/tickets/page.tsx | 103 | `new Date(value).toLocaleDateString()` | DataTable column render |
|
||||
| app/admin/data-browser/time-entries/page.tsx | 296 | `new Date(value).toLocaleDateString()` | DataTable column render |
|
||||
| app/admin/ticket-digest/page.tsx | 410 | `new Date(report.generated_at).toLocaleString()` | report timestamp |
|
||||
| app/admin/device-link-conflicts/page.tsx | 223 | `new Date(r.xref.lastSeenAt).toLocaleString()` | last seen |
|
||||
| app/admin/workflow/history/page.tsx | 192 | `new Date(exec.created_at).toLocaleString()` | execution timestamp |
|
||||
| app/admin/workflow/pipelines/[id]/page.tsx | 582 | `new Date(exec.started_at).toLocaleString()` | execution timestamp |
|
||||
| app/analyzer/itglue/applications/page.tsx | 129 | `new Date(r.latestAudit.generatedAt).toLocaleDateString()` | audit timestamp |
|
||||
| app/analyzer/itglue/applications/[id]/page.tsx | 411 | `new Date(audit.generated_at).toLocaleString()` | audit timestamp |
|
||||
| app/analyzer/itglue/applications/[id]/page.tsx | 720 | `new Date(w.performed_at).toLocaleString()` | write performed |
|
||||
| app/analyzer/itglue/applications/[id]/page.tsx | 779 | `new Date(h.generated_at).toLocaleString()` | history timestamp |
|
||||
| app/analyzer/itglue/configurations/page.tsx | 130 | `new Date(r.latestAudit.generatedAt).toLocaleDateString()` | audit timestamp |
|
||||
| app/analyzer/itglue/configurations/[id]/page.tsx | 393 | `new Date(audit.generated_at).toLocaleString()` | audit timestamp |
|
||||
| app/analyzer/itglue/configurations/[id]/page.tsx | 688 | `new Date(w.performed_at).toLocaleString()` | write performed |
|
||||
| app/analyzer/itglue/configurations/[id]/page.tsx | 741 | `new Date(h.generated_at).toLocaleString()` | history timestamp |
|
||||
| app/analyzer/itglue/sites/[companyId]/page.tsx | 212 | `new Date(e.queuedAt).toLocaleString()` | queued timestamp |
|
||||
| app/analyzer/queue/page.tsx | 103 | `new Date(a.triggeredAt).toLocaleString()` | triggered timestamp |
|
||||
| app/analyzer/ticket/[ticketNumber]/page.tsx | 143 | `new Date(a.triggeredAt).toLocaleString()` | triggered timestamp |
|
||||
| app/analyzer/tickets/page.tsx | 112 | `d.toLocaleDateString()` | helper |
|
||||
| app/analyzer/reports/page.tsx | 136 | `new Date(r.generatedAt).toLocaleString()` | report timestamp |
|
||||
| app/analyzer/reports/[id]/page.tsx | 186 | `new Date(report.generatedAt).toLocaleString()` | report timestamp |
|
||||
| app/analyzer/reports/[id]/page.tsx | 199 | `new Date(report.dateRangeActual.earliest).toLocaleDateString()` | range earliest |
|
||||
| app/analyzer/reports/[id]/page.tsx | 201 | `new Date(report.dateRangeActual.latest).toLocaleDateString()` | range latest |
|
||||
| app/dashboard/page.tsx | 152 | `new Date().toLocaleDateString(undefined, { … })` | header date |
|
||||
| app/quotes/page.tsx | 107 | `new Date(dateString).toLocaleDateString('en-US', { … })` | helper |
|
||||
| app/veeam-analysis/page.tsx | 671 | `new Date(summary.generated_at).toLocaleString()` | generated timestamp |
|
||||
| components/admin/DetailModal.tsx | 160 | `d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })` | detail row |
|
||||
| components/admin/DetailModal.tsx | 610 | `new Date(entry.entry_date).toLocaleDateString(undefined, { … })` | time-entry date |
|
||||
| components/admin/DetailModal.tsx | 656 | `new Date(note.create_date_time).toLocaleDateString(undefined, { … })` | note date |
|
||||
| components/admin/DetailModal.tsx | 658 | `new Date(note.create_date_time).toLocaleTimeString(undefined, { … })` | note time |
|
||||
| components/admin/IntegrationStatusTabs.tsx | 36 | `new Date(d).toLocaleString(undefined, { … })` | helper |
|
||||
| components/admin/SyncScheduler.tsx | 230 | `new Date(dateString).toLocaleString()` | scheduler |
|
||||
| components/admin/audit/audit-log-table.tsx | 186 | `new Date(log.timestamp).toLocaleString()` | audit log row |
|
||||
| components/admin/users/user-table.tsx | 205 | `new Date(user.created_at).toLocaleDateString()` | user row |
|
||||
| components/admin/users/user-sessions.tsx | 161 | `new Date(session.created_at).toLocaleString()` | session row |
|
||||
| components/admin/users/user-sessions.tsx | 164 | `new Date(session.expires_at).toLocaleString()` | session row |
|
||||
| components/analyzer/analysis-view.tsx | 115 | `new Date(a.triggeredAt).toLocaleString()` | analysis header |
|
||||
| components/analyzer/analysis-view.tsx | 222 | `new Date(event.timestamp).toLocaleString()` | event timestamp |
|
||||
| components/analyzer/analysis-view.tsx | 310 | `new Date(ts).toLocaleString()` | event timestamp |
|
||||
| components/analyzer/analysis-view.tsx | 401 | `new Date(a.timeline[expandedEvent].timestamp).toLocaleString()` | event timestamp |
|
||||
| components/settings/active-sessions.tsx | 118 | `new Date(session.created_at).toLocaleDateString()` | session row |
|
||||
| components/dashboard/resolution-trend.tsx | 27 | `new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })` | chart axis label |
|
||||
| components/dashboard/volume-trend.tsx | 28 | `new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })` | chart axis label |
|
||||
| components/quotes/ticket-detail-modal.tsx | 231 | `new Date(dateString).toLocaleString('en-US', { … })` | helper |
|
||||
| components/analytics/TimelineView.tsx | 133 | `new Date(groupKey + ':00:00').toLocaleString('en-US', { … })` | hour bucket |
|
||||
| components/analytics/TimelineView.tsx | 140 | `new Date(groupKey).toLocaleDateString('en-US', { … })` | day bucket |
|
||||
| components/analytics/TimelineView.tsx | 148 | `new Date(groupKey + '-01').toLocaleDateString('en-US', { … })` | month bucket |
|
||||
| components/analytics/TimelineView.tsx | 303 | `new Date(event.timestamp).toLocaleTimeString()` | event time |
|
||||
| components/analytics/ScoreCard.tsx | 486 | `analysis.dateRange.latest.toLocaleDateString()` | range latest |
|
||||
| components/configuration-items/auvik-tab.tsx | 26 | `new Date(dateString).toLocaleString()` | helper |
|
||||
| components/configuration-items/addigy-tab.tsx | 107 | `new Date(device['Last Check In']).toLocaleString()` | device check-in |
|
||||
| components/configuration-items/addigy-tab.tsx | 327 | `new Date(device['Warranty Expiration Date']).toLocaleDateString()` | warranty date |
|
||||
| components/status/activity-sparkline.tsx | 30 | `new Date(iso).toLocaleTimeString(undefined, { … })` | tooltip label |
|
||||
| components/backup/compliance-detail-table.tsx | 170 | `new Date(contract.start_date).toLocaleDateString()` | contract start |
|
||||
| components/backup/compliance-detail-table.tsx | 173 | `new Date(contract.end_date).toLocaleDateString()` | contract end |
|
||||
| components/backup/company-backup-detail.tsx | 48 | `new Date(dateStr).toLocaleString()` | helper |
|
||||
|
||||
## Explicit-zone callsites (no migration)
|
||||
|
||||
| File | Line | Snippet |
|
||||
|------|------|---------|
|
||||
| app/mobile/finance/page.tsx | 44, 77, 375 | already migrated by Plan 04 Task 2 (excluded from this audit) |
|
||||
| app/mobile/tickets/[id]/page.tsx | 50 | already migrated by Plan 04 Task 2 (excluded from this audit) |
|
||||
| lib/hooks/use-user-timezone.ts | 60 | helper threading caller-supplied `timeZone` |
|
||||
|
||||
## Number-format callsites (not date — ignore)
|
||||
|
||||
These are `Number.prototype.toLocaleString()` calls — thousands separators on numeric values, NOT date callsites.
|
||||
|
||||
Count: 47
|
||||
|
||||
Examples (representative, not exhaustive):
|
||||
- `app/admin/page.tsx:131` — `counts.linkConflicts.toLocaleString()`
|
||||
- `app/admin/sync/page.tsx:344,348,360,378,396,400,404` — sync count formatters
|
||||
- `app/admin/sync/duo/page.tsx:281` — `value.toLocaleString()`
|
||||
- `app/admin/sync/veeam/page.tsx:162,177` — record counts
|
||||
- `app/admin/sync/itglue/page.tsx:116-125,150,194` — stat counts
|
||||
- `app/admin/sync/mimecast/page.tsx:35,866,901,903,905` — count formatters
|
||||
- `app/admin/sync/sentinelone/page.tsx:100,123,137,165` — record counts
|
||||
- `app/admin/data-browser/ticket-notes/page.tsx:180,181` — total counts
|
||||
- `app/admin/data-browser/time-entries/page.tsx:494,496` — total counts
|
||||
- `app/admin/qbo/page.tsx:43` — `n.toLocaleString()` helper
|
||||
- `app/analyzer/tickets/page.tsx:744` — `total.toLocaleString()`
|
||||
- `app/dashboard/page.tsx:426,427` — stat counts
|
||||
- `app/sentinelone/coverage/page.tsx:96` — `Number(value).toLocaleString()`
|
||||
- `components/admin/SyncDashboard.tsx:126,132,138` — record counts
|
||||
- `components/admin/EntitySyncProgress.tsx:219,239` — total records
|
||||
- `components/admin/ChunkedSyncProgress.tsx:117,131,166` — chunk counts
|
||||
- `components/analyzer/analysis-view.tsx:118,119` — token counts
|
||||
- `components/dashboard/kpi-card.tsx:60` — `value.toLocaleString()`
|
||||
- `components/mobile/KpiCardMobile.tsx:29` — `value.toLocaleString()` (number branch)
|
||||
|
||||
## Server-side callsites (out of scope)
|
||||
|
||||
These run in Node (api routes), not React components. Out of scope for the
|
||||
client hook. Today the only such callsites are the analyzer prompt builders —
|
||||
deliberately locale-only (LLM input).
|
||||
|
||||
| File | Line | Reason |
|
||||
|------|------|--------|
|
||||
| app/api/veeam/rpo-analyze/route.ts | 78, 108, 109 | LLM prompt builder — locale-only by design |
|
||||
| app/api/veeam/ticket-analysis/run/route.ts | 78, 90, 97, 98 | LLM prompt builder — locale-only by design |
|
||||
|
||||
## Deliberate UTC callsites
|
||||
|
||||
Files that already pass `{ timeZone: 'UTC' }` for a specific reason. Leave as-is.
|
||||
|
||||
| File | Line | Reason |
|
||||
|------|------|--------|
|
||||
| components/mobile/EngagementHoursSparkline.tsx | 39 | UTC pin for sparkline shape (not a clock) — data is daily UTC buckets |
|
||||
|
||||
## Summary
|
||||
|
||||
- **Leak count: 81** (rows in the leak table above; 81 distinct callsites across 39 files)
|
||||
- **Explicit-zone count: 5** (3 finance + 1 tickets + 1 hook helper)
|
||||
- **Server-side count: 7** (3 in rpo-analyze route + 4 in ticket-analysis route)
|
||||
- **Deliberate-UTC count: 1** (EngagementHoursSparkline)
|
||||
- **Number-format count: 47** (ignored — not date callsites)
|
||||
|
||||
## Plan 05 dispatch
|
||||
|
||||
- Leak count > 0: **Plan 05 (sibling, depends_on `07.1-04`) IS NEEDED.**
|
||||
- Plan 05's `files_modified` is the unique set of leak file paths above (39 files).
|
||||
- Plan 05 must:
|
||||
1. Add `useUserTimezone()` (or its server equivalent for non-client files) to each of the 39 files.
|
||||
2. Thread `tz` into every leak callsite listed above so the count of `timeZone:` ≥ count of `toLocaleDateString(` + `toLocaleString(` + `toLocaleTimeString(` callsites in each file.
|
||||
3. Skip the four file groups documented as out-of-scope (Number-format / Server-side / Deliberate-UTC / already-migrated).
|
||||
4. Verify with the same positive-assertion grep approach Plan 04 used.
|
||||
|
||||
Note: The `app/admin/data-browser/time-entries/page.tsx.backup` file is an
|
||||
orphaned backup (no `.backup` suffix is part of the active app). It contains a
|
||||
leak at line 166 but is unreachable. Plan 05 should delete the file (not migrate
|
||||
it) — confirm by running `git log -- app/admin/data-browser/time-entries/page.tsx.backup`
|
||||
before deletion.
|
||||
Loading…
Add table
Add a link
Reference in a new issue