Lifetime totals (count · distance · ascent · elapsed time) + a last-4-weeks count on the profile, viewer-scoped (public-only for visitors, full for the owner). Design records the cost decision: compute on the fly with a single aggregate over stored columns on the owner_id-leading indexes (cheap even for power users) — no cache table, no schema change — with a documented escape hatch to a user_activity_stats cache if profiling ever shows it's hot. Validates with `openspec validate --strict`. Specs only; no code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1.1 KiB
1.1 KiB
1. Aggregate query
- 1.1 Add
getActivityStats(ownerId, { publicOnly })toactivities.server.ts: one aggregate (count,sum(distance),sum(elevationGain),sum(duration)) + alast4Weekscount (started_at >= now() - 28d, COALESCE withcreated_at), scoped bypublicOnly. - 1.2 Return zeros/empty when the owner has no visible activities.
2. Loader & view
- 2.1 Profile loader (
users.$username.server.ts) callsgetActivityStats(ownerId, { publicOnly: !isOwn }); expose the totals + last4Weeks. - 2.2 Render a compact stats header on
users.$username.tsxabove the routes/activities lists (reuseStatRow+stats.tsformatters); hide it when there are no activities.
3. i18n
- 3.1 Add
journal.profileStats.*keys (activities, distance, ascent, time, last4Weeks) to en + de.
4. Tests & checks
- 4.1 Unit/component: the stats header renders totals + last-4-weeks; hidden on empty (jsdom).
- 4.2 E2E: create activities, assert the profile shows the totals; a visitor sees public-only (a private activity is excluded from the count).
- 4.3
pnpm typecheck && pnpm lint && pnpm test && pnpm test:e2egreen.