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.
Two additions, both touching the form-config story:
1. scripts/sync-help-from-civi.mjs
Diffs per-field help text in config/form.ts against CustomField rows
in CiviCRM and (with --write) updates the file in place. Reads env
from .env.local via Node's --env-file flag. Run as `npm run sync-help`
or `npm run sync-help -- --write`. A --debug mode prints the parser's
field list without calling Civi.
Rationale: this form is low-traffic and help text doesn't change
often once in production. A manual one-off sync is leaner than
coupling every page load (or every build) to a Civi API call.
2. fieldGroups: visual clustering of related fields within a section
New optional FieldGroupConfig overlay on StageSectionConfig — pure
presentation, names existing fields by name so submit/visibility
logic walks them unchanged. StageSection.tsx pulls grouped fields
out of the standalone per-field grid and renders each group as its
own bordered card with an optional heading. Stage 2 now clusters
Market Study, Pro Forma, Business Plan, and Board Self Assessment
(each a date + upload pair) into their own cards.
Future stages now render as preview-only "look ahead" cards instead of being
hidden. A user at stage 2 can see headers and contents for stages 3, 4, 5,
but those sections are visibly locked and uneditable.
Locked-card treatment:
- Dashed-rule border, paper-2 fill, no shadow — visually quieter than
active cards
- "Upcoming" pill in the header with a small lock glyph
- Muted stage rank mark (dashed badge, low-opacity icon)
- Panel content wrapped in fieldset[disabled] so every form control inside
is natively non-interactive, with an opacity tweak for affordance
- "A look ahead" banner explaining that fields will become editable when
the co-op reaches this stage
Section visibleWhen is still consulted on submit, so locked-stage values
never get written back to CiviCRM even if data is prefilled.
Journey rail:
- Vertical rail (md+) in a new left gutter; each card carries an aligned
marker. Past stages = filled leaf circle with check; current = filled
leaf disc with rank number, leaf-100 halo ring, and a subtle rail-pulse
box-shadow animation (motion-safe). A "Now" pill sits beneath the
current marker. Future stages = dashed hollow ring with lock glyph.
- Connector segments between markers are solid leaf when the next stage
is past-or-current, dashed muted when future — so the transition from
"traveled" to "ahead" reads at the right place in the journey.
- Mobile fallback: a small vertical stem in the gap between adjacent
cards, styled the same way (solid vs dashed) so the progression cue
still reads on narrow viewports.
- H3: Live currency preview below currency inputs shows the value formatted
with thousands separators (en-US, USD) using Intl.NumberFormat. Skipped
inside matrix cells to keep the Y1 monthly table compact.
- M1: Date inputs now apply min/max bounds. Default window is 1900-01-01 to
2100-12-31; per-field override via FieldConfig.min/max as ISO strings.
- H6: On successful submit, replace the form with a SuccessDestination card
(large checkmark, org name, "Submit another" + "safe to close" affordance).
Prevents accidental duplicate submits from back-button / autofill replay.
- M6: Sticky submit bar respects iOS safe-area-inset-bottom.
FieldRenderer now takes a control prop so the currency preview can subscribe
to its single field via useWatch without re-rendering the whole form.