# 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:

1. **Mobile sections** — Engagement (`/mobile/engagement`, see §6.5).
2. **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 `ExternalLink` icon hint to signal "leaves mobile shell".
3. **Account** — current user (avatar + email, read-only), then **Sign out**
   (calls `signOut()` then `router.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 from `app/layout.tsx` via `<link rel="manifest">`.
- `viewport` meta in `app/layout.tsx` (or root metadata): include
  `viewport-fit=cover` so 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-pwa` or a custom SW — deferred until a clear offline use-case lands.
- **Tablet** — `md:max-w-2xl mx-auto` wrapper is noted as a follow-up but is
  **not** part of this spec's deliverable. Keep the current `max-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):**
  - `Bell` icon button — **placeholder only**, no menu, no badge logic. Wire
    to `aria-label="Notifications"` and an empty `onClick` so it's
    keyboard-accessible. A future phase will turn this into a real list.
  - Compact user avatar (`Avatar` from shadcn, size `h-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:

1. **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.
2. **"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.
3. **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 (`Collapsible` from 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.tsx` largely 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_analyses` rows via a new
  `/api/mobile/analyzer/feed` endpoint (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

1. PWA scaffolding — `manifest.json`, viewport meta, safe-area utility.
2. New `app/mobile/layout.tsx` — header, More drawer (Sheet), 5-cell bottom nav.
   Delete `app/mobile/nav/page.tsx` in the same change.
3. Dashboard restyle (2×2 grid, Needs Attention, status row).
4. Tickets restyle (filter Collapsible, priority-bar rows, infinite scroll).
   Detail page header reskin only.
5. Finance restyle.
6. Analyzer page + `/api/mobile/analyzer/feed` endpoint.
7. Engagement Overview (`/mobile/engagement`) — phone-first layout from existing data sources.
8. Engagement user profile (`/mobile/engagement/[userId]`) — replaces the desktop modal pattern with a real mobile page.

Each step ships independently — no big-bang merge.
