wulf-pulse/.planning/phases/08-engagement-user-profile-new/08-02-SUMMARY.md

6.1 KiB
Raw Blame History

phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics requirements_addressed
08-engagement-user-profile-new 02 mobile-engagement
mobile
engagement
profile
page
components
requires provides affects
GET /api/mobile/engagement/user/[userId]/photo
GET /api/engagement/user/[userId]
components/mobile/EngagementPeriodChips.tsx
components/mobile/EngagementUserRow.tsx#getInitials
GET /mobile/engagement/[userId] (real route, not modal)
app/mobile/engagement/[userId]/page.tsx
components/mobile/EngagementProfileSkeleton.tsx
components/mobile/EngagementProfileHeader.tsx
components/mobile/EngagementProfileMetricGrid.tsx
components/mobile/EngagementProfileBreakdown.tsx
components/mobile/EngagementRecentEntries.tsx
components/mobile/EngagementRecentMeetings.tsx
app/mobile/engagement/page.tsx (sessionStorage scroll shim only)
added patterns
App Router dynamic route `[userId]` with `params: Promise<{userId}>` unwrapped via React.use
Image error → initials fallback via local useState toggle
rAF-throttled scroll capture + retry-on-restore (D-04 fallback)
modified created
app/mobile/engagement/page.tsx
components/mobile/EngagementRecentEntries.tsx
components/mobile/EngagementRecentMeetings.tsx
app/mobile/engagement/[userId]/page.tsx
components/mobile/EngagementProfileSkeleton.tsx
components/mobile/EngagementProfileHeader.tsx
components/mobile/EngagementProfileMetricGrid.tsx
components/mobile/EngagementProfileBreakdown.tsx
components/mobile/EngagementRecentEntries.tsx
components/mobile/EngagementRecentMeetings.tsx
Profile is a real route, not a modal — preserves device-back behavior (D-04 intent)
Photo fallback to initials is a render-time `<img>` onError → useState toggle (no double-fetch)
Coerce Postgres NUMERIC strings to Number() at render — pg returns numeric columns as strings; period totals are parseFloat'd in the API but recentEntries[]/matchedEntries[] hours_worked are passed through verbatim
Scroll restoration: SC#2 is partial — sessionStorage shim with rAF retry was added but did not reliably restore window scroll on this layout in user testing. User accepted as a known limitation; no follow-up plan filed.
duration_minutes completed_date tasks_completed files_modified files_created
60 2026-05-08 4 3 7
ENG-06
ENG-07
ENG-08

Phase 8 Plan 02: Mobile Engagement User Profile Summary

One-liner: Mobile engagement user profile at /mobile/engagement/[userId] — a real Next.js route (not a modal) composing six new components on top of the existing /api/engagement/user/[userId] endpoint plus the Plan 01 photo proxy.

Tasks Completed

Task Name Commit Files
1a Page shell + Skeleton + period/fetch wiring 3247c92 app/mobile/engagement/[userId]/page.tsx, components/mobile/EngagementProfileSkeleton.tsx
1b Identity header + 2×2 metric grid + page wiring df78ab8 components/mobile/EngagementProfileHeader.tsx, components/mobile/EngagementProfileMetricGrid.tsx, app/mobile/engagement/[userId]/page.tsx
2 Activity breakdown + Recent entries + Recent meetings 0be0c1f components/mobile/EngagementProfileBreakdown.tsx, components/mobile/EngagementRecentEntries.tsx, components/mobile/EngagementRecentMeetings.tsx, app/mobile/engagement/[userId]/page.tsx
3 Manual verification (scroll restoration) n/a (user-tested)

Post-test fixes

Fix Commit Files Reason
Coerce hours_worked to Number before .toFixed 81079ad components/mobile/EngagementRecentEntries.tsx, components/mobile/EngagementRecentMeetings.tsx Postgres NUMERIC arrives as string via pg — runtime TypeError on render
Scroll-restoration shim attempt 6bdc937, then revised in subsequent commits app/mobile/engagement/page.tsx Mobile shell uses overflow-y-auto on <main> but the document scrolls in practice; sessionStorage save+restore added with rAF retry. SC#2 still partial — see Decisions.

What Was Built

/mobile/engagement/[userId] page — full profile surface composed of:

  1. H1 — display name from Graph user
  2. Sticky period chips (D7/D30/D90) — defaults to D30, refetches on change
  3. Identity card — Graph photo via the Plan 01 proxy with onError → getInitials(displayName) fallback; title, department, mailto link, last-active label (relative ≤7d via date-fns, absolute >7d via formatInUserTimezone)
  4. 2×2 metric grid — Hours worked, Billable hours, Days worked, Meetings attended (period-scoped)
  5. Activity breakdown card — Time / Communication / Meetings subsections with conditional Zoom + after-hours rows
  6. Recent time entries — collapsed list of recent Autotask time_entries with billable badge + truncated note
  7. Recent Teams meetings — list with participant names, client attendees, inline matched time entries

Loading state renders EngagementProfileSkeleton. Error states: 404 → "User not found" with back link; 5xx/network → skeleton + Retry button (uses retryNonce to re-trigger the fetch effect).

Reuses /api/engagement/user/[userId] verbatim — no API modifications. The existing EngagementUserRow.tsx link to /mobile/engagement/${graphUserId} (already present from Phase 7) now reaches a real destination.

Verification Results

SC#1 — Page renders all sections: ✓ Passed (user-confirmed) SC#2 — Scroll restoration on back gesture: ⚠ Partial — list scrolls back to top, not to the previous row position. The mobile shell's layout has <main> with overflow-y-auto but in practice the document scrolls (window.scrollY carries the value, <main>.scrollTop stays 0). A sessionStorage shim with rAF retry was added but did not consistently restore window.scrollY after the row list re-rendered. User accepted as a known limitation.

Notable Deviations

None. Plan was followed; the only adjustments were the post-test fixes documented above.