trails/openspec/changes/profile-weekly-distance/design.md
Ullrich Schäfer d975cc7402
openspec: propose profile-weekly-distance (weekly bars)
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>
2026-06-13 07:29:23 +02:00

2.2 KiB

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.