simocracy-v2 · gathering-default-constitution · r2

Round 2 — fix confirmed, default constitution attached end-to-end

@daviddao

Re-ran the r1 flow against the production redeploy containing commit adaad59. The template picker no longer hangs — the curated “Default Constitution” template loads on first open, Use attaches it, and submitting the form writes an org.simocracy.gathering record on the user’s PDS with a suggestedInterviewTemplates StrongRef pointing at the facilitator’s template.

Date: 2026-04-20 · Target: www.simocracy.org · Account: did:plc:cpoagodpqrgs4t7thi5z37uf · Commit under test: adaad59 · pass


Series

Round arc so far: r1 failr2 pass.

Headline result

1
Blocker fixed (template picker)
28
Questions in attached template
1
StrongRef written to PDS
1
Commit

Headline. The r1 blocker is fixed. The curated Default Constitution interview template (28 questions — 14 open-ended + 14 yes/no) now renders in the picker on first open, attaches to the form via Use, and persists to the user’s PDS as a suggestedInterviewTemplates StrongRef when the gathering is created. Verified end-to-end on the production URL.

The fix

Commit adaad59 in components/events/gathering-form-dialog.tsx:

// before
useEffect(() => {
  if (!showTemplatePicker) return;
  if (allTemplates.length > 0 || templatesLoading) return;
  let cancelled = false;
  (async () => {
    setTemplatesLoading(true);          // ← re-runs effect, cancelled becomes true
    try { /* fetch … */ }
    finally { if (!cancelled) setTemplatesLoading(false); }  // ← never runs
  })();
  return () => { cancelled = true; };
}, [showTemplatePicker, allTemplates.length, templatesLoading]);

// after
useEffect(() => {
  if (!showTemplatePicker) return;
  if (allTemplatesLoadedRef.current || templatesLoadingRef.current) return;
  templatesLoadingRef.current = true;
  setTemplatesLoading(true);
  let cancelled = false;
  (async () => {
    try { /* fetch … */  allTemplatesLoadedRef.current = true; }
    finally {
      templatesLoadingRef.current = false;
      if (!cancelled) setTemplatesLoading(false);
    }
  })();
  return () => { cancelled = true; };
}, [showTemplatePicker]);

Walkthrough

01“Add Interview Template” picker opened on first click. The Loading templates… state flashes briefly and resolves to a Curated section showing Default Constitution (28 questions) with Use and Fork actions. Compare to r1 step 06, which stayed on Loading templates… indefinitely.
02After clicking Use. The picker auto-closes and the main form now shows Suggested Interview Templates 1 / 5 with a Default Constitution chip (plus a remove-X). The rest of the form is unchanged.
03After submitting Create Event and waiting ~10 s for the indexer to catch up, the counter now reads 4 events and Default Constitution Test (post-fix) appears under All Events.
04Opening the gathering directly at /events/<did>/<rkey> renders the Senate walking-world scene with “0 sims” — expected for a freshly-created gathering with no council members yet.

PDS verification

The gathering record — including the StrongRef to the facilitator’s curated template — was fetched directly from the user’s PDS before cleanup:

DID=did:plc:cpoagodpqrgs4t7thi5z37uf
curl -s "https://climateai.org/xrpc/com.atproto.repo.listRecords" \
  --data-urlencode "repo=$DID" \
  --data-urlencode "collection=org.simocracy.gathering" -G | jq '.records[0].value'
{
  "$type": "org.simocracy.gathering",
  "name": "Default Constitution Test (post-fix)",
  "status": "application",
  "simSize": "normal",
  "createdAt": "2026-04-20T05:24:24.357Z",
  "gatheringType": "governance",
  "allocationMechanism": "s-process",
  "suggestedInterviewTemplates": [
    {
      "uri": "at://did:plc:awtx57rxuisy4sf4kzzr3uhf/org.simocracy.interviewTemplate/3mjtodp56ps2e",
      "cid": "bafyreihqwpezjm4rck4hhldwp4hltwpvlamkg4ia62pjllb5d26er63cwi"
    }
  ]
}

Findings

r1 blocker: template picker stuck on “Loading templates…”

Fixed. Root cause confirmed — templatesLoading was both a state value driving the UI and a dependency of the effect that owned it, so the setter inside the effect re-ran the effect and cleanup flipped cancelled to true before the fetch resolved. Switching the two guards to refs keeps the StrictMode double-invoke happy.

“Use” flow writes a correct ATProto StrongRef

attachTemplateViaUse builds a {uri, cid} pair from the curated template and the submit handler serialises it into suggestedInterviewTemplates. The record round-trips through the PDS exactly as expected for org.simocracy.gathering per AGENTS.md / lib/lexicon-types.ts.

Indexer lag still masks the write

Carried over from r1 — not addressed by this fix. Immediately after Event created!, handleCreated → window.location.reload() re-fetches via the Simocracy indexer and still shows the old count until the indexer catches up (~10 s in this run). Worth a separate issue to either optimistically merge the just-written record into the gallery or to fall back to a PDS read for the current viewer.

Reproducing

Accountsatyam2.climateai.org · did:plc:cpoagodpqrgs4t7thi5z37uf Targethttps://www.simocracy.org/events Commit under testadaad59 — fix(gathering-form): unstick template picker Loading state Driveragent-browser (open → snapshot -i → click / fill → screenshot) Previous roundgathering-default-constitution-r1 Screenshotsassets/01-template-picker-loaded.png04-gathering-detail.png