wulf-pulse/.planning/ROADMAP.md
lorentz 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

17 KiB
Raw Blame History

Roadmap: Pulse Mobile Shell Redesign

Overview

Eight phases mirror the deliberate build order in the source spec (docs/superpowers/specs/2026-05-03-mobile-shell-design.md §8). Each phase ships independently to master — no big-bang merge. Phase 1 lays PWA metadata and safe-area utilities. Phase 2 rebuilds app/mobile/layout.tsx with the new header, 5-cell bottom nav, and More drawer (deleting /mobile/nav in the same change). Once the shell lands, Phases 37 are independent restyles/new pages and may be executed in parallel; Phase 8 follows Phase 7 because the user profile is reached from the Engagement overview. All work happens in place under /mobile/* — no /mobile-v2, no parallel routes.

Phases

Phase Numbering:

  • Integer phases (1, 2, 3): Planned milestone work
  • Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)

Decimal phases appear between their surrounding integers in numeric order.

  • Phase 1: PWA Scaffolding — Manifest, viewport meta, and safe-area utilities so the shell installs and paints under the home indicator
  • Phase 2: Mobile Shell + More Drawer — New app/mobile/layout.tsx (header + 5-cell bottom nav) and Sheet drawer that replaces /mobile/nav
  • Phase 3: Dashboard Restyle — 2×2 KPI grid, Needs Attention strip, worker/backup status row (no charts)
  • Phase 4: Tickets Restyle — Collapsible URL-synced filters, priority-bar rows, cursor-based infinite scroll, detail header reskin
  • Phase 5: Finance Restyle — Adopt new Card + typography scale, swap wide tables for stacked lists (completed 2026-05-03)
  • Phase 6: Analyzer Feed (NEW)/mobile/analyzer read-only stream + /api/mobile/analyzer/feed
  • Phase 7: Engagement Overview (NEW)/mobile/engagement phone-first overview reachable from the More drawer
  • Phase 7.1: User Timezone Fix (INSERTED — urgent) — Per-user IANA timezone column + viewer-tz date math so dashboards and filters render the right "today"
  • Phase 8: Engagement User Profile (NEW)/mobile/engagement/[userId] real-page profile that replaces the desktop modal pattern
  • Phase 9: User Profile & Preferences (NEW)/mobile/profile settings page (timezone chooser, theme, mobile push, Teams + ntfy channels)

Phase Details

Phase 1: PWA Scaffolding

Goal: A manager who taps "Add to Home Screen" gets a standalone Pulse icon that opens to the mobile shell with content respecting the device safe areas. Depends on: Nothing (first phase) Requirements: PWA-01, PWA-02, PWA-03, PWA-04 Success Criteria (what must be TRUE):

  1. Visiting /manifest.json returns valid JSON with name: "Pulse", display: "standalone", start_url: "/mobile", and theme/background colors matching the app shells
  2. The root app/layout.tsx references the manifest via <link rel="manifest"> and the viewport meta includes viewport-fit=cover
  3. A safe-area utility (Tailwind arbitrary values or shared class) is available so any sticky top/bottom bar can opt into env(safe-area-inset-top) / env(safe-area-inset-bottom) padding
  4. Installing Pulse to a phone home screen launches a chromeless app pointed at /mobile (no service worker, no offline) Plans: 2 plans
  • 01-01-PLAN.md — Web App Manifest + viewport-fit=cover (PWA-01, PWA-02, PWA-03)
  • 01-02-PLAN.md — Safe-area pt-safe / pb-safe @utility blocks in brand.css (PWA-04, gap closure) UI hint: no

Phase 2: Mobile Shell + More Drawer

Goal: Every /mobile/* page renders inside a new layout — sticky header (Wulf mark + Bell placeholder + avatar), scrollable content, and a 5-cell bottom nav whose fifth control opens a Sheet drawer that fully replaces /mobile/nav. Depends on: Phase 1 Requirements: SHELL-01, SHELL-02, SHELL-03, SHELL-04, SHELL-05, SHELL-06, NAV-01, NAV-02, NAV-03, DRAWER-01, DRAWER-02, DRAWER-03, DRAWER-04, DRAWER-05, DRAWER-06 Success Criteria (what must be TRUE):

  1. On any /mobile/* route the user sees a sticky header with the Wulf wordmark linking to /mobile/dashboard, a Bell icon button (keyboard-focusable, no menu), and a compact avatar — no page title in the header
  2. A fixed bottom bar exposes four primary tabs (Dashboard, Tickets, Finance, Analyzer) plus a More cell; tapping a tab routes to its page and the active tab uses text-primary based on pathname.startsWith(href)
  3. Tapping More (or the header avatar) opens a single Sheet drawer with three sections — Mobile sections (Engagement), Full site (Quotes, Configuration Items, Backup Status, Ticket Digest, Admin/Sync — each with an ExternalLink hint), and Account (current user read-only + Sign out)
  4. Tapping Sign out in the drawer signs the user out and lands them on /auth/sign-in
  5. app/mobile/nav/page.tsx no longer exists; visiting /mobile/nav does not render the old standalone nav page
  6. Page content scrolls under the sticky header and is not hidden behind the bottom nav (bottom padding accounts for nav height + safe-area inset) Plans: 2 plans
  • 02-01-PLAN.md — Build mobile shell components (HeaderBar, BottomNav, MoreDrawer) + analyzer placeholder (SHELL-02..04, SHELL-06, NAV-01..03, DRAWER-01..05)
  • 02-02-PLAN.md — Wire new components into app/mobile/layout.tsx, delete app/mobile/nav/page.tsx (SHELL-01, SHELL-05, DRAWER-06) UI hint: yes

Phase 3: Dashboard Restyle

Goal: A manager opening /mobile/dashboard sees the state of the business at a glance — four KPIs, items needing attention, and a worker/backup status row — with no charts. Depends on: Phase 2 Requirements: DASH-01, DASH-02, DASH-03, DASH-04 Success Criteria (what must be TRUE):

  1. Dashboard renders a 2×2 grid of four primary KPI cards drawn from desktop hero stats (no 1×4 row, no charts)
  2. Below the grid, a "Needs Attention" horizontally-scrollable strip surfaces overdue tickets, failed backups, and stalled workflows; tapping a card opens its detail view
  3. A compact status row shows analyzer worker, RMM worker, and backup-success-rate; tapping any element opens the corresponding desktop admin page
  4. The page contains no recharts/chart components on phone widths Plans: 2 plans
  • 03-01-PLAN.md — /api/mobile/dashboard reshape + KpiCardMobile/NeedsAttentionStrip/WorkerStatusRow components (DASH-01, DASH-02, DASH-03)
  • 03-02-PLAN.md — Replace /mobile/dashboard page body with 3-section layout, no charts (DASH-01, DASH-02, DASH-03, DASH-04) UI hint: yes

Phase 4: Tickets Restyle

Goal: A manager triages tickets on a phone with a collapsible filter bar that deep-links via URL, priority-coloured rows, and infinite scroll — and the detail page header matches the new shell. Depends on: Phase 2 Requirements: TICK-01, TICK-02, TICK-03, TICK-04, TICK-05, TICK-06, TICK-07 Success Criteria (what must be TRUE):

  1. The Tickets page opens with the filter strip collapsed; expanding it reveals status, priority, queue, and an assigned-to-me toggle, and changing any filter updates the URL query string (deep link works on reload)
  2. Each list row has a left-edge stripe matching priority (Critical/High/Medium/Low → red/orange/amber/slate) and shows ticket #, title, company, age, and assignee
  3. Single-tapping a row navigates to /mobile/tickets/[id]
  4. Scrolling to the bottom of the list automatically loads the next ~25 rows (no Next button); a "Load more" fallback button is also visible/focusable for accessibility
  5. The detail page header uses the new shell styling (Wulf mark, breadcrumb back) while the body remains largely unchanged Plans: 3 plans
  • 04-01-PLAN.md — /api/mobile/tickets cursor rewrite + TicketFilterStrip + TicketRowSkeleton components (TICK-01, TICK-02, TICK-05)
  • 04-02-PLAN.md — Replace app/mobile/tickets/page.tsx with URL-synced filters, priority-stripe rows, IntersectionObserver infinite scroll (TICK-01..TICK-06)
  • 04-03-PLAN.md — Reskin in-page header of app/mobile/tickets/[id]/page.tsx (back chevron + breadcrumb + ExternalLink) (TICK-07) UI hint: yes

Phase 5: Finance Restyle

Goal: A manager reading AR / invoice / payment status on a phone sees properly spaced cards and stacked lists instead of squished wide tables — same data, new shell. Depends on: Phase 2 Requirements: FIN-01, FIN-02 Success Criteria (what must be TRUE):

  1. /mobile/finance adopts the new Card and typography scale — no horizontal overflow, spacing legible on small phones
  2. Sections that previously rendered wide tables on phone widths now render as stacked lists (no new sections, no new data sources) Plans: 2 plans
  • 05-01-PLAN.md — FinanceRow + FinanceSkeleton helper components (FIN-01, FIN-02)
  • 05-02-PLAN.md — Rewrite app/mobile/finance/page.tsx to KPI grid + stacked lists + shadcn Collapsibles (FIN-01, FIN-02) UI hint: yes

Phase 6: Analyzer Feed (NEW)

Goal: A manager taps the Analyzer tab and skims a most-recent-first stream of AI ticket analyses, opening any one to a phone-friendly summary view that links out to desktop for full details. Depends on: Phase 2 Requirements: ANL-01, ANL-02, ANL-03, ANL-04, ANL-05, ANL-06 Success Criteria (what must be TRUE):

  1. Tapping the Analyzer tab in the bottom nav lands on /mobile/analyzer and shows a most-recent-first list of AI ticket analyses
  2. Each row shows ticket #, title, the analyzer's one-line summary, a confidence badge, and a stage indicator (Triage → Analyze → Deep Review)
  3. Tapping a row opens a mobile summary view rendering Summary, Next Step, and Next Step Rationale, with a "View full analysis" link out to the desktop analyzer page
  4. The mobile feed never exposes editing, re-run, or prompt-tuning controls (read-only by design)
  5. The list reads from analyzer_analyses via /api/mobile/analyzer/feed (or a reused list endpoint that already returns the right shape) Plans: 3 plans
  • 06-01-PLAN.md — /api/mobile/analyzer/feed endpoint with cursor pagination + kiosk_settings scoping (ANL-01, ANL-02, ANL-06)
  • 06-02-PLAN.md — AnalyzerFeedRow/StagePips/ConfidenceBadge/RowSkeleton components + replace /mobile/analyzer placeholder with feed list page (ANL-01, ANL-02, ANL-05, ANL-06)
  • 06-03-PLAN.md — /mobile/analyzer/[id] detail page reading existing /api/analyzer/analyses/[id] (ANL-03, ANL-04, ANL-05) UI hint: yes

Phase 7: Engagement Overview (NEW)

Goal: A manager reaches Engagement from the More drawer and sees a phone-first overview — period chips, stacked summary cards, a sortable per-employee list, and one compact sparkline. Depends on: Phase 2 Requirements: ENG-01, ENG-02, ENG-03, ENG-04, ENG-05, ENG-09 Success Criteria (what must be TRUE):

  1. The Mobile sections row in the More drawer links to /mobile/engagement; the Analyzer is on the bottom bar but Engagement is not
  2. The overview page shows a period selector (today / 7d / 30d) sticky just below the H1, with active period clearly indicated
  3. Summary cards (active users, total Graph hours, total Autotask hours, hours-per-active-user) render single-column stacked — no 4-up grid on phone widths
  4. The per-employee list renders as stacked rows (avatar/initials, name, role, hours bar) with a search input and a sort control above (sort by hours, name, utilization)
  5. A single compact "hours trend" sparkline renders at the top of the list, scoped to the selected period — no multi-series chart Plans: 3 plans
  • 07-01-PLAN.md — /api/mobile/engagement/summary + /api/mobile/engagement/trend endpoints with period whitelist + requireAuth (ENG-03, ENG-05)
  • 07-02-PLAN.md — Engagement* mobile components (PeriodChips, SummaryCard, HoursSparkline, SortChips, SearchInput, UserRow, UserRowSkeleton + getInitials utility) (ENG-02, ENG-03, ENG-04, ENG-05)
  • 07-03-PLAN.md — app/mobile/engagement/page.tsx orchestration (period/sort state, IntersectionObserver, empty/error/not-configured states) (ENG-01, ENG-02, ENG-03, ENG-04, ENG-05, ENG-09) UI hint: yes

Phase 7.1: User Timezone Fix (INSERTED — urgent)

Goal: A user opening Pulse sees dashboards, filters, and "today/this week" date math computed in their own IANA timezone — not server UTC — so reports stop showing yesterday's data as today (and vice versa). Persistence layer remains UTC; only the read/display path changes. Depends on: Nothing structural (Better Auth users table extension + read-path changes) Requirements: TZ-01, TZ-02, TZ-03, TZ-04 Success Criteria (what must be TRUE):

  1. Each user has an IANA timezone (e.g. America/New_York) persisted server-side; default = process.env.DEFAULT_TIMEZONE || 'UTC' for users with no value yet
  2. Mobile and desktop dashboards, ticket filters, finance views, and engagement period selectors compute day/week boundaries against the viewer's timezone — not UTC and not the browser's local zone (browser zone may differ from the user's chosen zone, e.g. travel)
  3. Authenticated GET /api/me/timezone returns the user's tz; PUT /api/me/timezone accepts an IANA string and rejects anything not in Intl.supportedValuesOf('timeZone')
  4. A shared client hook (useUserTimezone()) reads the value from useSession() so all components use a single source of truth — no per-page Intl calls scattered around
  5. Existing UTC-stored data stays untouched (no destructive migration); only formatting and range-bucketing change Plans: 5 plans
  • 07.1-01-PLAN.md — Add timezone column to user table + Better Auth additionalField (TZ-01)
  • 07.1-02-PLAN.md — /api/me/timezone GET + PUT with IANA validation (TZ-03)
  • 07.1-03-PLAN.md — Server-side read paths use user.timezone for day/week/month boundaries; auth-gates /api/mobile/finance; migrates /api/dashboard/trends (TZ-02)
  • 07.1-04-PLAN.md — useUserTimezone() client hook + reported-bug-surface mobile page migration + codebase-wide audit (TZ-04, TZ-02 client portion)
  • 07.1-05-PLAN.md — Codebase-wide useUserTimezone() adoption per the Plan 04 audit (TZ-04 SC#4 single-source-of-truth at codebase scale) UI hint: no (this is a data/plumbing phase; the picker UI is part of Phase 9)

Phase 8: Engagement User Profile (NEW)

Goal: From the Engagement overview, a manager taps an employee row and arrives at a real, shareable profile page — single-column phone-first — and the device back gesture returns them to the overview. Depends on: Phase 7 Requirements: ENG-06, ENG-07, ENG-08 Success Criteria (what must be TRUE):

  1. Tapping a row in the per-employee list navigates to /mobile/engagement/[userId] (segment form, shareable URL)
  2. The profile is a real page (not a modal) — the device/browser back gesture returns to the overview at the same scroll position
  3. The profile renders single-column: identity header → period selector → key metrics (compact) → activity breakdown list → recent items, sourced from the existing engagement profile data endpoints (no new data) Plans: TBD UI hint: yes

Phase 9: User Profile & Preferences (NEW)

Goal: A logged-in user reaches a profile/settings page from the More drawer and can configure timezone (chooser UI for TZ-01), theme (light/dark/system, persisted server-side for cross-device consistency), mobile push notifications (per-event toggles), and personal notification channels (Teams webhook URL, ntfy topic). Changes persist per-user and the existing notify pipeline routes through these per-user channels for events the user is subscribed to. Depends on: Phase 7.1 (timezone schema), Phase 2 (More drawer) Requirements: TBD — flesh out via /gsd-discuss-phase 9 before planning Success Criteria (what must be TRUE):

  1. Tapping Profile/Account in the More drawer routes to /mobile/profile (real page, not modal)
  2. Page exposes four sections — Timezone, Theme, Notifications, Channels — each persisting per-user; cross-device consistent; gated by requireAuth()
  3. Notification routing in lib/services/pipeline-steps/notify.ts resolves per-user channel preferences when the event has a user owner, falling back to global channels otherwise Plans: TBD UI hint: yes

Progress

Execution Order: Phases execute in numeric order. Phase 2 unblocks Phases 37 (any order, parallelizable). Phase 8 follows Phase 7.

Phase Plans Complete Status Completed
1. PWA Scaffolding 1/2 Executing -
2. Mobile Shell + More Drawer 0/TBD Not started -
3. Dashboard Restyle 0/2 Not started -
4. Tickets Restyle 0/3 Not started -
5. Finance Restyle 2/2 Complete 2026-05-03
6. Analyzer Feed 0/3 Not started -
7. Engagement Overview 0/3 Not started -
7.1. User Timezone Fix 0/5 Not started -
8. Engagement User Profile 0/TBD Not started -
9. User Profile & Preferences 0/TBD Not started -

Roadmap created: 2026-05-03 Source spec: docs/superpowers/specs/2026-05-03-mobile-shell-design.md