Bundles several in-progress efforts that were sitting uncommitted: - User queue-preferences (migration 087, API route, popover component) - QBO invoice soft-delete (migration 088) and AR diagnostics route - Dashboard/mobile engagement route and page adjustments - Docker Compose log-rotation config - One-off ticket/RMM investigation scripts (scripts/) - Planning docs: phase verification/pattern notes, mobile shell design spec - .gitignore: exclude local scratch financial/inventory data and Claude Code worktree/local-settings runtime state (never meant for version control) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
10 KiB
Mobile Shell Redesign — Design Spec
Date: 2026-05-03
Scope: /mobile/* shell, navigation, and per-page layouts on Pulse.
1. Goal & audience
Pulse's mobile shell exists for managers on the go — quick status checks, triage decisions, and read-only awareness. It is not a full replacement for the desktop app; pages where mobile editing isn't justified should link out to the desktop equivalent.
Success means a manager can:
- See the state of the business at a glance (Dashboard).
- Triage and inspect tickets (Tickets).
- Read AR / invoice / payment status (Finance).
- Skim recent AI ticket analyses (Analyzer).
- Reach Engagement and sign-out without clutter on the primary nav.
2. Approach
Rebuild /mobile in place. Keep all existing /mobile/* route paths
(/mobile/dashboard, /mobile/tickets, /mobile/tickets/[id],
/mobile/finance). Replace /mobile/layout.tsx, the four page files, and
delete /mobile/nav (its content moves into a Sheet drawer — see §3).
No new top-level routes. No parallel /mobile-v2 directory. The existing pages
stay their canonical URLs throughout the redesign.
3. Navigation
3.1 Bottom tab bar (primary)
Four tabs, equal-width, fixed to the bottom of the viewport:
| Tab | Icon (lucide) | Route |
|---|---|---|
| Dashboard | LayoutDashboard |
/mobile/dashboard |
| Tickets | Ticket |
/mobile/tickets |
| Finance | DollarSign |
/mobile/finance |
| Analyzer | Sparkles |
/mobile/analyzer |
Active state: text-primary; inactive: text-muted-foreground. Active
detection via pathname.startsWith(href).
3.2 "More" Sheet drawer (secondary)
A fifth control on the bottom bar — Menu icon labelled More — opens a
shadcn Sheet (side="right", or "bottom" on phones; pick one and stay
consistent). The drawer replaces the standalone /mobile/nav page entirely.
Drawer contents, top to bottom:
- Mobile sections — Engagement (
/mobile/engagement, see §6.5). - Full site — link list to desktop pages that have no mobile view yet
(Quotes, Configuration Items, Backup Status, Ticket Digest, Admin / Sync).
Each link uses the
ExternalLinkicon hint to signal "leaves mobile shell". - Account — current user (avatar + email, read-only), then Sign out
(calls
signOut()thenrouter.push('/auth/sign-in')).
Delete app/mobile/nav/page.tsx after the drawer is in place.
4. PWA setup
public/manifest.json— name "Pulse", short_name "Pulse",display: "standalone",start_url: "/mobile", theme/background colors matching the dark and light shells. Reference it fromapp/layout.tsxvia<link rel="manifest">.viewportmeta inapp/layout.tsx(or root metadata): includeviewport-fit=coverso the shell can paint behind the home indicator.- Safe-area insets — both the sticky header and the bottom tab bar add
env(safe-area-inset-top)/env(safe-area-inset-bottom)padding. Use Tailwind arbitrary values (pt-[env(safe-area-inset-top)],pb-[env(safe-area-inset-bottom)]) or a small utility class. - No service worker, no offline mode in this iteration. Don't ship
next-pwaor a custom SW — deferred until a clear offline use-case lands. - Tablet —
md:max-w-2xl mx-autowrapper is noted as a follow-up but is not part of this spec's deliverable. Keep the currentmax-w-lg.
5. Shell (app/mobile/layout.tsx)
5.1 Header
Sticky top, bg-background/95 backdrop-blur, bottom border. Three slots:
- Left: Wulf mark (existing logo asset) + "Pulse" wordmark, linked to
/mobile/dashboard. Use the actual brand mark, not the text-only fallback. - Right (in order):
Bellicon button — placeholder only, no menu, no badge logic. Wire toaria-label="Notifications"and an emptyonClickso it's keyboard-accessible. A future phase will turn this into a real list.- Compact user avatar (
Avatarfrom shadcn, sizeh-7 w-7). On tap, opens the More drawer (so the avatar is the entry point alongside the bottom-bar More button).
No page title in the header — pages render their own H1 in the content area.
5.2 Content area
<main> between header and bottom nav, scrollable. Add bottom padding equal to
the bottom-nav height + safe-area inset so content doesn't hide under the bar.
5.3 Bottom nav
Fixed, full-width, border-t bg-background. Wraps in max-w-lg mx-auto so
content and nav share the same gutter. Five cells: 4 tabs (§3.1) + More (§3.2).
6. Pages
6.1 Dashboard (/mobile/dashboard)
Sections, top to bottom:
- 2×2 KPI grid — four primary metric cards. KPIs to choose from the desktop dashboard's hero stats; final selection during build, but the layout is fixed at 2×2 on phone widths.
- "Needs Attention" strip — horizontal scroll of compact cards surfacing items that require manager action (overdue tickets, failed backups, stalled workflows). Each card opens its detail view directly.
- Backup / worker status — compact status row showing the analyzer worker, RMM worker, and backup-success-rate at a glance. Read-only; tap opens the desktop admin page.
No charts on the mobile Dashboard in this iteration — recharts on small widths isn't earning its weight.
6.2 Tickets (/mobile/tickets)
- Collapsible filter strip at top (
Collapsiblefrom shadcn, default collapsed). When expanded: status, priority, queue, assigned-to-me toggle. Filter state syncs to the URL query string so deep-linking works. - Priority-bar rows — list rows have a left-edge color stripe by priority (Critical/High/Medium/Low → red/orange/amber/slate). Body shows ticket #, title, company, age, assignee. Single-tap opens detail.
- Infinite scroll — replace the current pagination with cursor-based infinite scroll. Fetch in pages of ~25; trigger next page when the last row enters the viewport (IntersectionObserver). Keep a "Load more" fallback button for accessibility.
- Detail page — keep
/mobile/tickets/[id]/page.tsxlargely as-is; only reskin the header to match the new shell (Wulf mark, breadcrumb back).
6.3 Finance (/mobile/finance)
Restyle of the existing page only. No new data, no new sections. Adopt the new Card and typography scale, fix any spacing that breaks on small phones. If a section currently relies on a wide table, swap it for a stacked list on mobile.
6.4 Analyzer (/mobile/analyzer) — NEW
Read-only feed of recent AI ticket analyses.
- List view: most-recent-first stream of analyses. Each row shows ticket #, title, the analyzer's one-line summary, confidence badge, and a stage indicator (Triage → Analyze → Deep Review). Tap opens a mobile summary view.
- Mobile summary view: renders Summary, Next Step, Next Step Rationale (all already produced by the analyzer pipeline). Includes a "View full analysis" link out to the desktop analyzer page.
- No editing, no re-run, no prompt tuning on mobile.
- Source data: existing
analyzer_analysesrows via a new/api/mobile/analyzer/feedendpoint (or reuse an existing list endpoint if one already returns the right shape).
6.5 Engagement (/mobile/engagement) — NEW, accessed via More drawer
Engagement gets a real mobile refactor, not a thin adaptation. The desktop
page (app/engagement/page.tsx, ~1300 lines) and profile page
(app/engagement/profile/page.tsx, ~650 lines) both rely on wide tables, dense
charts, and modal patterns that don't translate to phone widths. Build the
mobile views from the same data sources but with phone-first layouts.
Overview (/mobile/engagement):
- Period selector chip row (today / 7d / 30d) — sticky just below the page H1.
- Summary cards stacked single-column (active users, total Graph hours, total Autotask hours, hours-per-active-user). No 4-up grid; reading numbers across a 4-up row on a phone is a non-starter.
- Per-employee list as stacked rows (avatar / initials, name, role, hours bar). Sortable via a small control above the list (sort by hours, name, utilization). Search input above the list.
- Charts: replace the desktop's wide bar/line charts with one compact "hours trend" sparkline at the top of the list, period-scoped. No multi-series chart on mobile in this iteration.
User profile (/mobile/engagement/profile?userId=... or
/mobile/engagement/[userId]):
- Reuses the existing profile data endpoints. Layout is single-column: identity header → period selector → key metrics (compact) → activity breakdown list → recent items.
- This replaces the desktop user-detail modal pattern on mobile — tapping a row navigates to a real page, not a modal, so the shell's back gesture works correctly.
- Pick exactly one of the two route shapes above during build (prefer the
[userId]segment form for shareable URLs); don't ship both.
Engagement is not on the bottom bar — it lives in the More drawer because managers don't check it as often as the four primary surfaces.
7. Out of scope (explicit non-goals)
- Service worker, offline cache, push notifications.
- Tablet breakpoint (md:max-w-2xl) — noted, deferred.
- Real notification list behind the bell.
- Mobile editing on Engagement (user detail) or Analyzer (re-run, prompt edits).
- Charts / recharts on mobile Dashboard.
- Replacing or restyling the desktop pages reachable from the More drawer.
8. Build order
- PWA scaffolding —
manifest.json, viewport meta, safe-area utility. - New
app/mobile/layout.tsx— header, More drawer (Sheet), 5-cell bottom nav. Deleteapp/mobile/nav/page.tsxin the same change. - Dashboard restyle (2×2 grid, Needs Attention, status row).
- Tickets restyle (filter Collapsible, priority-bar rows, infinite scroll). Detail page header reskin only.
- Finance restyle.
- Analyzer page +
/api/mobile/analyzer/feedendpoint. - Engagement Overview (
/mobile/engagement) — phone-first layout from existing data sources. - Engagement user profile (
/mobile/engagement/[userId]) — replaces the desktop modal pattern with a real mobile page.
Each step ships independently — no big-bang merge.