A 12-week distance bar chart under the profile stats header, viewer-scoped, built on profile-stats (#539). Design records the same no-cache rationale: one indexed grouped aggregate over a 12-week slice (≤12 rows), gap-filled in JS. Validates with `openspec validate --strict`. Specs only; no code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
2.2 KiB
Markdown
56 lines
2.2 KiB
Markdown
## Context
|
|
|
|
`profile-stats` added `getActivityStats` (a single indexed aggregate over stored
|
|
columns) and a `ProfileStats` header. This change adds a per-week breakdown
|
|
beneath it. `activities` has `distance`, `started_at`, `created_at`,
|
|
`visibility`, and `owner_id`-leading indexes.
|
|
|
|
## Goals / Non-Goals
|
|
|
|
**Goals**
|
|
- A compact weekly-distance trend on the profile, cheap and viewer-scoped.
|
|
|
|
**Non-Goals**
|
|
- No per-week elevation/time toggle, no selectable window in v1 (fixed 12
|
|
weeks).
|
|
- No hover tooltip framework — a native `title` per bar is enough.
|
|
- No "where I've been" map (separate idea).
|
|
|
|
## Decisions
|
|
|
|
### D1: One grouped aggregate — still no cache
|
|
`SELECT date_trunc('week', coalesce(started_at, created_at)) AS wk,
|
|
sum(distance) AS m
|
|
FROM journal.activities
|
|
WHERE owner_id = $1 [AND visibility = 'public']
|
|
AND coalesce(started_at, created_at) >= now() - interval '12 weeks'
|
|
GROUP BY wk`.
|
|
Bounded to 12 weeks and filtered on the `owner_id` index, this scans a tiny
|
|
recent slice and returns ≤ 12 rows — same cost class as the lifetime aggregate
|
|
(`profile-stats` §D1). No cache table; the documented escape hatch there covers
|
|
this too.
|
|
|
|
### D2: Gap-fill in JS
|
|
The query returns only weeks that have activities. The server fills the result
|
|
to a contiguous 12-week series (oldest → newest) with `0` for empty weeks, so
|
|
the chart always has a stable 12-bar axis. Week boundaries follow Postgres
|
|
`date_trunc('week', …)` (ISO weeks, Monday start).
|
|
|
|
### D3: Viewer scoping
|
|
Identical to `profile-stats`: `publicOnly: !isOwn`. Same WHERE clause shape;
|
|
remote-ingested rows (`owner_id NULL`) never appear.
|
|
|
|
### D4: Presentation
|
|
A small SVG bar chart (`WeeklyDistanceChart`) under the stats header: 12 bars,
|
|
heights normalized to the max week, each bar carrying a `title` with its week
|
|
distance (formatted via `stats.ts`). Renders nothing when every week is 0
|
|
(no distance in the window). Read-only, no interactivity beyond the native
|
|
title.
|
|
|
|
## Risks / Trade-offs
|
|
|
|
- **Fixed 12-week window** — a power user with a long history sees only the
|
|
recent quarter. Acceptable for a cadence glance; a selectable range is a
|
|
future enhancement.
|
|
- **Distance-only** — elevation/time trends are deferred to keep the chart
|
|
legible and the query single-column.
|