Commit graph

296 commits

Author SHA1 Message Date
91b876310e feat(07.1-05): user-tz on dashboard, quotes, veeam-analysis
- dashboard/page.tsx: thread tz into PageHeader description's
  toLocaleDateString call.
- quotes/page.tsx: thread tz into formatDate arrow helper inside the
  default export.
- veeam-analysis/page.tsx: thread tz into the summary footer's
  generated-at toLocaleString call.

Migrates 3 of 81 audit leak callsites.
2026-05-07 08:36:09 -04:00
96edfb4444 feat(07.1-05): user-tz on analyzer pages
- itglue/applications, applications/[id], configurations,
  configurations/[id], sites/[companyId], queue, ticket/[ticketNumber],
  tickets, reports, reports/[id]: useUserTimezone() in default export;
  thread tz into every inline toLocale*String call.
- analyzer/tickets/page.tsx converts module-scope formatRelative(iso)
  helper to formatRelative(iso, tz); updates 1 callsite.

Migrates 16 of 81 audit leak callsites.
2026-05-07 08:34:58 -04:00
23b179f2a7 feat(07.1-05): user-tz on admin operational pages
- zabbix-wan, rmm-overshell, itglue-writes, ticket-digest,
  device-link-conflicts, workflow/history, workflow/pipelines/[id]:
  each gets useUserTimezone() at the component entry; threads tz into
  every inline toLocaleString call.

Migrates 11 of 81 audit leak callsites.
2026-05-07 08:31:22 -04:00
8c56cafe0b feat(07.1-05): user-tz on admin sync pages
- duo, sentinelone, datto-rmm, veeam, itglue, mimecast: each gets
  useUserTimezone() in default export and threads tz through
  fmtDate/sub-component props.
- duo (1 callsite, closure inline), sentinelone (1, module-scope helper),
  datto-rmm (1 helper + StatusTab/HistoryTab props), veeam (1 helper +
  5 sub-components), itglue (1 helper + StatusTab/HistoryTab props),
  mimecast (1 helper + 6 sub-components incl. 2 dialogs with inline
  toLocaleString calls).
- Module-scope fmtDate(d) signatures converted to fmtDate(d, tz).

Migrates 13 of 81 audit leak callsites.
2026-05-07 08:28:42 -04:00
a709144685 feat(07.1-05): user-tz on engagement overview + profile pages
- app/engagement/page.tsx: useUserTimezone in EngagementPage; thread tz
  into 7 toLocale* callsites (lines 844, 1012, 1108, 1227, 1255 — last
  two have 2 calls per line for date+time).
- app/engagement/profile/page.tsx: useUserTimezone in EngagementProfilePage;
  add tz prop to ActivityHeatmap; convert module-scope monthLabel(m) to
  monthLabel(m, tz); update 2 callsites of monthLabel.

Migrates 9 of 81 audit leak callsites.
2026-05-07 08:23:24 -04:00
b417988ee6 feat(07.1-05): user-tz on admin data-browser DataTable columns
- Add useUserTimezone() to 6 admin/data-browser pages
- Thread { timeZone: tz } into 8 DataTable column render() calls
- Delete orphaned app/admin/data-browser/time-entries/page.tsx.backup
  (per audit footnote — never imported, contained 1 leak at line 166)

Files: contracts, projects, tasks, ticket-notes, tickets, time-entries

Migrates 8 of 81 audit leak callsites.
2026-05-07 08:21:18 -04:00
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
36eba2e2af docs(07.1-03): complete user-tz server migration plan 2026-05-07 08:08:43 -04:00
04d036ab78 feat(07.1-03): user-tz day buckets on /api/dashboard/trends
- volumeRes / resolutionRes generate_series and join keys converted from
  CURRENT_DATE / *_date::date = days.d to user-tz two-step idiom.
- engineersRes WHERE filter te.entry_date::date = CURRENT_DATE migrated
  to user-tz on both sides.
- queueHeatmap (open-only counts) preserved unchanged — no day-boundary
  math; comment added explaining why.
