docs(07): capture phase context (auto mode)

This commit is contained in:
lorentz 2026-05-03 22:18:28 -04:00
parent 97a54fd8d8
commit c85e6347c7
2 changed files with 579 additions and 0 deletions

View file

@ -0,0 +1,427 @@
# Phase 7: Engagement Overview (NEW) - Context
**Gathered:** 2026-05-04 (auto mode)
**Status:** Ready for planning
<domain>
## Phase Boundary
Build a phone-first refactor of the Engagement overview at `/mobile/engagement`
— accessed exclusively from the More drawer (NOT the bottom bar). The page
gives a manager a quick read on team engagement: a sticky 3-chip period
selector under the H1, four stacked summary cards (active users / total Graph
hours / total Autotask hours / hours-per-active-user), one compact "hours
trend" sparkline at the top of the per-employee list, and a sortable +
searchable list of stacked employee rows (avatar/initials, name, role,
hours bar) sourced from the existing engagement data layer.
In scope:
- New page `app/mobile/engagement/page.tsx`
- 2 new mobile endpoints: `/api/mobile/engagement/summary` (4 totals for
ENG-03) and `/api/mobile/engagement/trend` (daily hours time-series for
ENG-05 sparkline)
- Reuse of existing `/api/engagement/users` for the per-employee list
(ENG-04)
- New components: `EngagementPeriodChips`, `EngagementSummaryCard`,
`EngagementHoursSparkline`, `EngagementSortChips`, `EngagementSearchInput`,
`EngagementUserRow`, `EngagementUserRowSkeleton`
- Confirm More drawer entry to `/mobile/engagement` is correctly wired
(Phase 2 already added it per DRAWER-03; verify and don't regress)
Out of scope:
- The user profile page at `/mobile/engagement/[userId]` — that's Phase 8
(ENG-06..08)
- Multi-series charts on mobile (explicit spec out-of-scope §6.5, §7)
- Mobile editing (read-only by design — PROJECT.md Out of Scope, REQ
EDIT-01)
- "Today" period chip — engagement_snapshots only aggregate at D7/D30/D90
granularity; a "today" period would require a new D1 sync and is out of
spec scope for this phase
- Modifying desktop `/engagement/*` pages or APIs (PROJECT.md Out of Scope:
"Restyling or replacing the desktop pages…")
- Sort by anything beyond the 3 ENG-04 axes (hours / name / utilization)
- Server-side search (client-side filter satisfies ENG-04)
</domain>
<decisions>
## Implementation Decisions
### Page route, drawer entry, and shell integration
- **D-01:** New page at `app/mobile/engagement/page.tsx`. The More drawer
already routes here per `DRAWER-03` (Phase 2). Verify the route works end-
to-end after this phase lands; do not change `MoreDrawer.tsx`.
- **D-02:** ENG-09 enforcement — Engagement is NOT on the bottom nav. The
Phase 2 `BottomNav.tsx` is already correct (4 tabs + More cell, no
Engagement). Do not modify `BottomNav.tsx`.
- **D-03:** Page is `'use client'` + `useState` + `useEffect` + `fetch`
(CLAUDE.md: no SWR/react-query, no new state libs).
### Period selector (ENG-02)
- **D-04:** 3 chips: `7d`, `30d`, `90d`. Mapped 1:1 to the data layer's
`period_type` values `D7`, `D30`, `D90` (per
`migrations/041_create_engagement_tables.sql`). Default: `30d` (`D30`)
— matches the existing endpoints' default.
- **D-05:** Sticky just below the page H1: `sticky top-0 z-10 bg-background
pt-2 pb-3 -mx-4 px-4` (offset for the page padding so the chips run
edge-to-edge of the shell while content above scrolls).
- **D-06:** Active chip: `bg-primary text-primary-foreground`. Inactive:
`bg-muted text-foreground hover:bg-muted/80`. Chip shape: `rounded-full
px-3 py-1.5 text-xs font-semibold` (matches mobile compact-control
density). Three chips in a horizontal `flex gap-2` row, no scroll.
- **D-07:** Spec text says "today / 7d / 30d" but the data layer offers
only D7/D30/D90 aggregates. We follow the data layer and document this as
a deviation. "Today" is captured in `<deferred>` for a future D1 sync.
### Summary cards (ENG-03)
- **D-08:** 4 cards stacked single-column (no 4-up grid on phone widths).
In order:
1. **Active users** — count of users with engagement activity in the
selected period (matches existing `summary.activeThisPeriod`)
2. **Total Graph hours** — total Microsoft Graph activity hours
(`audio_duration_seconds + meeting_duration_seconds`, summed across
all active users in the period, converted to hours with 1 decimal)
3. **Total Autotask hours** — total `time_entries.hours_worked` across
all human resources matching graph_users in the period
4. **Hours per active user**`totalAutotaskHours / activeUsers` (one
decimal). If `activeUsers == 0`, render "—"
- **D-09:** New endpoint `/api/mobile/engagement/summary` returns these 4
metrics directly, accepting `period=D7|D30|D90`. Reason: existing
`/api/engagement/summary` returns *averages* (avgHoursWorked,
avgBillableHours, etc.), not the totals ENG-03 specifies. A thin mobile
endpoint is cleaner than computing on the client.
- **D-10:** Card visual: shadcn `Card` + `CardContent`. Big number
(`text-2xl font-semibold`), small label below (`text-xs
text-muted-foreground`). One card per row, `space-y-3` between cards.
Cards have no shadow, just border (matches FinanceRow density).
### Hours trend sparkline (ENG-05)
- **D-11:** Single compact sparkline at the top of the per-employee list,
scoped to the selected period. Series: total Autotask hours per day for
the period.
- **D-12:** Custom inline SVG sparkline component — `EngagementHoursSparkline`
— takes `points: { date: string; hours: number }[]` and renders a 3rem-
tall path. Reason: DASH-04 precedent (no recharts on mobile); spec §6.5
("no multi-series chart on mobile in this iteration"). One series, no
axes, no tooltips. A faint baseline at 0 and a single colored line
(`stroke-primary stroke-2 fill-none`).
- **D-13:** Sparkline card has a small label row: `Hours trend · last
{period_label}` left, latest-value (e.g. `12.4h today`) right, both
`text-xs text-muted-foreground`. Card height ~80px total.
- **D-14:** New endpoint `/api/mobile/engagement/trend` returns `{ points:
{ date: string; hours: number }[] }` for the period. Aggregates daily
totals across `time_entries` joined to graph_users (same scope as the
summary card filter). Period maps: D7 → 7 daily points, D30 → 30 daily
points, D90 → 90 daily points (or 30 grouped weekly for D90 if perf
matters — planner decides if 90 daily points renders cleanly at narrow
viewport).
- **D-15:** When `points` is empty, render the card with the period label
and "No activity" inline — no broken empty SVG.
### Per-employee list (ENG-04)
- **D-16:** Reuse existing `/api/engagement/users` directly, no mobile
wrapper. Existing response shape (`{ users[], pagination }`) is suitable.
- **D-17:** Initial fetch: `?period={D7|D30|D90}&sort=billable_hours&order=desc&page=1`
(page size 50 is the existing endpoint's fixed value).
- **D-18:** Pagination strategy: page-based, infinite scroll via
IntersectionObserver (mirror Phase 4/6 pattern). Sentinel triggers
`?page=N+1` fetch when last row enters viewport. "Load more" fallback
button below the sentinel, hidden when `pagination.totalPages` reached.
Most teams have ≤50 staff so most users will only see one page.
- **D-19:** Row shape (stacked card per user):
- **Top line:** Avatar circle (initials from `displayName`, h-8 w-8) +
`displayName` (`text-sm font-semibold`, 1-line truncate) + role
(`jobTitle` if present, `text-xs text-muted-foreground`, 1-line
truncate) — left side; total billable hours on right (`text-sm
font-semibold`, e.g. "12.4h")
- **Hours bar:** `<div className="h-1.5 bg-muted rounded">` with inner
`<div>` width = `min(100%, billableHours / maxRowHours * 100%)`,
`bg-primary`. `maxRowHours` = the largest billable hours value in the
current page (computed client-side after fetch).
- **Tap target:** wraps in `<Link href={\`/mobile/engagement/\${graphUserId}\`}>`
so Phase 8 (already-planned) can pick up navigation. Even though
Phase 8 builds the destination page, the link is wired here so Phase
7's row component is feature-complete; Phase 8 owns the page that
receives the tap.
### Sort + search (ENG-04)
- **D-20:** Sort control above the list: 3 chips — `Hours`, `Name`,
`Utilization`. Active chip: `bg-primary text-primary-foreground`.
Tapping a chip triggers a refetch with the corresponding `sort` param.
Default: `Hours` desc.
- `Hours``sort=billable_hours&order=desc`
- `Name``sort=display_name&order=asc`
- `Utilization``sort=billable_hours&order=desc` (utilization isn't a
direct sort on the endpoint; we sort by billable_hours and visually
compute `billableHours / hoursWorked` ratio in the row. If a planner
finds this confusing, pivot to `sort=hours_worked` and compute
utilization = `billable / total` × 100% in row content)
- **D-21:** Search input above the list: a single `<Input>` placeholder
"Search by name or email", debounced 300ms, filters loaded users
client-side (no server search param). Filter applies AFTER fetch — so
it's instant on the visible page set but won't auto-load more pages
when filter narrows results. Acceptable for ≤50-user teams; document
in `<deferred>` if scale grows.
- **D-22:** When the search filter has no results on the loaded set,
render an inline "No matches for '{query}'" line with a "Clear search"
button. Don't hide the page entirely.
### Loading / empty / error states
- **D-23:** Initial load → 4 summary card skeletons + 1 sparkline-card
skeleton + 5 user-row skeletons. Reuse `Skeleton` from
`components/ui/skeleton.tsx`.
- **D-24:** Subsequent infinite-scroll → small inline spinner above the
Load more button. Mirror Phase 4 D-21 / Phase 6 D-29.
- **D-25:** Fetch errors → `toast.error()` (sonner) per failure;
Load more button flips to "Retry". Mirror Phase 6 D-30.
- **D-26:** Empty: when `summary.activeUsers == 0` AND `users.length == 0`
for the selected period, render an `EmptyState`-style card: heading
"No engagement data for this period", body "Try a different period or
trigger a sync from `/admin`", with a `Settings` icon. Period chips
remain interactive so the user can switch.
- **D-27:** When `configured: false` from the summary endpoint (Microsoft
Graph not configured), render a banner card: heading "Engagement sync
not configured", body "Set MSGRAPH_* env vars and restart" with a link
out to admin. Inherit existing `isMsgraphConfigured()` semantics from
the existing endpoint.
### Typography & spacing (mirror Phase 4 UI-SPEC)
- **D-28:** Two weights only: `font-normal` (400) and `font-semibold`
(600). No `font-medium`.
- **D-29:** Three sizes: `text-sm` (14px) primary, `text-xs` (12px)
secondary/labels, `text-[10px]` for badges/captions. Plus `text-2xl`
for the four big summary numbers and `text-base font-semibold` for the
page H1 "Engagement".
- **D-30:** Page container: `px-4 py-4 space-y-4` (matches Phase 5/6).
No horizontal overflow at 360px viewport.
### Page H1 placement (NAV-01 / spec §5.1)
- **D-31:** `<h1>Engagement</h1>` renders in the page body, not the shell
header (Phase 2 spec: "no page title in the header"). H1 sits above
the period selector. The period selector is sticky relative to the
page; the H1 scrolls away.
### Auth + scoping
- **D-32:** Both new endpoints use `requireAuth()` from
`lib/auth-utils.ts`. No company scoping (`kiosk_settings`) — engagement
data is org-wide and the desktop endpoints already operate org-wide
(no per-company filter on `/api/engagement/*`). Mobile follows the
same posture.
- **D-33:** Existing `/api/engagement/users` does NOT use `requireAuth()`
— it's an existing-product gap (similar to the IDOR posture noted in
Phase 6). Document as inherited risk in the threat model; do NOT fix
the desktop endpoint in this phase (the spec out-of-scope explicitly
forbids modifying desktop pages/endpoints).
### What NOT to change
- **D-34:** Existing `/api/engagement/*` endpoints are unchanged.
- **D-35:** Existing `app/engagement/*` desktop pages are unchanged.
- **D-36:** No edits to `lib/services/msgraph-*` or
`lib/services/engagement-sync-service.ts` — all read-only consumption.
- **D-37:** No new state libraries; no SWR/react-query (CLAUDE.md).
- **D-38:** No Zod in API routes (CLAUDE.md: "no Zod in API routes
unless required").
### Claude's Discretion
- Exact sparkline math (linear interpolation across days, gap handling
for missing days)
- Whether to use `BarChart` rectangles or a `path` for the sparkline
(recommend `path` for compactness)
- Avatar fallback initials algorithm (recommend first letter of first +
last word of `displayName`)
- Whether to expand or hide the search input by default (recommend
always-visible, unobtrusive)
- Skeleton visual pattern density
- Exact chip vs button styling for the period selector and sort toggle
(recommend matching shadcn `Toggle` density)
- Whether `EngagementUserRow` extracts a separate component (yes, for
Phase 8 reuse — the user profile page may share the avatar + name
identity block)
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Phase spec
- `docs/superpowers/specs/2026-05-03-mobile-shell-design.md` §6.5
(Engagement Overview) — primary scope. §3.2 (More drawer) confirms
Engagement entry. §7 lists explicit non-goals for this phase.
- `.planning/REQUIREMENTS.md` (ENG-01..05, ENG-09) — locked acceptance
criteria.
### Project conventions
- `CLAUDE.md` — Pulse stack rules (no SWR/react-query, no ORM, no Zod in
API routes, port 3100), `/mobile/*` boundary, kebab-case files,
PascalCase exports.
- `DESIGN.md` — design tokens, component vocabulary, navigation IA.
- `ARCHITECTURE.md` — engagement sync overview (read for context; sync
pipeline itself is unchanged this phase).
### Prior phase contracts (patterns to mirror)
- `.planning/phases/02-mobile-shell-more-drawer/02-CONTEXT.md` — shell
decisions; the engagement page docks under this layout. DRAWER-03 wires
Engagement entry from the More drawer.
- `.planning/phases/03-dashboard-restyle/03-01-SUMMARY.md``KpiCardMobile`
pattern (the four summary cards mirror this scale).
- `.planning/phases/04-tickets-restyle/04-CONTEXT.md` — IntersectionObserver
+ Load more pattern (D-08..D-14), URL-synced filter pattern (NOT used
here — sort/search stays in component state per the lower data scale).
- `.planning/phases/04-tickets-restyle/04-UI-SPEC.md` — typography contract
(2 weights × 3 sizes), spacing scale, color tokens to mirror.
- `.planning/phases/05-finance-restyle/05-CONTEXT.md` — Card/typography
reuse pattern; inline error/Retry pattern (D-18); empty state
convention (D-19).
- `.planning/phases/06-analyzer-feed-new/06-CONTEXT.md` — Pattern for
reusing existing data endpoints (D-25), pattern for adding a
/api/mobile/* mobile endpoint when the existing shape doesn't fit
(D-09 here mirrors 06-09 there).
### Existing code (entry points)
- `app/api/engagement/summary/route.ts` — desktop summary endpoint;
reference for averages / period mapping / staff filter logic.
- `app/api/engagement/users/route.ts`**REUSED as-is** by the mobile
list. Pattern reference for the period mapping (`D7|D30|D90`), sort
whitelist, and pagination shape.
- `app/api/engagement/user/[userId]/route.ts` — Phase 8 scope, mentioned
here only because Phase 7's row links to `/mobile/engagement/[userId]`
which Phase 8 owns.
- `app/api/engagement/user/[userId]/history/route.ts` — Phase 8 scope.
- `app/engagement/page.tsx` — desktop overview (~1300 lines). Reference
ONLY — DO NOT modify, DO NOT port; build mobile views from same data
sources, phone-first.
- `app/engagement/profile/page.tsx` — desktop profile (~650 lines).
Reference ONLY — Phase 8 scope.
- `app/mobile/layout.tsx` (Phase 2) — shell where the engagement page
docks; no changes needed.
- `components/mobile/MoreDrawer.tsx` (Phase 2) — already routes to
`/mobile/engagement` per DRAWER-03; verify, don't modify.
### Existing schema
- `migrations/041_create_engagement_tables.sql``graph_users` +
`engagement_snapshots` tables. `period_type` enum: `D7`, `D30`, `D90`.
`engagement_snapshots.UNIQUE(user_email, period_type, period_end)`.
- `migrations/042_add_engagement_calendar_columns.sql` — additional
columns added later; read for additional fields available on snapshots
if helpful.
- The `time_entries`, `resources`, `zoom_calls`, `zoom_meetings` tables
are joined for hours/zoom totals in the existing endpoints — same
joins apply for the trend endpoint.
### Components and primitives
- `components/ui/{card,badge,skeleton,empty-state,input,separator}.tsx`
— shadcn primitives in use across mobile phases.
- `components/mobile/KpiCardMobile.tsx` — Phase 3 KPI card pattern;
consider for the four summary cards (or its scale, if a separate
component fits better).
- `components/mobile/AnalyzerFeedRow.tsx`, `components/mobile/FinanceRow.tsx`
— recent extracted-row component patterns to mirror for
`EngagementUserRow`.
- `components/mobile/AnalyzerRowSkeleton.tsx` — recent skeleton pattern.
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `requireAuth()` from `lib/auth-utils.ts` — auth gate for new endpoints.
- `postgresClient.query()` from `lib/services/postgres-client.ts`
parameterized SQL.
- `isMsgraphConfigured()` from `lib/services/msgraph-factory.ts`
returns boolean; surface as banner per D-27.
- `engagement_snapshots` + `graph_users` + `time_entries` + `resources`
tables — already populated by the engagement sync (`engagement-daily`
schedule, 6am).
- shadcn primitives: `Card`, `Badge`, `Skeleton`, `EmptyState`, `Input`,
`Button`, `Separator`. All in `components/ui/`.
- `lucide-react` icons already in deps (Settings, Search, Loader2,
Users, etc.).
- `IntersectionObserver` — browser-native, no dep.
- `relTime()` helper inline in `app/mobile/tickets/page.tsx:29-37`
— not relevant here (no time-ago) but the helper file shows the
pattern.
### Established Patterns
- Mobile pages: `'use client'` + `useState` + `useEffect` + `fetch('/api/...')`
- API routes: NextResponse.json + `requireAuth()` from `lib/auth-utils.ts`
- Postgres via `postgresClient.query()` parameterized SQL; manual
snake_case → camelCase transform.
- TypeScript interfaces exported from API route file alongside the
handler; pages consume via `import type { ... } from '@/app/api/.../route'`.
- IntersectionObserver pattern from `app/mobile/tickets/page.tsx` and
`app/mobile/analyzer/page.tsx` (Phase 4 / Phase 6).
- Skeleton render pattern from `components/mobile/AnalyzerRowSkeleton.tsx`
/ `components/mobile/TicketRowSkeleton.tsx`.
### Integration Points
- `app/mobile/layout.tsx` (Phase 2) renders the shell — `/mobile/engagement`
docks inside it. Bottom nav is unaffected (Engagement is NOT a tab
per ENG-09).
- `MoreDrawer.tsx` (Phase 2) already routes to `/mobile/engagement` per
DRAWER-03 — verify with a manual tap during human UAT.
- The new `/api/mobile/engagement/*` endpoints sit under existing
Better Auth + middleware (already requires auth for `/api/*` routes
not in the public list per `middleware.ts`).
- Phase 8 will build `/mobile/engagement/[userId]` — Phase 7's user
row already wires `<Link href={\`/mobile/engagement/\${graphUserId}\`}>`
so the navigation works as soon as Phase 8 lands.
</code_context>
<specifics>
## Specific Ideas
- Mirror Phase 6's pattern of "reuse existing data endpoint where
possible, add a thin /api/mobile/* endpoint only where the existing
shape doesn't fit." Two new endpoints here is the minimum: one for
totals (existing endpoint returns averages), one for time-series
(no existing endpoint).
- Sparkline should feel calm, not flashy: a single thin line, no
dots, no axis labels, no animation, ~3rem tall. Linear's "monthly
active" sparklines are a good reference.
- Avatar initials: same algorithm as `app/engagement/page.tsx` if it has
one; otherwise first letter of first word + first letter of last word
of `displayName`. Stick to upper-case, neutral background.
- Hours bar: keep it 6px tall (`h-1.5`); width should make it obvious
who's putting in the most time without being a chart on its own.
Don't add a numeric label inside the bar — the right-aligned hours
number above the bar already shows the value.
</specifics>
<deferred>
## Deferred Ideas
- "Today" period chip — engagement_snapshots only aggregate at
D7/D30/D90. A D1 sync would require adding a new period_type and
updating `engagement-sync-service.ts`. Defer to a future phase.
- Server-side search on the per-employee list — client-side filter is
fine at ≤50 staff. Add server-side search if the team grows past
~150 staff and the page=1 fetch can no longer cover all visible
results.
- Multi-series trend chart (Graph hours vs Autotask hours overlaid) —
explicitly out-of-scope per spec §6.5 ("no multi-series chart on
mobile"). Desktop already has this.
- Per-row drill-down to the user profile — that's Phase 8 (ENG-06..08).
Phase 7 wires the `<Link>` only.
- Sort by zoom calls / meetings / emails — outside ENG-04's three sort
axes. Add if managers request.
- "Engagement sync now" button on mobile — admin action, lives on the
desktop `/admin` page. Mobile is read-only (REQ EDIT-01).
- IDOR fix on existing `/api/engagement/*` endpoints — inherited risk
from the existing product. Out of scope per spec §7 ("Restyling or
replacing the desktop pages reachable from the More drawer — desktop
pages stay as they are"). Track in STATE.md follow-up; recommend a
future security phase.
- Response virtualization for the per-employee list — page size is 50
and most teams have ≤50 staff, so a single rendered list is fine for
v1. Add `react-window` or similar only if perf measurement warrants.
</deferred>
---
*Phase: 07-engagement-overview-new*
*Context gathered: 2026-05-04*

View file

@ -0,0 +1,152 @@
# Phase 7: Engagement Overview (NEW) - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-05-04
**Phase:** 07-engagement-overview-new
**Mode:** auto (recommended defaults selected for every gray area)
**Areas discussed:** Period selector mapping, Summary metric semantics, Per-
employee list data source, Sparkline data, List pagination, Search, Sort
options, Sparkline implementation, Loading/error/empty states, Typography &
spacing
---
## Period selector mapping
| Option | Description | Selected |
|--------|-------------|----------|
| Spec-literal: today / 7d / 30d | Matches spec text but data layer has no D1 aggregate | |
| Data-aligned: 7d / 30d / 90d | Maps to existing D7/D30/D90 period_type values | ✓ |
| Custom (date range picker) | Heavier UX, not in ENG-02 | |
**Auto-selection:** 7d / 30d / 90d — preserves data-layer fidelity.
"Today" deferred (would need new D1 sync).
---
## Summary metric semantics
| Option | Description | Selected |
|--------|-------------|----------|
| Reuse existing /api/engagement/summary (averages) and compute totals client-side | Simple, but math on client and avg×count is approximate | |
| New /api/mobile/engagement/summary returning the 4 ENG-03 metrics directly | Cleanest; mirrors Phase 6 pattern of one mobile endpoint | ✓ |
| Extend existing endpoint with totals fields | Couples desktop + mobile semantics | |
**Auto-selection:** New mobile endpoint — clean separation, accurate totals.
---
## Per-employee list data source
| Option | Description | Selected |
|--------|-------------|----------|
| Reuse existing /api/engagement/users directly | Already returns the right shape with sort/page params | ✓ |
| Wrap in /api/mobile/engagement/users | Adds a thin mobile-only endpoint with no value-add | |
| Inline SQL in the mobile page | Anti-pattern; violates server/client separation | |
**Auto-selection:** Reuse existing — already shapes the data correctly.
---
## Sparkline data source
| Option | Description | Selected |
|--------|-------------|----------|
| New /api/mobile/engagement/trend (daily totals) | Required — no existing endpoint returns time-series | ✓ |
| Reuse engagement_snapshots client-side | snapshots aren't daily; can't compute trend client-side | |
| Skip the sparkline | Violates ENG-05 | |
**Auto-selection:** New trend endpoint — required for ENG-05.
---
## List pagination
| Option | Description | Selected |
|--------|-------------|----------|
| Page-based + IntersectionObserver | Existing endpoint is page-based; matches Phase 4/6 UX | ✓ |
| Cursor-based (rewrite endpoint) | Requires modifying desktop endpoint (out of scope) | |
| No pagination (load all) | Page size 50; fine for ≤50 staff but breaks at scale | |
**Auto-selection:** Page-based + IntersectionObserver — same UX as Phase 4/6.
---
## Search behavior
| Option | Description | Selected |
|--------|-------------|----------|
| Client-side filter on loaded users | Instant feedback, no server param needed at ≤50 staff | ✓ |
| Server-side search param | Requires modifying desktop endpoint | |
| Skip search | Violates ENG-04 | |
**Auto-selection:** Client-side filter — fine at current scale.
---
## Sort axes
| Option | Description | Selected |
|--------|-------------|----------|
| 3 chips: Hours / Name / Utilization | Matches ENG-04 exactly | ✓ |
| Dropdown with 5+ axes | Too many for phone | |
| No sort control | Violates ENG-04 | |
**Auto-selection:** 3 chips matching ENG-04.
---
## Sparkline implementation
| Option | Description | Selected |
|--------|-------------|----------|
| Custom inline SVG path | No chart library, 30 lines, fully controlled | ✓ |
| recharts LineChart | Already a dep, but DASH-04 forbids recharts on mobile | |
| Visx/d3 | New dep, overkill for one sparkline | |
**Auto-selection:** Custom SVG — DASH-04 precedent.
---
## Loading / empty / error states
| Option | Description | Selected |
|--------|-------------|----------|
| Skeleton + toast.error + Retry + EmptyState card | Phase 4/5/6 precedent | ✓ |
| Single spinner only | Less polished | |
| Server-rendered placeholder | Doesn't match the client-fetch pattern | |
**Auto-selection:** Mirror established mobile patterns.
---
## Typography & spacing
| Option | Description | Selected |
|--------|-------------|----------|
| Mirror Phase 4 UI-SPEC: 2 weights / 3 sizes + base/2xl extras | Consistency across mobile shell | ✓ |
| New scale just for Engagement | Avoid divergence cost | |
**Auto-selection:** Mirror Phase 4/5/6.
---
## Auto-Resolved (`--auto` mode)
All ten gray areas were auto-resolved with the recommended option per
the workflow's `--auto` mode. No interactive questioning occurred.
## Deferred Ideas
(See `07-CONTEXT.md` `<deferred>` section for the canonical list.)
- "Today" period chip (requires D1 sync)
- Server-side search at scale
- Multi-series trend chart (out of spec)
- Per-row drill-down to user profile (Phase 8 owns this)
- Sort by zoom calls / meetings / emails
- "Engagement sync now" button on mobile (read-only by design)
- IDOR fix on existing /api/engagement/* endpoints (desktop, out of scope)
- List virtualization (deferred until scale demands)