Each date-field event is now plotted on the lane corresponding to the Framework Stage the co-op was in on the field's stored date, resolved from the activity stream's stage transitions. Date_Opened is pinned to Stage 5 as the journey anchor. Events that pre-date any known transition fall back to the field's section rank so they still surface somewhere. Extracts the STAGE_RANK map (previously inline in ReportView) into lib/stageRank.ts alongside the new buildStageRankAtDate resolver factory.
Updates the tool product name across the app UI (header, page title,
section labels, form buttons, success/error states, report stat labels),
README, deployment docs, and the CiviCRM email template guidance.
Custom domain references move from check-in.fci.coop to survey.fci.coop
(DNS update still required).
The underlying CiviCRM "Check-in (organizing)" activity type, custom
group machine names (Check_in_data__organizing_), health-check ids,
and the internal org_engagement_check_in form id are unchanged --
those are CiviCRM contract surfaces, not product copy.
Form: dropped the boxed card treatment for FieldGroupCard in favor
of a leaf-tinted left rule + small uppercase mini-label. Eats only
~14px of horizontal space (border + pl-3/sm:pl-4) instead of
~32-40px for the previous bg-tinted card with px-4/sm:px-5 on both
sides, so the inner 2-col grid keeps more breathing room for the
fields themselves.
Report: same field-group concept now applies to ReportSection.
Grouped FieldHistoryRows render together inside a leaf-tinted left
rule with a small label above. Walk preserves the declared field
order — a group is emitted at its first member's position; the
other members are skipped when the loop later reaches them. Mixes
cleanly with the existing MembershipChart inline insertion and the
divide-y rhythm of standalone rows.
Sweeps every active label / help / intro / visible paragraph in the
app to use 'member-owner' terminology consistently. Stub option
labels for the Capital Stack (Member equity / Member loans) updated
on the label side; option `value:` strings stay as the CRM-side
stored values. Sentinel comparisons in ReportView's chart legend
updated to track the new field labels (otherwise the `(custom
label)` parenthetical would print spuriously even at default).
Untouched on purpose:
- Civi machine names (`Members__current_`, `Member_*`,
`*_Member*`) — wire-level identifiers, must match Civi.
- Option-group `value:` strings — CRM-stored values, must match.
- NCG_Member / INFRA_Member option labels — these refer to a
co-op's membership in distributor networks, not member-owners.
- Commented-out fields and technical comments referencing Civi
field names.
- Actual member line now uses the same carry-forward step pattern as
the goal line — between measurements the chart holds the prior value
instead of interpolating diagonally, and the final value extends flat
to the right edge. Eliminates the apparent dips that arose when
diagonal interpolation crossed missing periods or low intermediate
values.
- Per-field Sparkline (and its isNumericField / formatScalarText
helpers) removed entirely. Expanding 'earlier entries' now just shows
the chronological list. Curated multi-metric charts (like the
Membership chart) are the path forward for trend visualization.
- Membership chart relocated from the top-of-report band into the
Stage 0 ('Check-in (organizing)') section, rendered inline after
whichever of Members__current_ / Member_Goal_for_current_Stage
appears last in the section's fields-with-history list. Naturally
scopes the chart to wherever those questions live (no double-render
if config later moves them). Chart props refactored to take field
+ history pairs directly instead of walking the full sections array.
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.