docs(07): capture phase context (auto mode)
This commit is contained in:
parent
97a54fd8d8
commit
c85e6347c7
2 changed files with 579 additions and 0 deletions
427
.planning/phases/07-engagement-overview-new/07-CONTEXT.md
Normal file
427
.planning/phases/07-engagement-overview-new/07-CONTEXT.md
Normal 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*
|
||||
152
.planning/phases/07-engagement-overview-new/07-DISCUSSION-LOG.md
Normal file
152
.planning/phases/07-engagement-overview-new/07-DISCUSSION-LOG.md
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue