plresearch-org · cms-editable-listings

CMS-editable listing pages — admin smoke test

@daviddao

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.

Date: 2026-05-05 · pass · Branch: feat/cms-editable-listings · Editor: satyam2.climateai.org (did:plc:cpoagodpqrgs4t7thi5z37uf) · Target: http://127.0.0.1:3001

agent-browser atproto oauth e2e cms

Headline result

8/8
Editors loading
8/8
Saves persisted
8/8
EditEvents written
2
Pre-existing issues surfaced

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.

What's in scope

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:

PhaseRouterkeySections exposed
1.1 fix/areas/economies-governance/subareas/area-eg-subareashero + 9 subareas
1.2 fix/areas/economies-governance/impact/area-eg-impacthero + 2 cards
2 new/insights/insightshero + 3 subpage cards
2 new/publications/publicationshero
2 new/talks/talkshero
2 new/tutorials/tutorialshero
2 new/blog/bloghero
2 new/authors/authorshero + 2 blurbs

Walkthrough

1. OAuth login as satyam2.climateai.org

01/admin/ — pre-login state.
02climateai.org PDS sign-in (password redacted).
03OAuth scope authorize — Bluesky · Repository · Authenticate.
04Admin panel — Satyam Two recognized as admin (DID did:plc:cpoagodpqrgs4t7thi5z37uf). Note: the new rkeys are not listed yet because the indexer only surfaces records that already exist.

2. Phase 1.1 — 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.

05Live page with the floating Edit button visible (admin-only).
06Editor — page header (hero) + section heading.
07All 9 subarea sections (DPI/DeSci/DePIN/PGF/DeFi/DeGov/Network States/Improving Nations/ReFi) each with title, tagline, description.
08After typing — “Unsaved changes” + Discard/Save bar.
09Save → bar resets to disabled. PUT /api/pages/area-eg-subareas → 200 in 3.4s.

3. Phase 1.2 — area-eg-impact (broken edit fixed)

10Editor — hero + 2 cards (Impact Report 2025, Live Dashboard).
11Live page after save — “(smoke-test)” suffix on the Impact Report card body.

4. Phase 2 — /insights/

12Live page with Edit button — was hardcoded before.
13Editor — hero + 3 subpage cards (Publications/Talks/Tutorials).
14Saved — “(smoke-test marker)” visible in hero subtitle.

5. Phase 2 — /publications/

15Live publications page — Edit button now wired up.
16Editor — hero only, with explicit “list is generated from content/publications/” explainer.
17Saved — hero updated; publications list untouched.

6. Phase 2 — /talks/, /tutorials/, /blog/

18Talks editor.
19Talks live, saved.
20Tutorials editor.
21Tutorials live, saved.
22Blog editor.
23Blog live, saved.

7. Phase 2 — /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.

24Live authors page — Edit button wired.
25Editor — hero + leadership-blurb + advisors-blurb. Author cards intentionally not editable here (source: content/authors/).
26Live page on the Advisors tab — “(smoke-test marker)” visible above the carousel.

8. Edit-history byline — verified on a separately-edited record

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):

27Opportunity-space page — byline renders “Last edited by Satyam Two · 13d ago · View history (2)”.
28Drawer — “2 edits”, per-event avatar, handle, relative time, changed fields. Footnote correctly attributes data to the editor's own PDS.

Verification — editEvents on the editor's PDS

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 rkeychangedFields
2026-05-04T19:46:00authorssections
2026-05-04T19:44:14blogsections
2026-05-04T19:43:10tutorialssections
2026-05-04T19:42:07talkssections
2026-05-04T19:41:05publicationssections
2026-05-04T19:39:39insightssections
2026-05-04T19:38:17area-eg-impactsections
2026-05-04T19:35:24area-eg-subareassections

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.

Findings

Pass — every editor on the PR is wired up correctly

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.

Warn — byline doesn't appear on the new pages until the indexer crawls (pre-existing)

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.

Warn — 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.

Info — admin panel doesn't list the new rkeys until they have records

/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.

Info — /research redirect produces stale breadcrumbs

Cosmetic, 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.

Reproducing

Branchfeat/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