wulf-pulse/.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-PLAN.md
lorentz 82958c5ec6 docs(07.1-05): build leak migration manifest from audit
- Add 07.1-05-MANIFEST.md with per-file migration plan for 51 leak files
  derived from 07.1-04-AUDIT.md (81 leak callsites total)
- Each file gets pre-migration leak count + per-callsite before/after
  snippets + post-migration acceptance grep
- Defer components/configuration-items/auvik-tab.tsx (no 'use client' —
  preserves layering boundary)
- Defer app/admin/data-browser/time-entries/page.tsx.backup (orphaned;
  marked for git rm in Task 2)
- Update Plan 05 files_modified frontmatter to enumerate every file
  Task 2 will touch (50 active migrations + 1 deletion + manifest)
2026-05-07 08:19:30 -04:00

23 KiB

phase plan type wave depends_on files_modified autonomous requirements requirements_addressed must_haves
07.1-user-timezone-fix-inserted-urgent 05 execute 3
07.1-04
.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md
app/engagement/profile/page.tsx
app/engagement/page.tsx
app/admin/sync/duo/page.tsx
app/admin/sync/datto-rmm/page.tsx
app/admin/sync/veeam/page.tsx
app/admin/sync/itglue/page.tsx
app/admin/sync/mimecast/page.tsx
app/admin/sync/sentinelone/page.tsx
app/admin/zabbix-wan/page.tsx
app/admin/rmm-overshell/page.tsx
app/admin/itglue-writes/page.tsx
app/admin/data-browser/tasks/page.tsx
app/admin/data-browser/projects/page.tsx
app/admin/data-browser/ticket-notes/page.tsx
app/admin/data-browser/contracts/page.tsx
app/admin/data-browser/tickets/page.tsx
app/admin/data-browser/time-entries/page.tsx
app/admin/data-browser/time-entries/page.tsx.backup
app/admin/ticket-digest/page.tsx
app/admin/device-link-conflicts/page.tsx
app/admin/workflow/history/page.tsx
app/admin/workflow/pipelines/[id]/page.tsx
app/analyzer/itglue/applications/page.tsx
app/analyzer/itglue/applications/[id]/page.tsx
app/analyzer/itglue/configurations/page.tsx
app/analyzer/itglue/configurations/[id]/page.tsx
app/analyzer/itglue/sites/[companyId]/page.tsx
app/analyzer/queue/page.tsx
app/analyzer/ticket/[ticketNumber]/page.tsx
app/analyzer/tickets/page.tsx
app/analyzer/reports/page.tsx
app/analyzer/reports/[id]/page.tsx
app/dashboard/page.tsx
app/quotes/page.tsx
app/veeam-analysis/page.tsx
components/admin/DetailModal.tsx
components/admin/IntegrationStatusTabs.tsx
components/admin/SyncScheduler.tsx
components/admin/audit/audit-log-table.tsx
components/admin/users/user-table.tsx
components/admin/users/user-sessions.tsx
components/analyzer/analysis-view.tsx
components/settings/active-sessions.tsx
components/dashboard/resolution-trend.tsx
components/dashboard/volume-trend.tsx
components/quotes/ticket-detail-modal.tsx
components/analytics/TimelineView.tsx
components/analytics/ScoreCard.tsx
components/configuration-items/addigy-tab.tsx
components/status/activity-sparkline.tsx
components/backup/compliance-detail-table.tsx
components/backup/company-backup-detail.tsx
true
TZ-04
TZ-02
TZ-04
TZ-02
truths artifacts key_links
Every 'browser-local zone leak' callsite identified by Plan 04 Task 3's audit (07.1-04-AUDIT.md) is migrated to consume `useUserTimezone()` from `@/lib/hooks/use-user-timezone`
Each migrated client component imports `useUserTimezone` and threads `timeZone: tz` into every previously-leaking `toLocaleDateString(` / `toLocaleString(` / `toLocaleTimeString(` callsite in the same file
Module-scope formatter helpers that previously had no access to the user's tz are converted to accept `tz: string` as an argument; every callsite passes `tz` resolved from `useUserTimezone()`
Non-leak callsites (server-side route handlers, deliberate UTC pins, number formatters) are NOT modified
After this plan ships, a codebase-wide grep for `toLocale*String(` / `Intl.DateTimeFormat` returns ZERO 'browser-local zone leak' callsites in client components — satisfying Phase 7.1 SC#4 (single source of truth)
path provides contains
.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md Per-file migration plan derived from 07.1-04-AUDIT.md, recording every leak callsite, its replacement, and a per-file acceptance grep Leak migration manifest
from to via pattern
Each leak file lib/hooks/use-user-timezone.ts import { useUserTimezone } from '@/lib/hooks/use-user-timezone' useUserTimezone
from to via pattern
Each leaking toLocale call tz from useUserTimezone() { ...options, timeZone: tz } timeZone: tz
Close the codebase-wide adoption gap for `useUserTimezone()`. Plan 04 migrated the two reported-bug-surface mobile pages; Plan 04 Task 3 produced a complete audit (`07.1-04-AUDIT.md`) classifying every other `toLocale*String(` / `Intl.DateTimeFormat` callsite across `app/`, `components/`, and `lib/hooks/`. This plan migrates every callsite the audit classified as a "browser-local zone leak" so that Phase 7.1 SC#4 ("single source of truth — no scattered `Intl.DateTimeFormat` instantiations") is satisfied at the codebase scale.

