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.
- Remap globals.css tokens to FCI brand palette (Eggplant #801d7f,
Spring Pea #96bc33, Seed Grant #679038, Squash #c9ad2d, FCI gray
#4b5657). Existing leaf-* / clay-* class names preserved.
- Switch body font to Open Sans (FCI's free fallback for Museo Sans).
Headings keep Fraunces.
- Add contact identity (first name, last name, email) as readonly
fields at the top of Stage 0. /api/data fetches via APIv4
Contact.get with email_primary.email join; values flow through
FormDataPayload.contact and into the form's evalState so the
readonly renderer displays them. Draft restore re-applies them so
a stale local draft can't override.
- amplify.yml: fetch Amplify Secrets from SSM Parameter Store when
they don't arrive as build-shell env vars (the common failure mode
behind "Refusing to run in production without CIVI_*"). Adds a
length-only diagnostic echo and a hard-fail guard so a missing
required var stops the build with a clear message instead of
bundling empty strings and crashing the SSR Lambda at runtime.
- New STAGE_OPTION_GROUP_ID constant (=75) added to config and consumed
by both the in-card readonly and the header. Because the field carries
optionGroupId, /api/data auto-includes the Stage option group in its
parallel fetch (via the existing optionGroupIds derivation).
- SubmissionContextHeader: dropped the small inline 'Stage Organizing'
line. Replaced with an uppercase 'Current stage' eyebrow next to the
progress dots, followed by a display-font 20/24px leaf-toned label
underneath. Resolves the stored option value ('Organizing') to its
Civi option label ('Stage 1 — Convene & Prepare') via the new
resolveStageLabel helper; falls back to the raw value if the option
group hasn't loaded.
- Stage 0 'Check-in (organizing)' section: re-added a current_stage
readonly field at the top. With optionGroupId set, FieldRenderer's
readonly branch resolves the value to the Civi label for display.
The current_stage readonly field is removed from Stage 0 — the activity-
derived stage value already displays in the SubmissionContextHeader, so
showing it again in-card was redundant. current_stage remains in form
state (RHF defaults from /api/data) because every Stage 1-5 visibleWhen
rule and the journey rail still key off it; it just isn't rendered.
Section labels for Stages 1-5 updated to match the CiviCRM Stage option
group labels exactly (ampersand instead of 'and'; Stage 2 corrected from
'Feasibility' to 'Grow & Plan'). Stage 0 keeps its 'Check-in (organizing)'
label since it represents the always-visible core fields, not the Inquiry
stage rank.
Also folded in: in-progress visibleWhen additions on several Stage 0
fields and commented-out field stubs from your working tree.
Stage authority moves from Organization.Food_Co_op_Organizing.Stage to the
most recent Check-in (organizing) activity whose Stage custom field is set.
Staff create these activities manually to mark transitions; org-owner form
submissions no longer write the Stage field at all, so they cannot override
a staff-set transition.
- /api/data: removed the Contact.get for org-side Stage; added an
Activity.get filtered to ACTIVITY_TYPE_NAME + ACTIVITY_STAGE_FIELD IS
NOT EMPTY, ordered by activity_date_time DESC, id DESC, limit 1.
Fallback when no such activity exists: Inquiry (rank 0). Org-name
lookup, stage activity, prefill, and option-group fetch all run in
parallel via Promise.all.
- /api/submit: removed the stageAtSubmission read + the
[ACTIVITY_STAGE_FIELD] write on the activity record. The form's
activities are stage-null by design now.
- config/form.ts: dropped the stage_at_submission readonly field (no
longer being set or displayed). Kept ACTIVITY_STAGE_FIELD export — it's
now used by /api/data to find stage-bearing activities. Updated the
current_stage field comment to reflect the new source.
- components/EngagementForm.tsx: dropped stage_at_submission from
evalState (no longer referenced by any visibility rule or readonly
display).
Org.Food_Co_op_Organizing.Stage remains in CiviCRM for staff list views;
the middleware no longer reads or writes it. No backfill required —
orgs without a stage-bearing activity simply read as Inquiry.