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>
17 KiB
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 3–7 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/analyzerread-only stream +/api/mobile/analyzer/feed - Phase 7: Engagement Overview (NEW) —
/mobile/engagementphone-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/profilesettings 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):
- Visiting
/manifest.jsonreturns valid JSON withname: "Pulse",display: "standalone",start_url: "/mobile", and theme/background colors matching the app shells - The root
app/layout.tsxreferences the manifest via<link rel="manifest">and the viewport meta includesviewport-fit=cover - 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 - 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):
- 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 - 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-primarybased onpathname.startsWith(href) - 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
ExternalLinkhint), and Account (current user read-only + Sign out) - Tapping Sign out in the drawer signs the user out and lands them on
/auth/sign-in app/mobile/nav/page.tsxno longer exists; visiting/mobile/navdoes not render the old standalone nav page- 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):
- Dashboard renders a 2×2 grid of four primary KPI cards drawn from desktop hero stats (no 1×4 row, no charts)
- Below the grid, a "Needs Attention" horizontally-scrollable strip surfaces overdue tickets, failed backups, and stalled workflows; tapping a card opens its detail view
- A compact status row shows analyzer worker, RMM worker, and backup-success-rate; tapping any element opens the corresponding desktop admin page
- 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):
- 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)
- 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
- Single-tapping a row navigates to
/mobile/tickets/[id] - 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
- 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):
/mobile/financeadopts the new Card and typography scale — no horizontal overflow, spacing legible on small phones- 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):
- Tapping the Analyzer tab in the bottom nav lands on
/mobile/analyzerand shows a most-recent-first list of AI ticket analyses - Each row shows ticket #, title, the analyzer's one-line summary, a confidence badge, and a stage indicator (Triage → Analyze → Deep Review)
- 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
- The mobile feed never exposes editing, re-run, or prompt-tuning controls (read-only by design)
- The list reads from
analyzer_analysesvia/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):
- The Mobile sections row in the More drawer links to
/mobile/engagement; the Analyzer is on the bottom bar but Engagement is not - The overview page shows a period selector (today / 7d / 30d) sticky just below the H1, with active period clearly indicated
- 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
- 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)
- 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):
- 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 - 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)
- Authenticated
GET /api/me/timezonereturns the user's tz;PUT /api/me/timezoneaccepts an IANA string and rejects anything not inIntl.supportedValuesOf('timeZone') - A shared client hook (
useUserTimezone()) reads the value fromuseSession()so all components use a single source of truth — no per-pageIntlcalls scattered around - 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):
- Tapping a row in the per-employee list navigates to
/mobile/engagement/[userId](segment form, shareable URL) - The profile is a real page (not a modal) — the device/browser back gesture returns to the overview at the same scroll position
- 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):
- Tapping Profile/Account in the More drawer routes to
/mobile/profile(real page, not modal) - Page exposes four sections — Timezone, Theme, Notifications, Channels — each persisting per-user; cross-device consistent; gated by
requireAuth() - Notification routing in
lib/services/pipeline-steps/notify.tsresolves 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 3–7 (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