Purpose: Resolve the codebase-scale portion of TZ-04 + TZ-02 (client side). Plans 03 + 04 together cover the read paths and the hook itself; this plan finishes the migration work the audit revealed (>10 known leak callsites in admin/analyzer/engagement/dashboard pages and shared components).

Output: A 07.1-05-MANIFEST.md derived from the audit, plus edits to every file the audit classified as a leak. The manifest is the source of truth for files_modified (Plan 04's audit is what populates it) — at planning time the exact list isn't known; the executor MUST consume the audit and update this plan's files_modified array as the first action.

Conditional execution: If 07.1-04-AUDIT.md "Plan 05 dispatch" reports "Plan 05 is unnecessary. Mark in SUMMARY." (Leak count == 0), skip every task and return CLEAN immediately. Otherwise proceed.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/STATE.md @.planning/ROADMAP.md @.planning/REQUIREMENTS.md @CLAUDE.md @.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-04-AUDIT.md @lib/hooks/use-user-timezone.ts This plan depends on Plan 04 — the hook (`@/lib/hooks/use-user-timezone`) and the audit file MUST exist before Plan 05 starts. The audit is the SINGLE source of truth for which files are migrated.

The migration recipe for every leak callsite is:

  1. If the file is not already 'use client', the migration is impossible (server components can't call useUserTimezone). Re-classify the callsite as 'server_side' in a follow-up audit. (Audit step at planning time already filtered out server route files.)

  2. Add at the top of the imports (next to other @/lib imports): import { useUserTimezone } from '@/lib/hooks/use-user-timezone';

  3. Inside the default-exported component (or the hook entry point in a custom component), call: const tz = useUserTimezone();

  4. For each leaking call:

    new Date(ts).toLocaleString('en-US', { month: 'short', ... })
    

    new Date(ts).toLocaleString('en-US', { month: 'short', ..., timeZone: tz })
    

    For module-scope helper functions (function fmt(d) { return new Date(d).toLocaleString() }):

    • Convert to accept tz: string as a new parameter: function fmt(d, tz) { return new Date(d).toLocaleString(undefined, { timeZone: tz }) }
    • Update every callsite of that helper in the same file to pass tz.
  5. For DataTable column render: (value) => new Date(value).toLocaleDateString() patterns (common in admin pages), the migration is to:

    • Move the column definitions inside the component, OR
    • Pass tz via a closure when the columns are constructed inside the component, OR
    • Use the formatInUserTimezone helper from @/lib/hooks/use-user-timezone if column definitions stay at module scope and tz can be threaded as a param to a column-builder function.
  6. Re-run the codebase-wide grep AFTER all migrations:

    grep -rEn "Intl\\.DateTimeFormat|\\.toLocaleDateString\\(|\\.toLocaleTimeString\\(|\\.toLocaleString\\(" \\
      app/ components/ lib/hooks/
    

    The post-migration result must contain ONLY:

    • timeZone: in the same call (migrated → satisfied)
    • server-side route handlers under app/api/**/route.ts (deliberately server, out of scope)
    • components/ui/calendar.tsx (shadcn primitive — calendar-cell labels, not user-visible dates)
    • Number formatters (.toLocaleString() on number/bigint — not date formatters)
    • Files in the audit's "deliberate_utc" classification

Browser environment variable:

  • process.env.NEXT_PUBLIC_DEFAULT_TIMEZONE is the client-readable equivalent of DEFAULT_TIMEZONE. Same fallback semantics as useUserTimezone().
Task 1: Build the migration manifest from the audit .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md - .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-04-AUDIT.md (the input — Plan 04 Task 3's deliverable) - lib/hooks/use-user-timezone.ts (the migration target) Read `07.1-04-AUDIT.md`. If "Plan 05 dispatch" reports "Plan 05 is unnecessary", write a one-line `07.1-05-MANIFEST.md` with `Leak count: 0 — Plan 05 skipped` and return CLEAN. Otherwise, build the manifest.
Group the leak callsites by file. For each file, produce a section:

    ### <file path>

    - 'use client' status: <yes|no — needs `'use client'` added or refactor>
    - Pre-migration leak count: <N>
    - Post-migration acceptance grep:
          [ "$(grep -c 'timeZone:' <file>)" -ge <N> ]
    - Per-callsite plan:
      - Line <L>: `<snippet before>` → `<snippet after>`
      - ...

    Notes / risks: <any per-file gotchas — e.g., DataTable column factory
    at module scope, helper that needs `tz` parameter threading>

Then write a `## Files to migrate` summary list at the top with `[ ]`
checkboxes — Task 2 ticks them off as it migrates each file.

After writing the manifest, update Plan 05's `files_modified` frontmatter
array to include EVERY file path enumerated in `## Files to migrate`,
PLUS the manifest path itself. (Note: this requires editing
`07.1-05-PLAN.md` in place. Use the `Edit` tool to update only the
`files_modified:` block.)

Notes:
- Do NOT skip module-scope helpers — converting them to accept `tz` as a
  parameter is part of the migration. The audit may flag these as a
  "Notes" gotcha; the manifest captures the exact transformation.
- Files where the migration would require >5 component-shape changes
  (e.g., refactoring a class component to functional, or moving a large
  module-scope formatter into the component) should be flagged as
  "DEFER — out of scope for Plan 05" with a brief rationale and added to
  a `## Deferred` list at the bottom of the manifest. The deferred set
  becomes a v2 follow-up (a future phase).
test -f .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md && grep -q '## Files to migrate' .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md - File exists at `.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md` - Either: contains `Leak count: 0 — Plan 05 skipped` (and Tasks 2+3 are skipped), OR - Contains a `## Files to migrate` checklist with one entry per leak file from the audit - Each leak file has a `Per-callsite plan:` block enumerating every leak callsite by line number with before/after snippets - Plan 05's `files_modified` frontmatter has been updated to include every file in `## Files to migrate` plus the manifest path - Any file deferred is listed under `## Deferred` with rationale The manifest is the single source of truth for what Task 2 migrates and what Task 3's verification grep checks. Plan 05's `files_modified` accurately reflects every file this plan will touch. Task 2: Migrate every leak callsite per the manifest (see 07.1-05-MANIFEST.md `## Files to migrate` — populated by Task 1) - .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md (Task 1's output — the per-file migration plan) - lib/hooks/use-user-timezone.ts (the import target) - app/mobile/finance/page.tsx, app/mobile/tickets/[id]/page.tsx (Plan 04's reference migrations — copy the call shape) For each file in the manifest's `## Files to migrate` checklist, in the order they appear:
1. Open the file.
2. If it does not already declare `'use client'` at the top, STOP and
   move it to `## Deferred` in the manifest with rationale "would require
   'use client' conversion or component refactor — out of scope for Plan
   05". Do NOT add `'use client'` to a file that doesn't have it — that's
   a non-trivial change in Pulse (server components are fine for static
   shells; the user explicitly chose this layering).
3. Add `import { useUserTimezone } from '@/lib/hooks/use-user-timezone';`
   next to the other `@/lib` imports.
4. Inside the component (or each component if the file exports multiple),
   add `const tz = useUserTimezone();` near the top of the function body
   (before any useState/useEffect calls).
5. Apply each per-callsite transformation from the manifest verbatim.
6. For module-scope helpers, convert to accept `tz: string` as a new
   parameter and update every callsite in the same file.
7. Tick off the file in the manifest's `## Files to migrate` checklist.
8. Verify the file with the per-file acceptance grep recorded in the
   manifest:
       [ "$(grep -c 'timeZone:' <file>)" -ge <pre-migration leak count> ]

Do NOT modify files outside the manifest's `## Files to migrate` list.
Do NOT modify files in `## Deferred`.

Type-check after each file (or at the end of the batch) to catch any
parameter-threading regressions:
    ERR=$(npx tsc --noEmit --pretty 2>&1 | grep -E "<file_path>"); [ -z "$ERR" ]

Notes:
- The DataTable column-render pattern (common in `app/admin/data-browser/*/page.tsx`)
  may need a column-builder function that takes `tz` as a closure variable.
  The manifest will have called this out per file. Do NOT introduce a
  `useMemo` for column definitions unless the file already uses one
  (premature optimization).
- Some helper functions (e.g., `relTime`, `formatDate`, `fmt`) are defined
  at module scope in many files. Threading `tz` as an extra parameter is
  intentional — no `React.useContext` workaround.
- The shared admin sync formatter (e.g., `app/admin/sync/datto-rmm/page.tsx:23`)
  is a candidate for moving inside the component OR threading `tz`. The
  lighter-touch fix is threading.
MANIFEST=.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md; if grep -q "Plan 05 skipped" "$MANIFEST"; then echo "skipped"; else FILES=$(grep -E "^- \[x\] " "$MANIFEST" | sed -E 's/^- \[x\] //' | tr '\n' ' '); ALL_OK=1; for f in $FILES; do if ! grep -q "useUserTimezone" "$f"; then echo "MISSING: $f"; ALL_OK=0; fi; done; [ "$ALL_OK" = "1" ]; fi - Either: manifest reports "Plan 05 skipped" (Task 2 is a no-op) — accepted, OR - Every file in the manifest's `## Files to migrate` checklist is checked off `[x]` - Every checked-off file imports `useUserTimezone` from `@/lib/hooks/use-user-timezone` - Every checked-off file calls `useUserTimezone()` inside the component - Every per-file acceptance grep in the manifest passes - `ERR=$(npx tsc --noEmit --pretty 2>&1 | grep -E ""); [ -z "$ERR" ]` for every migrated file Every leak file in the audit has been migrated to consume `useUserTimezone()`. Deferred files (refactor-blocking) are documented explicitly. TypeScript compiles for every migrated file. Task 3: Codebase-wide post-migration verification grep (read-only verification; no file edits) - .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md (the deferred list — informs the expected residue) - .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-04-AUDIT.md (the deliberate_utc + server_side classifications — informs the expected residue) Re-run the codebase-wide leak discovery grep AFTER Task 2 finishes:
    grep -rEn "Intl\\.DateTimeFormat|\\.toLocaleDateString\\(|\\.toLocaleTimeString\\(" \\
      app/ components/ lib/hooks/ \\
      | grep -v "node_modules" \\
      | grep -v "components/ui/calendar.tsx" \\
      > /tmp/tz-post-migration.txt

For each line in the output, classify:
- Has `timeZone:` in the same call → migrated ✓
- Lives under `app/api/**/route.ts` → server-side, out of scope ✓
- Listed in audit's `deliberate_utc` section → out of scope ✓
- Listed in manifest's `## Deferred` section → known follow-up ✓
- None of the above → REGRESSION. Stop and re-migrate.

Acceptable residue:
- Server-side route handlers (Plan 03's responsibility, but they don't
  consume the client hook anyway)
- Deliberate UTC pins
- Deferred files (count must match the manifest's `## Deferred` count)

Document the residue list in `07.1-05-SUMMARY.md` for posterity.

Also run a final TypeScript check across all modified files:
    FILES=$(grep -E "^- \\[x\\] " .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md | sed -E 's/^- \\[x\\] //')
    for f in $FILES; do
      ERR=$(npx tsc --noEmit --pretty 2>&1 | grep -E "$f")
      [ -z "$ERR" ] || { echo "TS errors in $f"; exit 1; }
    done
RESIDUE=$(grep -rEn "Intl\.DateTimeFormat|\.toLocaleDateString\(|\.toLocaleTimeString\(" app/ components/ lib/hooks/ 2>/dev/null | grep -v node_modules | grep -v "components/ui/calendar.tsx" | grep -v "timeZone:" | grep -v "/route.ts:" | wc -l); MANIFEST=.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md; DEFERRED=$(grep -cE "^- " "$MANIFEST" 2>/dev/null | head -1); DELIBERATE=$(grep -cE "^\| " .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-04-AUDIT.md 2>/dev/null); echo "post-migration residue lines: $RESIDUE — must equal deferred + deliberate-UTC count from audit/manifest"; [ "$RESIDUE" -le "$((DEFERRED + DELIBERATE))" ] - Post-migration residue grep returns ≤ (deferred files in manifest + deliberate-UTC files in audit) - Every residue line is accounted for by either: deferred classification, deliberate-UTC classification, or `timeZone:` already present - No new "leak" callsite exists that wasn't classified by the audit OR migrated by Task 2 - All migrated files pass `npx tsc --noEmit --pretty` filtered to that file - `07.1-05-SUMMARY.md` documents the residue list and any deferred-file rationale The codebase-wide grep proves SC#4 is satisfied: every leak callsite is either migrated, deferred (with rationale), or out-of-scope (server-side or deliberate UTC). No new leak surfaces have been introduced.

<threat_model>

Trust Boundaries

Boundary Description
Server → client (session payload) Same boundary as Plan 04 — tz string travels via Better Auth session cookie; client trusts it for formatting only

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-07.1-05-01 Tampering Tampered session payload with malformed tz mitigate Same as Plan 04: useUserTimezone() validates against Intl.supportedValuesOf('timeZone') and falls back to `NEXT_PUBLIC_DEFAULT_TIMEZONE
T-07.1-05-02 Information Disclosure tz exposed in client memory across more pages accept Same as Plan 04: tz is non-sensitive metadata. The change here just propagates the same exposure to additional pages — not a new threat.
T-07.1-05-03 Tampering A migration accidentally drops or reformats the date mitigate Per-file acceptance grep in the manifest verifies that the count of timeZone: ≥ the pre-migration leak count. If a migration accidentally drops a timeZone: thread, the grep catches it. TypeScript also catches missing-arg regressions where module-scope helpers gained a tz parameter.
T-07.1-05-04 Repudiation Inconsistent date display between users with different tz preferences accept Intentional — this is the whole point of the phase. Two users in different tzs SHOULD see different "today" buckets.
T-07.1-05-05 Elevation of Privilege Component rendering server-only data with client-only hook mitigate Task 2's 'use client' precondition: any file lacking the directive is moved to deferred. We do NOT silently add 'use client' to a server component (would change rendering semantics).
</threat_model>
End-to-end checks for this plan:
  1. Static (precondition): test -f .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-04-AUDIT.md
  2. Static (manifest exists): test -f .planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-MANIFEST.md
  3. Static (skip path): if manifest contains Plan 05 skipped, plan returns CLEAN with no other checks.
  4. Static (migration coverage): every file in manifest's ## Files to migrate checklist is checked-off and contains useUserTimezone.
  5. Static (post-migration residue): see Task 3 verify.
  6. Type: ERR=$(npx tsc --noEmit --pretty 2>&1 | grep -E "<each modified file>"); [ -z "$ERR" ] for every migrated file.
  7. Runtime (spot check, with the dev server running and a logged-in user with timezone='America/New_York'): open one or two of the most-trafficked migrated pages (e.g., /admin/audit/audit-log-table consumer, /analyzer/queue, /dashboard); verify date strings respect the user-tz (set device tz to UTC, confirm rendered strings match ET).

<success_criteria>

  • Phase 7.1 SC#4 satisfied at codebase scale: no client-component leak callsites remain (only server-side, deliberate-UTC, and explicitly-deferred residues)
  • Every migrated file imports useUserTimezone and threads timeZone: into every formatter call
  • Module-scope helpers that previously had no tz access now accept tz: string as a parameter
  • TypeScript compiles for every migrated file
  • Deferred-file rationale is documented for any file the manifest excluded </success_criteria>
After completion, create `.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-05-SUMMARY.md` documenting: the manifest's `## Files to migrate` count vs the leak count from the audit (should match minus deferred), the per-file before/after leak counts, the deferred file list with rationale, the post-migration residue grep result, and any TypeScript regressions caught + fixed during migration.