plresearch-org · edit-ai-opportunity-spaces-r2 · round 2
Fix for round 1's failing scenario. Wired the AI & Robotics opportunity-spaces pages to the already-existing org.plresearch.opportunitySpace records on plresearch.org's PDS, shipped an inline editor, and re-ran the test. Full loop works: UI edit → PDS write → indexer catches up → public list + detail pages reflect the change.
Headline. Round 1's blocker is resolved. The AI & Robotics opportunity-spaces detail page now renders an “Edit page” button for admins, which opens a dedicated inline editor. Saves write org.plresearch.opportunitySpace records to plresearch.org's PDS through a new PUT /api/opportunity-spaces/[rkey] route; the indexer picks them up live; revalidateTag("indexer") busts the Next.js ISR cache so the public list and detail pages reflect the change within seconds. Only finding: one transient save failed (likely a stale cookie after navigation) and succeeded immediately on retry.
Round 1 concluded that no ATProto record backs these pages. That was wrong in spirit — 15 org.plresearch.opportunitySpace records were already on the PDS, the indexer already surfaced them via GraphQL, and the website already had fetchOpportunitySpaces() / fetchOpportunitySpace() helpers in src/lib/indexer.ts. The only thing missing was the last-mile plumbing. Round 2 wires it up.
feat/editable-opportunity-spaces → main
Commitb3e6c4d — feat(ai-robotics opspaces): indexer-backed data + inline editor
Diff6 files, +451/-6
| File | Change |
|---|---|
src/app/areas/ai-robotics/opportunity-spaces/page.tsx | List: fetchOpportunitySpaces('ai-robotics') with static JSON fallback. |
src/app/areas/ai-robotics/opportunity-spaces/[slug]/page.tsx | Detail: fetchOpportunitySpace(rkey) with fallback; renders <EditPageButton/>. |
src/app/areas/ai-robotics/opportunity-spaces/[slug]/edit/page.tsx | New. Admin-gated inline editor (title, tagline, description, inflection point, shift, opportunity). |
src/app/api/opportunity-spaces/[rkey]/route.ts | New. GET + PUT. Mirrors /api/pages/[rkey] pattern: session + ADMIN_DIDS gate, impersonates plresearch.org via app password, calls revalidateTag("indexer") after a successful write. |
src/components/EditPageButton.tsx | Added optional href prop so non-page-collection edit buttons can override EDIT_ROUTES. |
src/lib/lexicons.ts | Added OpportunitySpaceRecord type + opportunitySpaceRkey(areaSlug, id) helper (rkeys use ai-- / dhr-- / eg-- / neuro-- prefixes per commit bb88ffa). |
/areas/ai-robotics/opportunity-spaces/open-compute-networks/ now renders the “Edit page” button top-right for admins — the affordance that was missing in round 1.
/areas/ai-robotics/opportunity-spaces/open-compute-networks/edit. The editor loads the current record via GET /api/opportunity-spaces/ai--open-compute-networks and renders title, tagline, description, inflection point, shift, and opportunity as in-place EditableField textareas. Fixed “Editing mode — click any text to edit” bar at the bottom with a disabled Save button (no changes yet).
[edited via pi-eval test] to the tagline turns the Save bar active (“Unsaved changes”, Discard + Save enabled). The textarea lights up with a blue focus ring.
Save the bar briefly shows “✓ Saved” (green dot) and returns to idle with Save disabled — exactly like the existing /areas/<slug>/edit flow for page records.
/areas/ai-robotics/opportunity-spaces/ shows the new tagline (“… beyond the hyperscalers [edited via pi-eval test]”) within a few seconds of save. Confirms the full chain works: UI → PUT → PDS → indexer → revalidateTag → ISR.
updatedAt moved to 19:46:54Z. No state left over from the test.Don't trust the UI — confirm the record actually landed:
$ curl -sG "https://leccinum.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord" \
--data-urlencode "repo=did:plc:pgwr6hkosgznfl5nz7egajei" \
--data-urlencode "collection=org.plresearch.opportunitySpace" \
--data-urlencode "rkey=ai--open-compute-networks" | jq '{uri, cid, tagline: .value.tagline, updatedAt: .value.updatedAt}'
{
"uri": "at://did:plc:pgwr6hkosgznfl5nz7egajei/org.plresearch.opportunitySpace/ai--open-compute-networks",
"cid": "bafyreibyq5wrnfe4ogf7meibnpnh4ijthsvo2rnsh6lee5cgj7vfrdkxx4",
"tagline": "Distributed infrastructure for AI training and inference beyond the hyperscalers [edited via pi-eval test]",
"updatedAt": "2026-04-20T19:44:27.547Z"
}
And the indexer GraphQL reflects the same record:
$ curl -s -X POST "https://plresearch-indexer-production.up.railway.app/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ orgPlresearchOpportunitySpace(where:{uri:{eq:\"at://did:plc:pgwr6hkosgznfl5nz7egajei/org.plresearch.opportunitySpace/ai--open-compute-networks\"}}, first:1){ edges { node { tagline updatedAt } } } }"}'
{ "data": { "orgPlresearchOpportunitySpace": { "edges": [{
"node": { "tagline": "… [edited via pi-eval test]", "updatedAt": "2026-04-20T19:44:27.547Z" }
}] } } }
The Edit page button appears on /areas/ai-robotics/opportunity-spaces/[slug]/ for admins. Clicking it opens a working inline editor. Saves persist to the PDS and flow back to the public pages.
Both list and detail pages keep the static JSON in src/data/fa2/ai-opportunityspaces.json as a build-time fallback (same pattern as fetchPage in other routes). If the Railway indexer is unreachable, pages still render the seed content rather than 500.
PUT /api/opportunity-spaces/[rkey] rejects the request with 401 if there's no session, and 403 if the session DID isn't in ADMIN_DIDS — mirroring /api/pages/[rkey]. The UI-side useRequireAdmin() guard redirects non-admins to /admin. Verified by unauthenticated curl returning 401.
After a successful save, reopening the edit page and submitting a second save returned “Save failed — try again.” Retrying immediately worked. I did not capture the network tab, but the most likely cause is a cookie serialisation race in iron-session during the navigation between routes (the session store keeps state in both memory and cookie per src/lib/auth/client.ts). Not blocking — the retry succeeded — but worth investigating if it recurs.
The other three areas (Digital Human Rights, Neurotech, Economies & Governance) still read from their static JSON files and have no edit button. A follow-up round should generalise the pattern — it's now just 2×3 files of copy-paste plus an rkey-prefix update, since the API route and opportunitySpaceRkey() helper already handle all four areas.
The editor covers the prose fields. subfields[], tippingSignals[], keyAssumptions[], observations[], and fieldSignals[{kpi, measurement}] are read-only from the UI in this round. PUT accepts them if sent, so a future editor (or /admin) can add them without API changes.
https://www.plresearch.org/admin
Handlesatyam2.climateai.org
Passwordtest password — redacted in this report
Target edit page/areas/ai-robotics/opportunity-spaces/open-compute-networks/edit
Toolagent-browser (snapshot-driven)
PDS verifycurl -sG https://leccinum.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord --data-urlencode repo=did:plc:pgwr6hkosgznfl5nz7egajei --data-urlencode collection=org.plresearch.opportunitySpace --data-urlencode rkey=ai--open-compute-networks
Round 1 reportr1 (fail)
Screenshotsassets/01-*.png … assets/07-*.png