plresearch-org · cms-editable-listings
End-to-end smoke test of the eight new CMS editors on feat/cms-editable-listings: every page that gained an admin editor in this PR was driven through Edit → Save → live-page verification, and the resulting org.plresearch.editEvent records on the editor's PDS were verified via the AT Protocol XRPC API.
Pass. Every page on the PR is admin-editable end-to-end. Logged in via OAuth as satyam2.climateai.org, edited each new page, saved, reloaded the live route, and observed the smoke-test marker text propagated. All eight saves emitted an org.plresearch.editEvent record on the editor's PDS (https://climateai.org) — verified out-of-band with com.atproto.repo.listRecords.
The two non-blocking findings (§Findings) are both pre-existing on main and unrelated to the PR diff: indexer crawl lag delays the byline render on freshly-edited records, and fetchAtproPosts() queries a field the indexer schema doesn't expose.
The PR feat/cms-editable-listings registers eight new org.plresearch.page rkeys and pairs each with an inline /edit route. Six are listing pages that previously had hardcoded heros; two are existing pages whose Edit buttons pointed at routes that didn't exist (404). The map:
| Phase | Route | rkey | Sections exposed |
|---|---|---|---|
| 1.1 fix | /areas/economies-governance/subareas/ | area-eg-subareas | hero + 9 subareas |
| 1.2 fix | /areas/economies-governance/impact/ | area-eg-impact | hero + 2 cards |
| 2 new | /insights/ | insights | hero + 3 subpage cards |
| 2 new | /publications/ | publications | hero |
| 2 new | /talks/ | talks | hero |
| 2 new | /tutorials/ | tutorials | hero |
| 2 new | /blog/ | blog | hero |
| 2 new | /authors/ | authors | hero + 2 blurbs |
satyam2.climateai.org



did:plc:cpoagodpqrgs4t7thi5z37uf). Note: the new rkeys are not listed yet because the indexer only surfaces records that already exist.area-eg-subareas (broken edit fixed)Before this PR, the floating Edit page button on /areas/economies-governance/subareas/ linked to /areas/economies-governance/subareas/edit, which 404'd. The PR adds the editor.





area-eg-impact (broken edit fixed)

/insights/


/publications/

content/publications/” explainer.
/talks/, /tutorials/, /blog/





/authors/ (server/client split)Authors needed extra surgery: the original was a client component because of the tabs (Leadership / Advisors / Alumni), so the PR splits it into a server page.tsx that fetches the page record + a client AuthorsTabs.tsx. Worth eyeballing.


content/authors/).
The byline component reads org.plresearch.editEvent records via the Railway-hosted indexer. Verified working on a record edited 13 days ago (the indexer has crawled it):


