Postgres returns NUMERIC columns as strings via pg, so calling
.toFixed(1) on time_entries.hours_worked from /api/engagement/user/[userId]
threw at runtime. The period-level hours in the same response are already
parseFloat'd; the recentEntries and matchedEntries arrays pass rows through
verbatim, so wrap with Number() at render.
- New EngagementProfileBreakdown: Time/Communication/Meetings subsections, after-hours
and Zoom conditional rows, py-2 per UI-SPEC override
- New EngagementRecentEntries: collapsible list up to 10, Billable badge, Set<string>
expand state, empty-state copy
- New EngagementRecentMeetings: collapsible list up to 10, matched entries + attendees
in expanded view, (no subject) fallback, Set<string> expand state
- Page updated: 3 new component imports + breakdown/entries/meetings mounted in order
- No dangerouslySetInnerHTML; D-01/D-22 guard rails untouched