New MembershipChart card sits between the DateTimeline and the section
accordions, rendering whenever Members__current_ or
Member_Goal_for_current_Stage has any historical data.
- Actual member count: smooth leaf-700 polyline with a faint leaf-500
area fill underneath, dots at every measurement, an emphasized dot
on the most recent point with the value labeled inline.
- Goal: dashed clay-600 step line — each goal value is treated as a
target that holds until the next update, then extends flat to the
right edge of the chart. Dots at each update; the most-recent goal
value labeled at the right.
- Y-axis: niceYTicks picks 3–5 round-number ticks (snapped to
1/2/2.5/5/10 × 10^N) spanning [min(0, dataMin), dataMax]; faint
gridlines + tabular-num labels on the left. Anchoring at 0 keeps
growth-from-small-base readable.
- X-axis: reuses generateAxisTicks for adaptive month/year stepping,
matching the timeline above. Today gets a dashed clay vertical
guide when in range.
- Header: title + subtitle + an inline 'NNN of MMM target · X to go'
callout in tabular-nums, color-coded (clay-700 if behind goal,
leaf-700 if above).
- Legend at the bottom with line+dot chips for both series.
Three improvements to the report's DateTimeline:
1. Tooltips on every dot. Each dot is now a focusable span (tabIndex,
role=img, full aria-label). A small ink-tinted card appears above
the dot on mouse hover or keyboard focus, showing field label,
formatted date, stage rank, and an 'Opened' marker for Date_Opened.
Anchor flips to left/center/right based on the dot's position so
tooltips don't overflow the row at the edges.
2. Plot every date field, including stage 0. The previous version
skipped Stage 0 dates (Internal_Startup_Assessment_Date,
Date_Closed_Folded). Now there are six swim lanes (0-5) instead
of five. Stage 0 gets bg-leaf-200 so the gradient extends one
step lighter.
3. Adaptive month/year x-axis under the lanes. generateAxisTicks
picks a 'nice' interval based on the visible span: 1mo / 2mo /
3mo / 6mo / 1yr / 2yr. January-bordered ticks include the year
so the reader has anchors. Today gets its own labeled clay tick
when it falls in range.
Two visual additions to the read-only activity report.
Sparkline: when the user expands earlier-entries on a numeric field
(number/currency/percent) with two or more numeric points, the
expansion now leads with a 240x56 inline SVG trend chart — chronological
polyline, faint area fill, small dots at every measurement, a slightly
larger emphasized dot on the most recent point. Min and max captions
sit beneath in tabular-nums, formatted in the field's native style
(currency uses Intl, percent appends %, etc.). Non-numeric fields are
unchanged.
DateTimeline: a new card between the context header and the section
accordions. Walks every date-type field in stage sections 1-5 (Stage 0
omitted as it isn't a stage in the journey sense), pulls each field's
most-recent entered date, and lays the events out in five horizontal
swim lanes — one per stage rank, labeled at the left. Time axis
spans from the earliest event to max(latest event, Date_Opened).
Stage 5's Date_Opened is rendered as a larger clay-700 dot with a
heavier ring so it reads as the journey's anchor at the right end.
A faint clay-300 dashed vertical line marks 'today' if it falls
within the range. Color scale across stages is leaf-300 / leaf-500
/ leaf-600 / leaf-700 / clay-700 — a sprout-to-fruit gradient that
matches the existing palette. Empty stage rows still draw their lane
line at half opacity so the structure stays readable. SR-only event
list provides screen-reader access to all plotted dates with their
labels.
Stub payload enriched with four cross-stage date entries so the
timeline has content in dev preview.
CiviCRM APIv4 file custom fields return a bare file id by default; an
extra '.file_name' join is required to get the human-readable filename.
Both the form prefill walk (lib/prefill.ts) and the report walk
(app/api/report/route.ts) now request '<civiField>.file_name' for every
file-type field alongside the primary value, and wrap the prefill into
a { id, file_name } object so downstream UI has both. Falls back to
file_name undefined when the join returns null (eg orphaned id).
The form's FilePriorIndicator already accepts the object shape, so it
now shows the filename inline. The report's FormattedValue gets a
matching case: renders the file_name string if present, falls back to
'Attachment #<id>' when only the id came through.
Mirrors the form's IA and Field Almanac aesthetic; same auth (cid/cs
checksum) so the org owner who can fill the form can also view its
history.
New routes:
- GET /api/report — verifies checksum, resolves the org via the
Primary Contact relationship, fires Contact.get + Activity.get +
OptionValue.get in parallel. For every form field with a civiField,
walks all the org's Check-in (organizing) activities and collects
every non-empty value into a sorted-DESC history list. Returns
ReportPayload (orgName, currentStage, activities, fieldHistory,
options). Has stub-mode payload for env-less local dev.
- /report — page entry; same layout shell (SiteHeader + SiteFooter,
3xl page width). Eyebrow "Activity report - Co-op organizing".
ReportView component:
- ReportContextHeader: large org name, progress dots + uppercase
"Current stage" eyebrow + the Civi option *label* on its own line
at display-font xl/2xl leaf-800 (matches the form's header). Below
it a 3-up stat band: total check-ins, fields tracked, date span.
- One accordion card per stage section, in stage-rank order. Only
sections that have at least one field-with-entries render — past,
current, or "future-with-data" all welcome; truly empty stages stay
hidden so the page is calm.
- Same journey rail (md+) and mobile stem (md-) with past =
check-filled-leaf, current = filled-leaf-with-ring, future = dashed
hollow ring; solid leaf line vs dashed muted between markers.
- Within each card: divide-y rows. Field label and help on the left,
most-recent value on the right in display-font lg leaf-800, dated
beneath with an "{N} earlier entries" disclosure that expands a
small vertical timeline (date on left, value on right).
- FormattedValue handles currency (Intl), percent, number (tabular
nums), date (long, timezone-safe for YYYY-MM-DD), boolean (Yes/No),
select/readonly (resolved via option group), multiselect (handles
array or delimited string), file (filename), text-like (as-is).
- Loading / empty / error states match the form's treatments.
types/form.ts: new FieldHistoryEntry, ActivitySummary, ReportPayload.
The fieldHistory map keys by FieldConfig.name and only includes fields
that have at least one non-empty entry.