Audit-log records land on the editor's repo (Satyam Two's), not on plresearch.org's. Confirmed all 8 saves with a direct XRPC call:
curl -s 'https://climateai.org/xrpc/com.atproto.repo.listRecords?repo=did:plc:cpoagodpqrgs4t7thi5z37uf&collection=org.plresearch.editEvent&limit=20' | jq
Result (latest first, only this run shown):
| editedAt (UTC) | target rkey | changedFields |
|---|---|---|
2026-05-04T19:46:00 | authors | sections |
2026-05-04T19:44:14 | blog | sections |
2026-05-04T19:43:10 | tutorials | sections |
2026-05-04T19:42:07 | talks | sections |
2026-05-04T19:41:05 | publications | sections |
2026-05-04T19:39:39 | insights | sections |
2026-05-04T19:38:17 | area-eg-impact | sections |
2026-05-04T19:35:24 | area-eg-subareas | sections |
Each record is cryptographically anchored to satyam2's repo via the PDS commit log — the page records themselves live on plresearch.org's repo (server-side impersonation via the ATPROTO_HANDLE/ATPROTO_PASSWORD app-password trick), but attribution is preserved by separating the audit log onto the editor's own repo. Working as designed.
All eight new /edit routes load the page record, render the right fields, capture changes, save successfully, and the live route reflects the change immediately on reload (no indexer round-trip needed; the page route reads via fetchPage() with 60s revalidate but cache-busted by revalidateTag("indexer") in the PUT handler).
The Authors split (server page.tsx + client AuthorsTabs.tsx) renders cleanly and the tabs still work. No dev-tools React boundary warnings on that page.
The byline (EditHistoryByline) reads from orgPlresearchEditEvent via the Railway-hosted indexer at plresearch-indexer-production.up.railway.app/graphql. Direct query right after saving:
{ orgPlresearchEditEvent(where: {target: {eq: "at://…/area-eg-subareas"}}) { edges { node { editedAt } } } }
→ { "edges": [] }
Same query against an opportunity-space record edited 13 days ago returns the events. So the indexer does crawl org.plresearch.editEvent records on satyam2's PDS, but with non-trivial lag. This is not a regression introduced by the PR — same behaviour exists on main for any newly-saved record.
Mitigation options: (a) accept the lag and let the byline appear once the indexer catches up, (b) write the byline data to a server-rendered prop that reads directly from the PDS instead of the indexer (lower latency, higher fan-out), (c) optimistically inject the just-saved event into the byline state from the editor on save success.
fetchAtproPosts() queries a field the indexer doesn't expose (pre-existing)Visible in the Next.js dev-tools 1 Issue badge on /blog/:
[indexer] GraphQL errors: [{ message: 'Cannot query field "records" on type "Query".' }]
at query (src/lib/indexer.ts:78:15)
The query in fetchAtproPosts uses records(collection: "site.standard.document", …), but the indexer only exposes per-collection roots like orgPlresearchEditEvent, orgPlresearchPage, etc. Result: blog page silently falls back to the markdown-sourced posts only; ATProto-authored posts never appear, and the dev-tools issue badge is permanent. Not introduced by this PR — same error fires on main.
Fix: either rename the GraphQL field to whatever the indexer actually exposes for site.standard.document (likely siteStandardDocument or similar — would need an indexer-side check), or remove the call until the publication pipeline lands. Worth filing as its own ticket.
/admin/ uses fetchAllPages() to enumerate existing page records on plresearch.org's repo. It is not aware of PAGE_RKEYS — which means a new rkey is only navigable from the admin index after someone visits its /edit route at least once and saves. Not a defect of this PR (same behaviour for the existing rkeys before any edits land), but worth knowing for the team's ops handoff: from a fresh production deploy the new rkeys won't show on the admin index until first save.
If desired, two fixes: pre-seed empty page records for every PAGE_RKEYS entry on first deploy, or render the admin panel from PAGE_RKEYS directly with a “No record yet — click to start” affordance.
/research redirect produces stale breadcrumbsCosmetic, pre-existing. The breadcrumbs on /publications/, /talks/, /tutorials/ read “Home / Research / …”, but /research/ itself 308-redirects to /insights/. The breadcrumb link in the chrome therefore lands on /insights/, which is fine, but the label “Research” is now misleading — it should read “Insights”. Not in PR scope; flagging.
feat/cms-editable-listings (2 commits, head 6f4ecc4)
Pre-flightnpm run dev from repo root → 127.0.0.1:3001
Login handlesatyam2.climateai.org
Driveragent-browser — open / snapshot -i / click / fill / wait / screenshot
Editor flowFor each rkey: open /<route>/ → click Edit page → fill a field → click Save → re-open /<route>/ → screenshot.
EditEvent checkcurl https://climateai.org/xrpc/com.atproto.repo.listRecords?repo=did:plc:cpoagodpqrgs4t7thi5z37uf&collection=org.plresearch.editEvent
Indexer checkcurl …/graphql -d '{"query":"{ orgPlresearchEditEvent(where: {target: {eq: \"…\"}}) { edges { node { editedAt } } } }"}'
Screenshotsassets/01-29*.png