- requireAuth() session destructured; tz passed as $1 to all three
  migrated queries.
2026-05-07 08:05:32 -04:00
dc0b06b9c7 feat(07.1-03): user-tz boundaries on /api/mobile/finance + engagement; auth-gate finance
- /api/mobile/finance: add requireAuth() (aligns with all other /api/mobile/*
  handlers) + getUserTimezone(); migrate paid_mtd / paid_ytd to user-tz
  DATE_TRUNC, six aging-bucket comparisons to user-tz CURRENT_DATE, and
  days_overdue arithmetic. Preserved unchanged: 12-month rolling
  monthlyRevenue (rolling — not a calendar boundary).
- /api/mobile/engagement/summary: destructure session, resolve tz; migrate
  rolling time_entries WHERE clause to user-tz on both sides of >=. Added
  TZ-02 carve-out comment above the snapshot queries documenting why
  engagement_snapshots remain UTC-bucketed (deferred per REQUIREMENTS.md).
- /api/mobile/engagement/trend: replace every bare CURRENT_DATE with
  (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date; pass [tz] as params
  to postgresClient.query. Day buckets now align to user-tz days.
2026-05-07 08:04:47 -04:00
8a9887faa1 feat(07.1-03): user-tz day boundaries on /api/(mobile/)dashboard(/overview)
- Switch opened_today / resolved_today / yesterday / 7d-avg buckets from
  CURRENT_DATE to ((value AT TIME ZONE 'UTC') AT TIME ZONE $1)::date.
- Both routes destructure session from requireAuth() and resolve tz via
  getUserTimezone(); tz parameterized as $1 (no SQL interpolation).
- Preserved unchanged: due_date_time < NOW() (rolling SLA, tz-independent),
  the INTERVAL '24h/5min/1h' rolling-window queries (failed backups,
  stalled workflows, analyzer/RMM 1h fail counts, backup-success 24h).
- Added a code comment above the 24h failed-backups query explaining why
  it stays UTC-NOW relative.
2026-05-07 08:02:52 -04:00
ea5532c5c3 feat(07.1-03): add lib/services/user-timezone.ts helper
- getUserTimezone(session) returns validated IANA tz string with safe fallback
- DEFAULT_TIMEZONE_FALLBACK reads process.env.DEFAULT_TIMEZONE || 'UTC'
- Validates against Intl.supportedValuesOf('timeZone'); 64-char length cap
- Pure / synchronous / no DB / no @/lib/auth-utils import (avoids circular)
2026-05-07 08:01:23 -04:00
3f3142bbb7 docs(07.1-04): complete useUserTimezone hook + mobile migration plan
- Hook signature documented (useUserTimezone + formatInUserTimezone)
- Migrated callsite tables (before/after) for finance + tickets pages
- Audit results: 81 leak callsites across 39 files; Plan 05 dispatch = NEEDED
- All 3 task commits and acceptance criteria pass self-check
2026-05-07 07:58:42 -04:00
dfd0a9f2b2 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
2026-05-07 07:56:32 -04:00
14f4da3483 feat(07.1-04): migrate mobile finance + ticket detail to useUserTimezone
- app/mobile/finance/page.tsx: thread tz through fmtDate, setLastSync, monthLabel — 3 formatter callsites now pass timeZone
- app/mobile/tickets/[id]/page.tsx: thread tz through fmtDate (5 callsites) and TimelineCard prop
- All toLocaleDateString / toLocaleString calls in both files now render in user.timezone, not browser local zone
- Resolves TZ-02 on the directly-reported bug surface (mobile finance + ticket detail)
2026-05-07 07:54:21 -04:00
2ac2db7a23 feat(07.1-04): add useUserTimezone client hook
- New lib/hooks/use-user-timezone.ts exporting useUserTimezone() and formatInUserTimezone()
- Reads user.timezone from Better Auth useSession() additionalField (Plan 01)
- Validates against Intl.supportedValuesOf('timeZone') with safe fallback to NEXT_PUBLIC_DEFAULT_TIMEZONE || 'UTC'
- Pure formatInUserTimezone helper safe to call inside loops (not a hook)
- Resolves TZ-04
2026-05-07 07:52:16 -04:00
3a3564fc91 chore: merge 07.1-02 worktree commits 2026-05-07 07:48:54 -04:00
46ee1f6ade docs(07.1-01): complete user timezone column + Better Auth additionalField
- TZ-01 satisfied: per-user IANA timezone column added to "user" table
- session.user.timezone now exposed via Better Auth additionalFields
- Defaults: SQL DEFAULT 'UTC', app-level default reads process.env.DEFAULT_TIMEZONE
- No destructive ops; storage timezone of existing TIMESTAMP columns unchanged
2026-05-07 07:38:08 -04:00
1b80b3f7ab docs(07.1-02): complete user timezone endpoint plan
SUMMARY.md documenting GET/PUT /api/me/timezone shapes, IANA validation
rule, threat-model dispositions, and self-check results.
2026-05-07 07:37:25 -04:00
061f266b18 feat(07.1-01): expose user timezone on Better Auth session
- Adds `timezone` to additionalFields on the auth `user` config
- Default value reads process.env.DEFAULT_TIMEZONE (falls back to "UTC")
- session.user.timezone now available on every authenticated request
- Inferred User type automatically picks up the new field — no type changes needed
2026-05-07 07:36:35 -04:00
f50215f8fc feat(07.1-02): add GET/PUT /api/me/timezone endpoint
- New app/api/me/timezone/route.ts with GET + PUT handlers
- requireAuth() gate on both methods (401 unauthenticated)
- IANA whitelist via Intl.supportedValuesOf('timeZone') + 64-char cap
- PUT writes only session.user.id — no userId body/query param
- Updates audit column updated_at = NOW() on write
- Resolves TZ-03
2026-05-07 07:36:01 -04:00
25e6b7599a feat(07.1-01): add user timezone column migration
- Adds `timezone TEXT NOT NULL DEFAULT 'UTC'` to "user" table (TZ-01)
- Backfills any NULL rows defensively
- Idempotent: ADD COLUMN IF NOT EXISTS, no destructive ops
- Storage timezone of existing TIMESTAMP columns unchanged
2026-05-07 07:35:51 -04:00
bee35e0260 fix(auth): reduce mobile sign-in friction (PWA + auto-redirect)
Three independent changes that together stop the mobile re-auth churn:

- app/layout.tsx: add appleWebApp metadata so iOS "Add to Home Screen"
  launches Pulse in true standalone mode (own cookie jar, persists
  across Safari memory pressure)
- components/auth/sign-in-form.tsx: when /auth/sign-in mounts and
  ?callbackUrl starts with /mobile, auto-call authClient.signIn.social
  for Microsoft. With an active M365 browser session this redirect is
  silent — the user lands on /mobile/* with no tap.
- app/auth/sign-in/page.tsx: wrap SignInForm in <Suspense> (required
  by Next.js 16 because SignInForm now uses useSearchParams)

Pairs with operator-side env bump SESSION_TIMEOUT_SECONDS=2592000
(30 days, .env files are gitignored — applied on the running container
via docker compose up -d --force-recreate app).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 07:05:52 -04:00
f9ab954518 docs(phase-07.1): plan urgent user timezone fix (TZ-01..TZ-04)
Insert Phase 7.1 between Phase 7 and Phase 8 to address dashboards/filters
rendering wrong dates because day/week boundary math runs in server UTC
instead of the viewing user's timezone. Persistence stays UTC; only the
read/display path changes.

5 plans in 3 waves:
- 07.1-01 (Wave 1): migration 083 + Better Auth additionalField timezone
- 07.1-02 (Wave 1): /api/me/timezone GET+PUT with IANA validation
- 07.1-03 (Wave 2): server-side AT TIME ZONE migration across 6 routes,
  including auth-gate fix on /api/mobile/finance and trends route
- 07.1-04 (Wave 2): useUserTimezone() hook + 2 mobile pages + codebase audit
- 07.1-05 (Wave 3): codebase-wide useUserTimezone() adoption per audit

Add Phase 9 stub (User Profile & Preferences) to roadmap for the picker UI
that reuses 7.1's hook + endpoint.

REQUIREMENTS.md TZ-02 carves out engagement_snapshots UTC bucketing as a
documented exception (≤24h drift acceptable for admin overview).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 07:02:07 -04:00
0dec54ca4f docs(phase-07): evolve PROJECT.md after phase completion 2026-05-03 23:04:41 -04:00
a288df5b34 docs(phase-07): complete phase execution 2026-05-03 23:04:17 -04:00
349279a6ef test(07): persist human verification items as UAT 2026-05-03 23:04:12 -04:00
0af6136864 docs(07-03): complete mobile engagement page plan summary
- Documents orchestration model (3 fetches on period, 1 on sort, 0 on search)
- Documents deviation: lowercase EngagementSortKey values vs plan template (Wave 2 actual)
- Documents field mapping: existing endpoint 'email' -> component 'userEmail'
- Confirms DRAWER-03 reachability and ENG-09 BottomNav unchanged
- Flags inherited risk T-07-12 (IDOR on /api/engagement/users) for follow-up
2026-05-03 22:58:41 -04:00
5daf7f31e5 feat(07-03): create mobile engagement page (plan 01 endpoints + plan 02 components)
- New app/mobile/engagement/page.tsx ('use client', 378 lines)
- Period chips (D30 default), 3 independent fetches on mount/period change
- Sort chips (hours default), refetch users only on sort change
- Client-side search filter (useMemo, 300ms debounce via EngagementSearchInput)
- IntersectionObserver infinite scroll (rootMargin 200px) + Load more fallback
- 4 summary card skeletons + sparkline skeleton + 5 row skeletons on initial load
- Empty state (activeUsers === 0 + users.length === 0), not-configured banner, no-matches inline
- toast.error per failing fetch; Load more flips to Retry on error
- BottomNav and MoreDrawer unchanged (ENG-09 / D-01 / D-02)
2026-05-03 22:57:44 -04:00
d63789224e feat(07-02): add 7 engagement components (chips, summary card, sparkline, sort, search, user row + skeleton) 2026-05-03 22:53:33 -04:00
ccd2177977 docs(07-02): complete engagement component primitives plan summary
- 7 presentational components built: PeriodChips, SummaryCard, HoursSparkline, SortChips, SearchInput, UserRow, UserRowSkeleton
- getInitials exported from EngagementUserRow for Phase 8 reuse
- No recharts — inline SVG sparkline only (DASH-04)
- tsc exits 0
2026-05-03 22:52:04 -04:00
00d0102168 chore: merge executor worktree (07-01) 2026-05-03 22:45:36 -04:00
ecc7177832 docs(07-01): complete mobile engagement API endpoints plan summary 2026-05-03 22:45:15 -04:00
c3d370c2f0 feat(07-01): add /api/mobile/engagement/trend endpoint
- GET handler with requireAuth() gate before any DB query (T-07-01)
- Period whitelist ['D7','D30','D90'] with 400 for invalid values (T-07-02)
- generate_series ensures continuous daily series (D-15: no gaps)
- Returns EngagementTrendResponse with D7→7, D30→30, D90→90 SparklinePoints
- Bounded result set: whitelist caps to max 90 rows (T-07-03)
- Exports SparklinePoint and EngagementTrendResponse for Plan 03 import
2026-05-03 22:44:10 -04:00
f4a9fd83db feat(07-01): add /api/mobile/engagement/summary endpoint
- GET handler with requireAuth() gate before any DB query (T-07-01)
- Period whitelist ['D7','D30','D90'] with 400 for invalid values (T-07-02)
- Returns MobileEngagementSummary: configured, activeUsers, totalGraphHours, totalAutotaskHours, hoursPerActiveUser
- Reuses notAutomatedFilter and wulfconsulting email scope from desktop summary
- Exports MobileEngagementSummary interface for Plan 03 page import
2026-05-03 22:43:36 -04:00
1ec561bc29 docs(07): record planning state 2026-05-03 22:41:03 -04:00
d4841a7eb5 docs(07): create phase 7 engagement overview plans 2026-05-03 22:38:21 -04:00
71b20612b2 docs(07): UI design contract 2026-05-03 22:27:13 -04:00
8b6e8b27b1 docs(07): UI design contract for Engagement Overview 2026-05-03 22:22:41 -04:00
192eeb5bb6 docs(state): record phase 7 context session 2026-05-03 22:18:33 -04:00
c85e6347c7 docs(07): capture phase context (auto mode) 2026-05-03 22:18:28 -04:00
97a54fd8d8 docs(phase-06): evolve PROJECT.md after phase completion 2026-05-03 21:44:49 -04:00
2f56d7c808 docs(phase-06): complete phase execution 2026-05-03 21:44:21 -04:00
d26ddc1fae test(06): persist human verification items as UAT 2026-05-03 21:44:13 -04:00
cddf9cac8c docs(06-03): complete mobile analyzer detail page plan summary
- Documents title/company omission from identity block (D-25/D-36 conflict resolution)
- Documents IDOR posture T-06P03-02 as accept-and-flag with STATE.md follow-up recommendation
- Confirms no desktop files touched, TypeScript clean
2026-05-03 21:37:50 -04:00
aa4ff00065 feat(06-03): add mobile analyzer detail page /mobile/analyzer/[id]
- Real segment route reading GET /api/analyzer/analyses/[id] (D-25, reused as-is)
- Three content sections: Summary / Next Step / Next Step Rationale (D-21, ANL-03)
- Identity block: ticket# badge, completed-at relative time, stage pips, confidence badge, Review pill
- Header: back chevron (router.back()) + breadcrumb 'Analyzer / #{ticketNumber}' + external link (D-19)
- Footer: 'View full analysis' link to /analyzer/analysis/[id] with ExternalLink icon, min-h-[44px] (D-22, ANL-04)
- Read-only enforcement: zero form/edit/re-run/cancel controls (D-23, ANL-05)
- Loading skeleton, 404 state, error state with toast (D-28)
- Title/company omitted from identity block per D-25/D-36 (PersistedAnalysis lacks those fields)
2026-05-03 21:36:49 -04:00
86369bd6ab docs(06-02): complete analyzer feed UI components + page plan summary 2026-05-03 21:32:50 -04:00
c8aa69baf6 feat(06-02): replace analyzer placeholder with real feed list page
- Replaces 'coming soon' placeholder with full read-only feed
- useState/useEffect/fetch only (no SWR/react-query per CLAUDE.md D-38)
- IntersectionObserver sentinel with rootMargin 200px for auto-load
- Load more fallback button with aria-label, min-h-[44px] touch target
- 5 AnalyzerRowSkeleton instances on initial load (D-28)
- Empty state with dashed border, Sparkles icon, Open desktop Analyzer link
- toast.error on load failures; Load more flips to Retry on error
- No edit/re-run/prompt-tuning controls (ANL-05)
2026-05-03 21:31:21 -04:00
9c1a74009b feat(06-02): add AnalyzerStagePips, ConfidenceBadge, AnalyzerRowSkeleton, AnalyzerFeedRow components
- AnalyzerStagePips: 3-dot stage indicator with caret separators, sr-only accessibility label
- ConfidenceBadge: High/Medium/Low buckets (0.85/0.65) with green/amber/slate tones, dark mode
- AnalyzerRowSkeleton: Card-wrapped skeleton matching row shape (no border-l-4 per D-11)
- AnalyzerFeedRow: Full card row with header/title/summary/footer, Link to /mobile/analyzer/[id]
2026-05-03 21:30:17 -04:00
fe4fedace0 docs(06-01): complete analyzer feed API plan summary
- Document exported types, SQL approach, kiosk scoping, cursor encoding
- Include notes for Plan 06-02 executors (import path, sample URL)
- Self-check passed: file exists, commit 75238c1 verified, tsc exits 0
2026-05-03 21:25:49 -04:00